mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-12-23 23:28:15 +01:00
Compare commits
85 Commits
websocket-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
767a090dc5 | ||
|
|
23ca97d5ec | ||
|
|
77abff48e1 | ||
|
|
7f8494a4b1 | ||
|
|
fdec046428 | ||
|
|
25d54efd3a | ||
|
|
7f4e3690db | ||
|
|
ebc1258ea6 | ||
|
|
dc68bf87cc | ||
|
|
853e8e2810 | ||
|
|
072781fcd9 | ||
|
|
7ce85c7389 | ||
|
|
1cc2c97022 | ||
|
|
f41c4a0ad0 | ||
|
|
7336451109 | ||
|
|
c5b9375b4c | ||
|
|
25513d0dc3 | ||
|
|
c0cf91e174 | ||
|
|
d979e1b333 | ||
|
|
9f9c6c12f3 | ||
|
|
742adea26d | ||
|
|
2535b3fefc | ||
|
|
ba0ef10038 | ||
|
|
f1e35977e5 | ||
|
|
25735ead9c | ||
|
|
bf9e955514 | ||
|
|
726c41f842 | ||
|
|
c7de9251ed | ||
|
|
0f6235f13e | ||
|
|
6c2c2cd22b | ||
|
|
9e0bcd4b08 | ||
|
|
07b1bcdc34 | ||
|
|
f20a234f65 | ||
|
|
63082b996d | ||
|
|
27d43277d2 | ||
|
|
4d8d25a345 | ||
|
|
bed116d98b | ||
|
|
9ec006a3e4 | ||
|
|
245b5a2ffb | ||
|
|
b9ea0c31ce | ||
|
|
4aa0e4ba49 | ||
|
|
c48c2ebe7e | ||
|
|
0ccaf2c0bb | ||
|
|
ed569d8509 | ||
|
|
9b2b1f680d | ||
|
|
370dfecc15 | ||
|
|
e50c5eb40e | ||
|
|
6f6110e30e | ||
|
|
41f7157ffb | ||
|
|
858f67c280 | ||
|
|
83ffeb0d12 | ||
|
|
6e99202a18 | ||
|
|
fb5279ae88 | ||
|
|
b70cc3fc09 | ||
|
|
911c2dbe9f | ||
|
|
c620855d56 | ||
|
|
cccfdd9315 | ||
|
|
ecc7258093 | ||
|
|
0caea67542 | ||
|
|
11a8567598 | ||
|
|
9b80a7ef7d | ||
|
|
12028ab250 | ||
|
|
68e299a357 | ||
|
|
2681b9b3c6 | ||
|
|
782b7cd119 | ||
|
|
3141d6cab5 | ||
|
|
7b8770e2fc | ||
|
|
6153c0002a | ||
|
|
3d5e11b82f | ||
|
|
eacc3a0aa8 | ||
|
|
2826287d43 | ||
|
|
3bfa00389d | ||
|
|
ace7fca8c6 | ||
|
|
2b2f009a65 | ||
|
|
1444d575f0 | ||
|
|
081eef88cf | ||
|
|
8b0704eaf4 | ||
|
|
4889dd6fcb | ||
|
|
134247d88f | ||
|
|
e772ce673d | ||
|
|
3838485229 | ||
|
|
ba3377b262 | ||
|
|
e5bed394ee | ||
|
|
65b58aa05a | ||
|
|
d1e6708063 |
24
.github/workflows/asio__build-target-test.yml
vendored
24
.github/workflows/asio__build-target-test.yml
vendored
@@ -81,18 +81,24 @@ jobs:
|
||||
with:
|
||||
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.example }}
|
||||
path: ${{ env.TEST_DIR }}/${{ matrix.example }}/build
|
||||
- name: Install Python packages
|
||||
env:
|
||||
PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple"
|
||||
run: |
|
||||
sudo apt-get install -y dnsutils
|
||||
- name: Download Example Test to target ${{ matrix.config }}
|
||||
run: |
|
||||
python -m esptool --chip ${{ matrix.idf_target }} write_flash 0x0 ${{ env.TEST_DIR }}/${{ matrix.example }}/build/flash_image.bin
|
||||
- name: Run Example Test ${{ matrix.example }} on target
|
||||
working-directory: ${{ env.TEST_DIR }}/${{ matrix.example }}
|
||||
run: |
|
||||
python -m pytest --log-cli-level DEBUG --junit-xml=./examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.config }}.xml --target=${{ matrix.idf_target }}
|
||||
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
|
||||
python -m pytest --log-cli-level DEBUG --junit-xml=./examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.config }}.xml --target=${{ matrix.idf_target }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
|
||||
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: ifconfig-basic, path: "components/console_cmd_ifconfig/examples"}]
|
||||
include:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
|
||||
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: mqtt_ssl_auth_console, path: "components/console_cmd_mqtt/examples" }]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
|
||||
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: ping-basic, path: "components/console_cmd_ping/examples" }]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
|
||||
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: wifi-basic, path: "components/console_cmd_wifi/examples" }]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
|
||||
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: console_basic, path: "components/console_simple_init/examples" }]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
2
.github/workflows/eppp__build.yml
vendored
2
.github/workflows/eppp__build.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.5", "release-v5.4", "release-v5.3"]
|
||||
idf_ver: ["latest", "release-v6.0", "release-v5.5", "release-v5.4", "release-v5.3"]
|
||||
test: [ { app: host, path: "examples/host" }, { app: slave, path: "examples/slave" }, { app: test_app, path: "test/test_app" }]
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
|
||||
@@ -13,10 +13,7 @@ jobs:
|
||||
name: Build examples
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
|
||||
include:
|
||||
- idf_ver: "latest"
|
||||
warning: "Warning: The smallest app partition is nearly full"
|
||||
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
env:
|
||||
@@ -27,7 +24,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
- name: Build with IDF-${{ matrix.idf_ver }}
|
||||
env:
|
||||
EXPECTED_WARNING: ${{ matrix.warning }}
|
||||
EXPECTED_WARNING: "Warning: The smallest app partition is nearly full"
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
@@ -75,7 +72,7 @@ jobs:
|
||||
needs: build_all_examples
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["release-v5.4", "latest"]
|
||||
idf_ver: ["release-v5.5", "latest"]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- modem
|
||||
|
||||
24
.github/workflows/lws_build.yml
vendored
24
.github/workflows/lws_build.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Libwebsockets build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.3", "release-v5.4"]
|
||||
idf_ver: ["release-v5.3", "release-v5.4", "release-v5.5"]
|
||||
test: [ { app: example, path: "examples/client" }]
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.3", "release-v5.4"]
|
||||
idf_ver: ["release-v5.3", "release-v5.4", "release-v5.5"]
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: example, path: "examples/client" }]
|
||||
runs-on:
|
||||
@@ -65,14 +65,24 @@ jobs:
|
||||
with:
|
||||
name: lws_target_esp32_${{ matrix.idf_ver }}_${{ matrix.test.app }}
|
||||
path: ${{ env.TEST_DIR }}/ci/
|
||||
- name: Install Python packages
|
||||
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 --only-binary cryptography --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
|
||||
|
||||
63
.github/workflows/mdns__host-tests.yml
vendored
63
.github/workflows/mdns__host-tests.yml
vendored
@@ -29,6 +29,8 @@ jobs:
|
||||
# Build host tests app (with all configs and targets supported)
|
||||
python ./ci/build_apps.py components/mdns/tests/host_test/
|
||||
cd components/mdns/tests/host_test
|
||||
ls -la
|
||||
ls -ls build*
|
||||
# First run the linux_app and send a quick A query and a reverse query
|
||||
./build_linux_app/mdns_host.elf &
|
||||
python dnsfixture.py A myesp.local --ip_only | xargs python dnsfixture.py X
|
||||
@@ -55,8 +57,11 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
cd components/mdns/tests/test_afl_fuzz_host/
|
||||
make INSTR=off
|
||||
cd components/mdns/tests/host_unit_test/
|
||||
idf.py reconfigure
|
||||
mkdir build2 && cd build2
|
||||
cmake ..
|
||||
cmake --build .
|
||||
- name: Test no malloc functions
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -69,6 +74,39 @@ jobs:
|
||||
echo "OK"
|
||||
done
|
||||
|
||||
host_unit_test:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'mdns') || github.event_name == 'push'
|
||||
name: Unit tests on host
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@v4
|
||||
- name: Install bsdlib and ruby
|
||||
run: |
|
||||
apt-get update -y
|
||||
apt-get install -y libbsd-dev ruby
|
||||
- name: Build and run unit tests
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
cd components/mdns/tests/host_unit_test/
|
||||
idf.py reconfigure
|
||||
mkdir build2 && cd build2
|
||||
cmake -DUNIT_TESTS=test_receiver ..
|
||||
cmake --build .
|
||||
ctest --extra-verbose
|
||||
cd ..
|
||||
mkdir build3 && cd build3
|
||||
cmake -DUNIT_TESTS=test_sender ..
|
||||
cmake --build .
|
||||
ctest --extra-verbose
|
||||
|
||||
|
||||
fuzz_test:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'mdns-fuzz') || github.event_name == 'push'
|
||||
name: Fuzzer tests for mdns lib
|
||||
@@ -77,7 +115,7 @@ jobs:
|
||||
idf_ver: ["latest"]
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
container: aflplusplus/aflplusplus
|
||||
container: aflplusplus/aflplusplus:v4.34c
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@v4
|
||||
@@ -98,13 +136,26 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
export IDF_PATH=$GITHUB_WORKSPACE/idf
|
||||
cd components/mdns/tests/test_afl_fuzz_host/
|
||||
make fuzz
|
||||
cd components/mdns/tests/host_unit_test/
|
||||
pip install dnslib
|
||||
cd input && python generate_cases.py && cd ..
|
||||
cmake -B build2 -S . -G "Ninja" -DCMAKE_C_COMPILER=afl-cc
|
||||
cmake --build build2
|
||||
timeout 10m afl-fuzz -i input -o out -- build2/mdns_host_unit_test || \
|
||||
if [ $? -eq 124 ]; then # timeout exit code
|
||||
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
|
||||
|
||||
- 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
|
||||
path: components/mdns/tests/host_unit_test/out/default/crashes.tar.gz
|
||||
if-no-files-found: ignore
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Build examples
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
|
||||
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
|
||||
example: ["pppos_client", "modem_console", "modem_tcp_client", "ap_to_pppos", "simple_cmux_client"]
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
name: Build tests
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "latest"]
|
||||
idf_ver: ["latest","release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
|
||||
test: ["target", "target_ota", "target_iperf", "target_urc"]
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
4
.github/workflows/modem__target-test.yml
vendored
4
.github/workflows/modem__target-test.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
name: Build Target tests
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_ver: ["latest", "release-v5.5", "release-v6.0"]
|
||||
idf_target: ["esp32c3"]
|
||||
test: [ { app: pppd, path: test/target }, { app: pppd_chap_auth, path: test/target }, { app: sim800_c3, path: examples/pppos_client }, { app: sim800_cmux, path: examples/simple_cmux_client } ]
|
||||
include:
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
name: Run Target tests
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_ver: ["latest", "release-v5.5", "release-v6.0"]
|
||||
idf_target: ["esp32c3"]
|
||||
test: [ { app: pppd, path: test/target }, { app: pppd_chap_auth, path: test/target }, { app: sim800_c3, path: examples/pppos_client }, { app: sim800_cmux, path: examples/simple_cmux_client } ]
|
||||
include:
|
||||
|
||||
48
.github/workflows/mosq__build.yml
vendored
48
.github/workflows/mosq__build.yml
vendored
@@ -13,13 +13,15 @@ jobs:
|
||||
name: Mosquitto build
|
||||
strategy:
|
||||
matrix:
|
||||
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"
|
||||
idf_ver: ["latest", "release-v6.0", "release-v5.5", "release-v5.4", "release-v5.3", "release-v5.2", "release-v5.1"]
|
||||
example: ["broker"]
|
||||
include:
|
||||
# serverless_mqtt is not supported on >=v6.0 (esp-peer dependency)
|
||||
- idf_ver: "release-v5.3"
|
||||
example: "serverless_mqtt"
|
||||
- idf_ver: "latest"
|
||||
- idf_ver: "release-v5.4"
|
||||
example: "serverless_mqtt"
|
||||
- idf_ver: "release-v5.5"
|
||||
example: "serverless_mqtt"
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -61,7 +63,7 @@ jobs:
|
||||
needs: build_mosq
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.3"]
|
||||
idf_ver: ["release-v5.4", "release-v5.5", "release-v6.0", "latest"]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- ESP32-ETHERNET-KIT
|
||||
@@ -77,6 +79,20 @@ jobs:
|
||||
- name: Run Test
|
||||
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
|
||||
python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results
|
||||
unzip ci/artifacts.zip -d ci
|
||||
for dir in `ls -d ci/build_*`; do
|
||||
@@ -120,7 +136,7 @@ jobs:
|
||||
name: Build IDF tests
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_ver: ["release-v5.5"] # TODO: add release-v6.0/latest with esp-mqtt directly
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: publish, path: "tools/test_apps/protocols/mqtt/publish_connect_test" }]
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -165,7 +181,7 @@ jobs:
|
||||
needs: build_idf_tests_with_mosq
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_ver: ["release-v5.5"]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- ESP32-ETHERNET-KIT
|
||||
@@ -180,6 +196,20 @@ jobs:
|
||||
- name: Run Test
|
||||
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
|
||||
python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results "paho-mqtt<2" --upgrade
|
||||
unzip ci/artifacts.zip -d ci
|
||||
for dir in `ls -d ci/build_*`; do
|
||||
|
||||
7
.github/workflows/mqtt_cxx__build.yml
vendored
7
.github/workflows/mqtt_cxx__build.yml
vendored
@@ -13,9 +13,9 @@ jobs:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
|
||||
idf_ver: ["release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5"]
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: mqtt-basic, path: "components/esp_mqtt_cxx/examples" }]
|
||||
test: [ { app: mqtt-basic, path: "components/esp_mqtt_cxx/examples" }, { app: test, path: "components/esp_mqtt_cxx/test/unit" }]
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
steps:
|
||||
@@ -25,8 +25,7 @@ jobs:
|
||||
submodules: recursive
|
||||
- name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
|
||||
shell: bash
|
||||
working-directory: ${{matrix.test.path}}
|
||||
run: |
|
||||
. ${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
|
||||
python ./ci/build_apps.py ./${{ matrix.test.path }} --target ${{ matrix.idf_target }} -vv --preserve-all -c
|
||||
|
||||
16
.github/workflows/sockutls_build.yml
vendored
16
.github/workflows/sockutls_build.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
pip install idf-component-manager idf-build-apps --upgrade
|
||||
pip install idf-build-apps --upgrade
|
||||
python ci/build_apps.py ${TEST_DIR}
|
||||
cd ${TEST_DIR}
|
||||
${GITHUB_WORKSPACE}/ci/clean_build_artifacts.sh `pwd`/${TARGET_TEST_DIR}
|
||||
@@ -87,6 +87,20 @@ jobs:
|
||||
- name: Run Test
|
||||
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
|
||||
unzip ci/artifacts.zip -d ci
|
||||
for dir in `ls -d ci/build_*`; do
|
||||
rm -rf build sdkconfig.defaults
|
||||
|
||||
2
.github/workflows/tls_cxx__build.yml
vendored
2
.github/workflows/tls_cxx__build.yml
vendored
@@ -26,5 +26,5 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
pip install idf-component-manager idf-build-apps --upgrade
|
||||
pip install idf-build-apps --upgrade
|
||||
python ./ci/build_apps.py ./components/mbedtls_cxx/${{ matrix.test.path }} -vv --preserve-all
|
||||
|
||||
@@ -5,3 +5,5 @@ Warning: Deprecated: Option '--flash_freq' is deprecated. Use '--flash-freq' ins
|
||||
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'
|
||||
WARNING: The following Kconfig variables were used in "if" clauses, but not
|
||||
warning: unknown kconfig symbol 'LIBC_NEWLIB'
|
||||
|
||||
63
common_components/modem_sim/esp32c6.sdkconfig.defaults
Normal file
63
common_components/modem_sim/esp32c6.sdkconfig.defaults
Normal file
@@ -0,0 +1,63 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) 5.4.1 Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32c6"
|
||||
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
|
||||
CONFIG_APP_PROJECT_VER_FROM_CONFIG=y
|
||||
CONFIG_APP_PROJECT_VER="v4.1.0.0-dev"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="module_config/module_esp32c6_default/partitions_at.csv"
|
||||
CONFIG_PARTITION_TABLE_MD5=n
|
||||
CONFIG_AT_CUSTOMIZED_PARTITION_TABLE_FILE="module_config/module_esp32c6_default/at_customize.csv"
|
||||
CONFIG_AT_CUSTOMIZED_PARTITION_TABLE_OFFSET=0x1e000
|
||||
CONFIG_ESP_TLS_PSK_VERIFICATION=y
|
||||
CONFIG_ESP_TLS_INSECURE=y
|
||||
CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y
|
||||
CONFIG_ESP_ERR_TO_NAME_LOOKUP=n
|
||||
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
|
||||
CONFIG_HTTPD_MAX_URI_LEN=1024
|
||||
CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=y
|
||||
CONFIG_RTC_CLK_SRC_EXT_CRYS=y
|
||||
CONFIG_RTC_CLK_CAL_CYCLES=1024
|
||||
CONFIG_ESP_PHY_MAC_BB_PD=y
|
||||
CONFIG_PM_ENABLE=y
|
||||
CONFIG_PM_DFS_INIT_AUTO=y
|
||||
CONFIG_ESP_TASK_WDT_PANIC=y
|
||||
CONFIG_ESP_TASK_WDT_TIMEOUT_S=60
|
||||
CONFIG_ESP_DEBUG_OCDAWARE=n
|
||||
CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT=y
|
||||
CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0
|
||||
CONFIG_FATFS_LFN_HEAP=y
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
|
||||
CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=n
|
||||
CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y
|
||||
CONFIG_LOG_DEFAULT_LEVEL_ERROR=y
|
||||
CONFIG_LWIP_MAX_SOCKETS=16
|
||||
CONFIG_LWIP_SO_LINGER=y
|
||||
CONFIG_LWIP_SO_RCVBUF=y
|
||||
CONFIG_LWIP_IP4_REASSEMBLY=y
|
||||
CONFIG_LWIP_IP6_REASSEMBLY=y
|
||||
CONFIG_LWIP_IPV6_AUTOCONFIG=y
|
||||
CONFIG_LWIP_TCP_MAXRTX=6
|
||||
CONFIG_LWIP_TCP_SYNMAXRTX=3
|
||||
CONFIG_LWIP_PPP_SUPPORT=y
|
||||
CONFIG_LWIP_PPP_SERVER_SUPPORT=y
|
||||
CONFIG_LWIP_SNTP_MAX_SERVERS=3
|
||||
CONFIG_LWIP_SNTP_STARTUP_DELAY=n
|
||||
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
|
||||
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
|
||||
CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=n
|
||||
CONFIG_MBEDTLS_HAVE_TIME_DATE=y
|
||||
CONFIG_MBEDTLS_DHM_C=y
|
||||
CONFIG_NEWLIB_NANO_FORMAT=y
|
||||
CONFIG_VFS_SUPPORT_TERMIOS=n
|
||||
CONFIG_WL_SECTOR_SIZE_512=y
|
||||
CONFIG_AT_PROCESS_TASK_STACK_SIZE=6144
|
||||
CONFIG_AT_MDNS_COMMAND_SUPPORT=n
|
||||
CONFIG_AT_WPS_COMMAND_SUPPORT=n
|
||||
CONFIG_AT_SMARTCONFIG_COMMAND_SUPPORT=n
|
||||
CONFIG_AT_PING_COMMAND_SUPPORT=n
|
||||
CONFIG_LWIP_IP_FORWARD=y
|
||||
CONFIG_LWIP_IPV4_NAPT=y
|
||||
@@ -2,9 +2,10 @@
|
||||
set -e
|
||||
|
||||
# Create directory "modem_sim_esp32", go inside it
|
||||
# Usage: ./install.sh [platform] [module]
|
||||
# Usage: ./install.sh [platform] [module] [uart_tx_pin] [uart_rx_pin]
|
||||
|
||||
SCRIPT_DIR=$(pwd)
|
||||
UPDATE_UART_PINS_SCRIPT="$(cd "$(dirname "$0")" && pwd)/update_uart_pins.py"
|
||||
mkdir -p modem_sim_esp32
|
||||
cd modem_sim_esp32
|
||||
|
||||
@@ -39,6 +40,8 @@ mkdir -p build
|
||||
# Default values for platform and module
|
||||
platform="PLATFORM_ESP32"
|
||||
module="WROOM-32"
|
||||
uart_tx_pin=""
|
||||
uart_rx_pin=""
|
||||
|
||||
# Override defaults if parameters are provided
|
||||
if [ ! -z "$1" ]; then
|
||||
@@ -47,18 +50,49 @@ fi
|
||||
if [ ! -z "$2" ]; then
|
||||
module="$2"
|
||||
fi
|
||||
if [ ! -z "$3" ]; then
|
||||
uart_tx_pin="$3"
|
||||
fi
|
||||
if [ ! -z "$4" ]; then
|
||||
uart_rx_pin="$4"
|
||||
fi
|
||||
|
||||
target="${platform##*_}"
|
||||
target="${target,,}"
|
||||
|
||||
# Use provided pins for description when present; otherwise keep defaults
|
||||
description="4MB, Wi-Fi + BLE, OTA, TX:17 RX:16"
|
||||
if [ -n "$uart_tx_pin" ] || [ -n "$uart_rx_pin" ]; then
|
||||
desc_tx=${uart_tx_pin:-17}
|
||||
desc_rx=${uart_rx_pin:-16}
|
||||
description="4MB, Wi-Fi + BLE, OTA, TX:${desc_tx} RX:${desc_rx}"
|
||||
fi
|
||||
|
||||
# Create file "build/module_info.json" with content
|
||||
cat > build/module_info.json << EOF
|
||||
{
|
||||
"platform": "$platform",
|
||||
"module": "$module",
|
||||
"description": "4MB, Wi-Fi + BLE, OTA, TX:17 RX:16",
|
||||
"description": "$description",
|
||||
"silence": 0
|
||||
}
|
||||
EOF
|
||||
|
||||
cp "$SCRIPT_DIR/sdkconfig.defaults" "module_config/module_esp32_default/sdkconfig.defaults"
|
||||
# Optionally update UART pins in factory_param_data.csv for the selected module
|
||||
if [ -n "$uart_tx_pin" ] || [ -n "$uart_rx_pin" ]; then
|
||||
csv_path="components/customized_partitions/raw_data/factory_param/factory_param_data.csv"
|
||||
if [ ! -f "$csv_path" ]; then
|
||||
echo "Warning: $csv_path not found; skipping UART pin update."
|
||||
else
|
||||
python3 "$UPDATE_UART_PINS_SCRIPT" "$platform" "$module" "$uart_tx_pin" "$uart_rx_pin" "$csv_path"
|
||||
echo "Updated UART pins in $csv_path"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copy the platform-specific sdkconfig.defaults file if it exists
|
||||
if [ -f "$SCRIPT_DIR/${target}.sdkconfig.defaults" ]; then
|
||||
cp "$SCRIPT_DIR/${target}.sdkconfig.defaults" "module_config/module_${target}_default/sdkconfig.defaults"
|
||||
fi
|
||||
|
||||
echo "Installation completed successfully!"
|
||||
echo "Created modem_sim_esp32 directory with esp-at repository and configuration"
|
||||
|
||||
46
common_components/modem_sim/update_uart_pins.py
Normal file
46
common_components/modem_sim/update_uart_pins.py
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import csv
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
|
||||
def update_uart_pins(platform: str, module: str, tx_pin: str, rx_pin: str, csv_path: str) -> None:
|
||||
path = pathlib.Path(csv_path)
|
||||
rows = []
|
||||
found = False
|
||||
|
||||
with path.open(newline="") as f:
|
||||
reader = csv.DictReader(f)
|
||||
fieldnames = reader.fieldnames
|
||||
for row in reader:
|
||||
if row.get("platform") == platform and row.get("module_name") == module:
|
||||
if tx_pin:
|
||||
row["uart_tx_pin"] = tx_pin
|
||||
if rx_pin:
|
||||
row["uart_rx_pin"] = rx_pin
|
||||
found = True
|
||||
rows.append(row)
|
||||
|
||||
if not found:
|
||||
print(f"Warning: no row updated for platform={platform} module={module}")
|
||||
|
||||
with path.open("w", newline="") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 6:
|
||||
print("Usage: update_uart_pins.py <platform> <module> <uart_tx_pin> <uart_rx_pin> <csv_path>")
|
||||
return 1
|
||||
|
||||
platform, module, tx_pin, rx_pin, csv_path = sys.argv[1:6]
|
||||
update_uart_pins(platform, module, tx_pin, rx_pin, csv_path)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -7,5 +7,6 @@
|
||||
|
||||
#include "sys/socket.h"
|
||||
#include "socketpair.h"
|
||||
#include "asio_stub.hpp"
|
||||
|
||||
#include_next "asio/detail/config.hpp"
|
||||
|
||||
9
components/asio/port/include/asio_stub.hpp
Normal file
9
components/asio/port/include/asio_stub.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
#include <signal.h>
|
||||
|
||||
extern "C" int pthread_sigmask(int, const sigset_t *, sigset_t *);
|
||||
@@ -9,4 +9,8 @@ dependencies:
|
||||
override_path: '../console_simple_init'
|
||||
public: true
|
||||
espressif/ethernet_init:
|
||||
version: '>=0.0.7'
|
||||
matches:
|
||||
- if: idf_version >=6.0
|
||||
version: ^1.0.0
|
||||
- if: idf_version <6.0
|
||||
version: '==0.3.0'
|
||||
|
||||
@@ -9,3 +9,7 @@ dependencies:
|
||||
version: '>=1.1.0'
|
||||
override_path: '../console_simple_init'
|
||||
public: true
|
||||
espressif/mqtt:
|
||||
rules:
|
||||
- if: idf_version >=6.0
|
||||
version: ^1.0.0
|
||||
|
||||
@@ -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.1.3
|
||||
version: 1.1.4
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## [1.1.4](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.4)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed missing freertos deps ([f1e35977](https://github.com/espressif/esp-protocols/commit/f1e35977))
|
||||
- Add optional mqtt dependency ([911c2dbe](https://github.com/espressif/esp-protocols/commit/911c2dbe))
|
||||
|
||||
## [1.1.3](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.3)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/sdio_slave.h"
|
||||
#include "esp_serial_slave_link/essl_sdio.h"
|
||||
#include "eppp_sdio.h"
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/sdio_slave.h"
|
||||
#include "eppp_link.h"
|
||||
#include "eppp_transport.h"
|
||||
|
||||
@@ -5,3 +5,7 @@ dependencies:
|
||||
override_path: ../../..
|
||||
console_cmd_ping:
|
||||
version: '*'
|
||||
espressif/mqtt:
|
||||
rules:
|
||||
- if: idf_version >=6.0
|
||||
version: ^1.0.0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: 1.1.3
|
||||
version: 1.1.4
|
||||
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:
|
||||
|
||||
@@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(modem): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py esp_modem
|
||||
tag_format: modem-v$version
|
||||
version: 1.4.0
|
||||
version: 2.0.0
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## [2.0.0](https://github.com/espressif/esp-protocols/commits/modem-v2.0.0)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- inc headers for AT command definitions are no longer used directly, but pregenerated into *.h(pp) ([Use generated AT command definitions for IDE navigation](https://github.com/espressif/esp-protocols/commit/e2fa1110))
|
||||
|
||||
### Features
|
||||
|
||||
- Add support for multiple connection in AT based example ([2826287d](https://github.com/espressif/esp-protocols/commit/2826287d))
|
||||
- Add enhanced URC observer API ([4889dd6f](https://github.com/espressif/esp-protocols/commit/4889dd6f))
|
||||
- Support esp-modem use without PPP ([858f8570](https://github.com/espressif/esp-protocols/commit/858f8570), [#851](https://github.com/espressif/esp-protocols/issues/851))
|
||||
- Modem simulator based on esp-at ([e5787e3d](https://github.com/espressif/esp-protocols/commit/e5787e3d))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Update tests and examples to use modem-v2.0 ([4aa0e4ba](https://github.com/espressif/esp-protocols/commit/4aa0e4ba))
|
||||
- Replace MQTT client with simple ping command ([0ccaf2c0](https://github.com/espressif/esp-protocols/commit/0ccaf2c0))
|
||||
- Replace MQTT client with simple ping command ([9b2b1f68](https://github.com/espressif/esp-protocols/commit/9b2b1f68))
|
||||
- Update example to use optional mqtt deps ([3141d6ca](https://github.com/espressif/esp-protocols/commit/3141d6ca))
|
||||
- Minor fixed in the test code ([e772ce67](https://github.com/espressif/esp-protocols/commit/e772ce67))
|
||||
- Add missing set_echo() C wrapper ([d1e67080](https://github.com/espressif/esp-protocols/commit/d1e67080), [#926](https://github.com/espressif/esp-protocols/issues/926))
|
||||
- Fix modem console dependencies ([453be4cd](https://github.com/espressif/esp-protocols/commit/453be4cd))
|
||||
- Address build issues ([018ba58e](https://github.com/espressif/esp-protocols/commit/018ba58e))
|
||||
- Fix driver dependency issue on v6.0 ([67c682d9](https://github.com/espressif/esp-protocols/commit/67c682d9))
|
||||
- Fix CI build issues with IDFv6.0 ([15140e04](https://github.com/espressif/esp-protocols/commit/15140e04))
|
||||
- Add support for ESP-AT based tcp-client example ([14d3cb6b](https://github.com/espressif/esp-protocols/commit/14d3cb6b))
|
||||
- Use idf-build-apps for building target tests ([e9d9b3a8](https://github.com/espressif/esp-protocols/commit/e9d9b3a8))
|
||||
- Make MQTT public broker endpoint configurable ([6d541194](https://github.com/espressif/esp-protocols/commit/6d541194))
|
||||
- Fix URC handling in DTE data callback ([93029946](https://github.com/espressif/esp-protocols/commit/93029946))
|
||||
- Use another public broker for examples and tests ([fac2edbe](https://github.com/espressif/esp-protocols/commit/fac2edbe))
|
||||
- Fix incompatible iterator in std::search() in new gcc ([ed0f6334](https://github.com/espressif/esp-protocols/commit/ed0f6334))
|
||||
- Fix autodetect to support ACFC mode in PPP frames ([8b328a69](https://github.com/espressif/esp-protocols/commit/8b328a69), [#801](https://github.com/espressif/esp-protocols/issues/801))
|
||||
- Fix get_network_registration_state() to accept two params ([5f54d907](https://github.com/espressif/esp-protocols/commit/5f54d907), [#826](https://github.com/espressif/esp-protocols/issues/826))
|
||||
- Consume buffer after handled URC ([6eceb28f](https://github.com/espressif/esp-protocols/commit/6eceb28f))
|
||||
- Use generated AT command definitions for IDE navigation ([e2fa1110](https://github.com/espressif/esp-protocols/commit/e2fa1110), !BREAKING)
|
||||
|
||||
## [1.4.0](https://github.com/espressif/esp-protocols/commits/modem-v1.4.0)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dependencies:
|
||||
espressif/esp_modem:
|
||||
version: "^1.0.1"
|
||||
version: "^2"
|
||||
override_path: "../../../"
|
||||
|
||||
@@ -198,7 +198,7 @@ extern "C" bool modem_stop_network()
|
||||
|
||||
extern "C" void modem_deinit_network()
|
||||
{
|
||||
free(dce);
|
||||
delete dce;
|
||||
dce = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dependencies:
|
||||
espressif/esp_modem:
|
||||
version: "^1.0.1"
|
||||
version: "^2"
|
||||
override_path: "../../../"
|
||||
|
||||
@@ -3,7 +3,7 @@ dependencies:
|
||||
## Required IDF version
|
||||
idf: ">=4.1.0"
|
||||
espressif/esp_modem:
|
||||
version: "^1.0.0"
|
||||
version: "^2"
|
||||
override_path: "../../../"
|
||||
espressif/esp_modem_usb_dte:
|
||||
version: "^1.2.0"
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -97,10 +97,21 @@ void wakeup_modem(void)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
command_result handle_urc(uint8_t *data, size_t len)
|
||||
esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo &info)
|
||||
{
|
||||
ESP_LOG_BUFFER_HEXDUMP("on_read", data, len, ESP_LOG_INFO);
|
||||
return command_result::TIMEOUT;
|
||||
// Log buffer information for debugging
|
||||
ESP_LOGI(TAG, "URC Buffer Info: total_size=%zu, processed_offset=%zu, new_data_size=%zu, command_active=%s",
|
||||
info.buffer_total_size, info.processed_offset, info.new_data_size,
|
||||
info.is_command_active ? "true" : "false");
|
||||
|
||||
// Log the new data content
|
||||
if (info.new_data_size > 0) {
|
||||
ESP_LOG_BUFFER_HEXDUMP("on_read", info.new_data_start, info.new_data_size, ESP_LOG_INFO);
|
||||
}
|
||||
|
||||
// For console example, we just log and don't consume anything
|
||||
// This allows the data to be processed by command handlers
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -381,14 +392,14 @@ extern "C" void app_main(void)
|
||||
CHECK_ERR(dce->reset(), ESP_LOGI(TAG, "OK"));
|
||||
});
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
const ConsoleCommand HandleURC("urc", "toggle urc handling", no_args, [&](ConsoleCommand * c) {
|
||||
const ConsoleCommand HandleURC("urc", "toggle enhanced urc handling", no_args, [&](ConsoleCommand * c) {
|
||||
static int cnt = 0;
|
||||
if (++cnt % 2) {
|
||||
ESP_LOGI(TAG, "Adding URC handler");
|
||||
dce->set_urc(handle_urc);
|
||||
ESP_LOGI(TAG, "Adding enhanced URC handler");
|
||||
dce->set_enhanced_urc(handle_enhanced_urc);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "URC removed");
|
||||
dce->set_urc(nullptr);
|
||||
ESP_LOGI(TAG, "Enhanced URC removed");
|
||||
dce->set_enhanced_urc(nullptr);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dependencies:
|
||||
espressif/esp_modem:
|
||||
version: "^1.0.1"
|
||||
version: "^2"
|
||||
override_path: "../../../"
|
||||
|
||||
@@ -22,3 +22,18 @@ To enable this mode, please set `EXAMPLE_CUSTOM_TCP_TRANSPORT=y`
|
||||
This configuration could be used with any network library, which is connecting to a localhost endpoint instead of remote one. This example creates a localhost listener which basically mimics the remote endpoint by forwarding the traffic between the library and the TCP/socket layer of the modem (which is already secure if the TLS is used in the network library)
|
||||
|
||||

|
||||
|
||||
### Multi-connection support
|
||||
|
||||
This example supports opening multiple TCP connections concurrently when the modem firmware allows it.
|
||||
|
||||
- ESP-AT: Multi-connection mode is enabled via `AT+CIPMUX=1`. The example assigns a unique link ID per DCE instance and includes the link ID in `CIPSTART/CIPSEND/CIPRECVDATA` commands.
|
||||
- BG96/SIM7600: The example uses module-specific multi-connection syntax (for example `QIOPEN/CIPOPEN` with a connection ID) and tracks link IDs internally.
|
||||
|
||||
How it works:
|
||||
- The `sock_dce` layer creates multiple DCE instances over a shared DTE. A lightweight mutex coordinates access to the UART so only one DCE issues AT commands at a time.
|
||||
- Asynchronous URCs (for example `+IPD`, `+QIURC`, `+CIPRXGET: 1,<cid>`) wake the corresponding DCE which then performs receive operations for its link.
|
||||
|
||||
Usage:
|
||||
- `app_main` starts two DCE tasks to demonstrate concurrent connections. Adjust the number of DCE instances as needed.
|
||||
- For ESP-AT, ensure your firmware supports `CIPMUX=1` and passive receive (`CIPRECVTYPE`).
|
||||
|
||||
@@ -44,6 +44,18 @@ menu "Example Configuration"
|
||||
help
|
||||
Set APN (Access Point Name), a logical name to choose data network
|
||||
|
||||
config EXAMPLE_USE_TLS
|
||||
bool "Use TLS for MQTT broker"
|
||||
default n
|
||||
help
|
||||
Enable TLS for connection to the MQTT broker.
|
||||
|
||||
config EXAMPLE_BROKER_HOST
|
||||
string "MQTT broker host"
|
||||
default "test.mosquitto.org"
|
||||
help
|
||||
Hostname or IP address of the MQTT broker.
|
||||
|
||||
menu "UART Configuration"
|
||||
config EXAMPLE_MODEM_UART_TX_PIN
|
||||
int "TXD Pin Number"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <sys/socket.h>
|
||||
#include "esp_vfs.h"
|
||||
@@ -14,6 +15,29 @@
|
||||
namespace sock_dce {
|
||||
|
||||
constexpr auto const *TAG = "sock_dce";
|
||||
constexpr auto WAIT_TO_IDLE_TIMEOUT = 5000;
|
||||
|
||||
// Definition of the static member variables
|
||||
std::vector<DCE *> DCE::dce_list{};
|
||||
bool DCE::network_init = false;
|
||||
int Responder::s_link_id = 0;
|
||||
SemaphoreHandle_t Responder::s_dte_mutex{};
|
||||
|
||||
// Constructor - add this DCE instance to the static list
|
||||
DCE::DCE(std::shared_ptr<esp_modem::DTE> dte_arg, const esp_modem_dce_config *config)
|
||||
: Module(std::move(dte_arg), config)
|
||||
{
|
||||
dce_list.push_back(this);
|
||||
}
|
||||
|
||||
// Destructor - remove this DCE instance from the static list
|
||||
DCE::~DCE()
|
||||
{
|
||||
auto it = std::find(dce_list.begin(), dce_list.end(), this);
|
||||
if (it != dce_list.end()) {
|
||||
dce_list.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool DCE::perform_sock()
|
||||
@@ -61,13 +85,26 @@ bool DCE::perform_sock()
|
||||
|
||||
void DCE::perform_at(uint8_t *data, size_t len)
|
||||
{
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE);
|
||||
if (state != status::RECEIVING) {
|
||||
std::string_view resp_sv((char *)data, len);
|
||||
at.check_urc(state, resp_sv);
|
||||
if (state == status::IDLE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Trace incoming AT bytes when handling a response; use DEBUG level
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_DEBUG);
|
||||
switch (at.process_data(state, data, len)) {
|
||||
case Responder::ret::OK:
|
||||
// Release DTE access for this link after processing data
|
||||
ESP_LOGD(TAG, "GIVE data %d", at.link_id);
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
state = status::IDLE;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Responder::ret::FAIL:
|
||||
ESP_LOGD(TAG, "GIVE data %d", at.link_id);
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
state = status::FAILED;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
@@ -82,10 +119,14 @@ void DCE::perform_at(uint8_t *data, size_t len)
|
||||
std::string_view response((char *)data, len);
|
||||
switch (at.check_async_replies(state, response)) {
|
||||
case Responder::ret::OK:
|
||||
ESP_LOGD(TAG, "GIVE command %d", at.link_id);
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
state = status::IDLE;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Responder::ret::FAIL:
|
||||
ESP_LOGD(TAG, "GIVE command %d", at.link_id);
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
state = status::FAILED;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
@@ -121,7 +162,7 @@ bool DCE::at_to_sock()
|
||||
uint64_t data;
|
||||
read(data_ready_fd, &data, sizeof(data));
|
||||
ESP_LOGD(TAG, "select read: modem data available %" PRIu64, data);
|
||||
if (!signal.wait(IDLE, 1000)) {
|
||||
if (!signal.wait(IDLE, WAIT_TO_IDLE_TIMEOUT)) {
|
||||
ESP_LOGE(TAG, "Failed to get idle");
|
||||
close_sock();
|
||||
return false;
|
||||
@@ -131,6 +172,10 @@ bool DCE::at_to_sock()
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
// Take DTE mutex before issuing receive on this link
|
||||
ESP_LOGD(TAG, "TAKE RECV %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD(TAG, "TAKEN RECV %d", at.link_id);
|
||||
state = status::RECEIVING;
|
||||
at.start_receiving(at.get_buf_len());
|
||||
return true;
|
||||
@@ -139,7 +184,7 @@ bool DCE::at_to_sock()
|
||||
bool DCE::sock_to_at()
|
||||
{
|
||||
ESP_LOGD(TAG, "socket read: data available");
|
||||
if (!signal.wait(IDLE, 1000)) {
|
||||
if (!signal.wait(IDLE, WAIT_TO_IDLE_TIMEOUT)) {
|
||||
ESP_LOGE(TAG, "Failed to get idle");
|
||||
close_sock();
|
||||
return false;
|
||||
@@ -149,6 +194,10 @@ bool DCE::sock_to_at()
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
// Take DTE mutex before issuing send on this link
|
||||
ESP_LOGD(TAG, "TAKE SEND %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD(TAG, "TAKEN SEND %d", at.link_id);
|
||||
state = status::SENDING;
|
||||
int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0);
|
||||
if (len < 0) {
|
||||
@@ -201,7 +250,7 @@ void DCE::start_listening(int port)
|
||||
}
|
||||
int opt = 1;
|
||||
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
ESP_LOGI(TAG, "Socket created");
|
||||
ESP_LOGD(TAG, "Socket created");
|
||||
struct sockaddr_in addr = { };
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
@@ -213,7 +262,7 @@ void DCE::start_listening(int port)
|
||||
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Socket bound, port %d", 1883);
|
||||
ESP_LOGD(TAG, "Socket bound, port %d", 1883);
|
||||
err = listen(listen_sock, 1);
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
|
||||
@@ -224,12 +273,12 @@ void DCE::start_listening(int port)
|
||||
|
||||
bool DCE::connect(std::string host, int port)
|
||||
{
|
||||
dte->on_read(nullptr);
|
||||
tcp_close();
|
||||
dte->on_read([this](uint8_t *data, size_t len) {
|
||||
this->perform_at(data, len);
|
||||
return esp_modem::command_result::TIMEOUT;
|
||||
});
|
||||
data_ready_fd = eventfd(0, EFD_SUPPORT_ISR);
|
||||
assert(data_ready_fd > 0);
|
||||
// Take DTE mutex before starting connect for this link
|
||||
ESP_LOGD(TAG, "TAKE CONNECT %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD(TAG, "TAKEN CONNECT %d", at.link_id);
|
||||
if (!at.start_connecting(host, port)) {
|
||||
ESP_LOGE(TAG, "Unable to start connecting");
|
||||
dte->on_read(nullptr);
|
||||
@@ -241,12 +290,15 @@ bool DCE::connect(std::string host, int port)
|
||||
|
||||
bool DCE::init()
|
||||
{
|
||||
if (network_init) {
|
||||
return true;
|
||||
}
|
||||
network_init = true;
|
||||
Responder::s_dte_mutex = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
esp_vfs_eventfd_register(&config);
|
||||
|
||||
data_ready_fd = eventfd(0, EFD_SUPPORT_ISR);
|
||||
assert(data_ready_fd > 0);
|
||||
|
||||
dte->on_read(nullptr);
|
||||
const int retries = 5;
|
||||
int i = 0;
|
||||
@@ -287,6 +339,10 @@ bool DCE::init()
|
||||
esp_modem::Task::Delay(5000);
|
||||
}
|
||||
ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str());
|
||||
dte->on_read([](uint8_t *data, size_t len) {
|
||||
read_callback(data, len);
|
||||
return esp_modem::command_result::TIMEOUT;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ public:
|
||||
sock(s), data_ready_fd(ready_fd), dte(dte_arg) {}
|
||||
ret process_data(status state, uint8_t *data, size_t len);
|
||||
ret check_async_replies(status state, std::string_view &response);
|
||||
ret check_urc(status state, std::string_view &response);
|
||||
|
||||
void start_sending(size_t len);
|
||||
void start_receiving(size_t len);
|
||||
@@ -63,13 +64,19 @@ public:
|
||||
return total_len;
|
||||
}
|
||||
|
||||
// Unique link identifier used to target multi-connection AT commands
|
||||
int link_id{s_link_id++};
|
||||
// Shared mutex guarding DTE access across concurrent DCE instances
|
||||
static SemaphoreHandle_t s_dte_mutex;
|
||||
private:
|
||||
static int s_link_id;
|
||||
static constexpr size_t buffer_size = 512;
|
||||
|
||||
bool on_read(char *data, size_t len)
|
||||
{
|
||||
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
|
||||
::send(sock, data, len, 0);
|
||||
printf("sending %d\n", len);
|
||||
#else
|
||||
::memcpy(&buffer[actual_read], data, len);
|
||||
actual_read += len;
|
||||
@@ -101,6 +108,8 @@ private:
|
||||
class DCE : public Module {
|
||||
using Module::Module;
|
||||
public:
|
||||
DCE(std::shared_ptr<esp_modem::DTE> dte_arg, const esp_modem_dce_config *config);
|
||||
~DCE();
|
||||
|
||||
/**
|
||||
* @brief Opens network in AT command mode
|
||||
@@ -163,6 +172,10 @@ public:
|
||||
return 0;
|
||||
}
|
||||
at.clear_offsets();
|
||||
// Take DTE mutex before issuing receive on this link
|
||||
ESP_LOGD("TAG", "TAKE RECV %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD("TAG", "TAKEN RECV %d", at.link_id);
|
||||
state = status::RECEIVING;
|
||||
uint64_t data;
|
||||
read(data_ready_fd, &data, sizeof(data));
|
||||
@@ -184,6 +197,10 @@ public:
|
||||
if (!wait_to_idle(timeout_ms)) {
|
||||
return -1;
|
||||
}
|
||||
// Take DTE mutex before issuing send on this link
|
||||
ESP_LOGD("TAG", "TAKE SEND %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD("TAG", "TAKEN SEND %d", at.link_id);
|
||||
state = status::SENDING;
|
||||
memcpy(at.get_buf(), buffer, len_to_send);
|
||||
ESP_LOG_BUFFER_HEXDUMP("dce", at.get_buf(), len, ESP_LOG_VERBOSE);
|
||||
@@ -224,6 +241,14 @@ public:
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
static std::vector<DCE *> dce_list;
|
||||
static bool network_init;
|
||||
static void read_callback(uint8_t *data, size_t len)
|
||||
{
|
||||
for (auto dce : dce_list) {
|
||||
dce->perform_at(data, len);
|
||||
}
|
||||
}
|
||||
private:
|
||||
esp_modem::SignalGroup signal;
|
||||
void close_sock();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <sys/socket.h>
|
||||
#include "esp_vfs.h"
|
||||
@@ -14,6 +15,29 @@
|
||||
namespace sock_dce {
|
||||
|
||||
constexpr auto const *TAG = "sock_dce";
|
||||
constexpr auto WAIT_TO_IDLE_TIMEOUT = 5000;
|
||||
|
||||
// Definition of the static member variables
|
||||
std::vector<DCE*> DCE::dce_list{};
|
||||
bool DCE::network_init = false;
|
||||
int Responder::s_link_id = 0;
|
||||
SemaphoreHandle_t Responder::s_dte_mutex{};
|
||||
|
||||
// Constructor - add this DCE instance to the static list
|
||||
DCE::DCE(std::shared_ptr<esp_modem::DTE> dte_arg, const esp_modem_dce_config *config)
|
||||
: Module(std::move(dte_arg), config)
|
||||
{
|
||||
dce_list.push_back(this);
|
||||
}
|
||||
|
||||
// Destructor - remove this DCE instance from the static list
|
||||
DCE::~DCE()
|
||||
{
|
||||
auto it = std::find(dce_list.begin(), dce_list.end(), this);
|
||||
if (it != dce_list.end()) {
|
||||
dce_list.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool DCE::perform_sock()
|
||||
@@ -61,13 +85,26 @@ bool DCE::perform_sock()
|
||||
|
||||
void DCE::perform_at(uint8_t *data, size_t len)
|
||||
{
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE);
|
||||
if (state != status::RECEIVING) {
|
||||
std::string_view resp_sv((char *)data, len);
|
||||
at.check_urc(state, resp_sv);
|
||||
if (state == status::IDLE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Trace incoming AT bytes when handling a response; use DEBUG level
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_DEBUG);
|
||||
switch (at.process_data(state, data, len)) {
|
||||
case Responder::ret::OK:
|
||||
// Release DTE access for this link after processing data
|
||||
ESP_LOGD(TAG, "GIVE data %d", at.link_id);
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
state = status::IDLE;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Responder::ret::FAIL:
|
||||
ESP_LOGD(TAG, "GIVE data %d", at.link_id);
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
state = status::FAILED;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
@@ -82,10 +119,14 @@ void DCE::perform_at(uint8_t *data, size_t len)
|
||||
std::string_view response((char *)data, len);
|
||||
switch (at.check_async_replies(state, response)) {
|
||||
case Responder::ret::OK:
|
||||
ESP_LOGD(TAG, "GIVE command %d", at.link_id);
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
state = status::IDLE;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
case Responder::ret::FAIL:
|
||||
ESP_LOGD(TAG, "GIVE command %d", at.link_id);
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
state = status::FAILED;
|
||||
signal.set(IDLE);
|
||||
return;
|
||||
@@ -121,7 +162,7 @@ bool DCE::at_to_sock()
|
||||
uint64_t data;
|
||||
read(data_ready_fd, &data, sizeof(data));
|
||||
ESP_LOGD(TAG, "select read: modem data available %" PRIu64, data);
|
||||
if (!signal.wait(IDLE, 1000)) {
|
||||
if (!signal.wait(IDLE, WAIT_TO_IDLE_TIMEOUT)) {
|
||||
ESP_LOGE(TAG, "Failed to get idle");
|
||||
close_sock();
|
||||
return false;
|
||||
@@ -131,6 +172,10 @@ bool DCE::at_to_sock()
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
// Take DTE mutex before issuing receive on this link
|
||||
ESP_LOGD(TAG, "TAKE RECV %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD(TAG, "TAKEN RECV %d", at.link_id);
|
||||
state = status::RECEIVING;
|
||||
at.start_receiving(at.get_buf_len());
|
||||
return true;
|
||||
@@ -139,7 +184,7 @@ bool DCE::at_to_sock()
|
||||
bool DCE::sock_to_at()
|
||||
{
|
||||
ESP_LOGD(TAG, "socket read: data available");
|
||||
if (!signal.wait(IDLE, 1000)) {
|
||||
if (!signal.wait(IDLE, WAIT_TO_IDLE_TIMEOUT)) {
|
||||
ESP_LOGE(TAG, "Failed to get idle");
|
||||
close_sock();
|
||||
return false;
|
||||
@@ -149,6 +194,10 @@ bool DCE::sock_to_at()
|
||||
close_sock();
|
||||
return false;
|
||||
}
|
||||
// Take DTE mutex before issuing send on this link
|
||||
ESP_LOGD(TAG, "TAKE SEND %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD(TAG, "TAKEN SEND %d", at.link_id);
|
||||
state = status::SENDING;
|
||||
int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0);
|
||||
if (len < 0) {
|
||||
@@ -201,7 +250,7 @@ void DCE::start_listening(int port)
|
||||
}
|
||||
int opt = 1;
|
||||
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
ESP_LOGI(TAG, "Socket created");
|
||||
ESP_LOGD(TAG, "Socket created");
|
||||
struct sockaddr_in addr = { };
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
@@ -213,7 +262,7 @@ void DCE::start_listening(int port)
|
||||
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Socket bound, port %d", 1883);
|
||||
ESP_LOGD(TAG, "Socket bound, port %d", 1883);
|
||||
err = listen(listen_sock, 1);
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
|
||||
@@ -224,12 +273,12 @@ void DCE::start_listening(int port)
|
||||
|
||||
bool DCE::connect(std::string host, int port)
|
||||
{
|
||||
dte->on_read(nullptr);
|
||||
tcp_close();
|
||||
dte->on_read([this](uint8_t *data, size_t len) {
|
||||
this->perform_at(data, len);
|
||||
return esp_modem::command_result::TIMEOUT;
|
||||
});
|
||||
data_ready_fd = eventfd(0, EFD_SUPPORT_ISR);
|
||||
assert(data_ready_fd > 0);
|
||||
// Take DTE mutex before starting connect for this link
|
||||
ESP_LOGD(TAG, "TAKE CONNECT %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD(TAG, "TAKEN CONNECT %d", at.link_id);
|
||||
if (!at.start_connecting(host, port)) {
|
||||
ESP_LOGE(TAG, "Unable to start connecting");
|
||||
dte->on_read(nullptr);
|
||||
@@ -241,12 +290,15 @@ bool DCE::connect(std::string host, int port)
|
||||
|
||||
bool DCE::init()
|
||||
{
|
||||
if (network_init) {
|
||||
return true;
|
||||
}
|
||||
network_init = true;
|
||||
Responder::s_dte_mutex = xSemaphoreCreateBinary();
|
||||
xSemaphoreGive(at.s_dte_mutex);
|
||||
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
|
||||
esp_vfs_eventfd_register(&config);
|
||||
|
||||
data_ready_fd = eventfd(0, EFD_SUPPORT_ISR);
|
||||
assert(data_ready_fd > 0);
|
||||
|
||||
dte->on_read(nullptr);
|
||||
const int retries = 5;
|
||||
int i = 0;
|
||||
@@ -287,6 +339,10 @@ bool DCE::init()
|
||||
esp_modem::Task::Delay(5000);
|
||||
}
|
||||
ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str());
|
||||
dte->on_read([](uint8_t *data, size_t len) {
|
||||
read_callback(data, len);
|
||||
return esp_modem::command_result::TIMEOUT;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ public:
|
||||
sock(s), data_ready_fd(ready_fd), dte(dte_arg) {}
|
||||
ret process_data(status state, uint8_t *data, size_t len);
|
||||
ret check_async_replies(status state, std::string_view &response);
|
||||
ret check_urc(status state, std::string_view &response);
|
||||
|
||||
void start_sending(size_t len);
|
||||
void start_receiving(size_t len);
|
||||
@@ -63,13 +64,19 @@ public:
|
||||
return total_len;
|
||||
}
|
||||
|
||||
// Unique link identifier used to target multi-connection AT commands
|
||||
int link_id{s_link_id++};
|
||||
// Shared mutex guarding DTE access across concurrent DCE instances
|
||||
static SemaphoreHandle_t s_dte_mutex;
|
||||
private:
|
||||
static int s_link_id;
|
||||
static constexpr size_t buffer_size = 512;
|
||||
|
||||
bool on_read(char *data, size_t len)
|
||||
{
|
||||
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
|
||||
::send(sock, data, len, 0);
|
||||
printf("sending %d\n", len);
|
||||
#else
|
||||
::memcpy(&buffer[actual_read], data, len);
|
||||
actual_read += len;
|
||||
@@ -101,6 +108,8 @@ private:
|
||||
class DCE : public Module {
|
||||
using Module::Module;
|
||||
public:
|
||||
DCE(std::shared_ptr<esp_modem::DTE> dte_arg, const esp_modem_dce_config *config);
|
||||
~DCE();
|
||||
|
||||
// --- ESP-MODEM command module starts here ---
|
||||
#include "esp_modem_command_declare_helper.inc"
|
||||
@@ -141,6 +150,10 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__));
|
||||
return 0;
|
||||
}
|
||||
at.clear_offsets();
|
||||
// Take DTE mutex before issuing receive on this link
|
||||
ESP_LOGD("TAG", "TAKE RECV %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD("TAG", "TAKEN RECV %d", at.link_id);
|
||||
state = status::RECEIVING;
|
||||
uint64_t data;
|
||||
read(data_ready_fd, &data, sizeof(data));
|
||||
@@ -163,6 +176,10 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__));
|
||||
if (!wait_to_idle(timeout_ms)) {
|
||||
return -1;
|
||||
}
|
||||
// Take DTE mutex before issuing send on this link
|
||||
ESP_LOGD("TAG", "TAKE SEND %d", at.link_id);
|
||||
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
|
||||
ESP_LOGD("TAG", "TAKEN SEND %d", at.link_id);
|
||||
state = status::SENDING;
|
||||
memcpy(at.get_buf(), buffer, len_to_send);
|
||||
ESP_LOG_BUFFER_HEXDUMP("dce", at.get_buf(), len, ESP_LOG_VERBOSE);
|
||||
@@ -204,6 +221,14 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
static std::vector<DCE*> dce_list;
|
||||
static bool network_init;
|
||||
static void read_callback(uint8_t *data, size_t len)
|
||||
{
|
||||
for (auto dce : dce_list) {
|
||||
dce->perform_at(data, len);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
esp_modem::SignalGroup signal;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
dependencies:
|
||||
espressif/esp_modem:
|
||||
version: "^1.0.1"
|
||||
override_path: "../../../"
|
||||
version: ^2
|
||||
override_path: ../../../
|
||||
espressif/mbedtls_cxx:
|
||||
version: "*"
|
||||
override_path: "../../../../mbedtls_cxx"
|
||||
version: '*'
|
||||
override_path: ../../../../mbedtls_cxx
|
||||
espressif/mqtt:
|
||||
rules:
|
||||
- if: idf_version >=6.0
|
||||
version: ^1.0.0
|
||||
|
||||
@@ -22,15 +22,26 @@
|
||||
#include "esp_log.h"
|
||||
#include "tcp_transport_mbedtls.h"
|
||||
#include "tcp_transport_at.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define BROKER_URL "test.mosquitto.org"
|
||||
#define USE_TLS CONFIG_EXAMPLE_USE_TLS
|
||||
#define BROKER_HOST CONFIG_EXAMPLE_BROKER_HOST
|
||||
#if USE_TLS
|
||||
#define BROKER_SCHEME "mqtts"
|
||||
#define BROKER_PORT 8883
|
||||
#else
|
||||
#define BROKER_SCHEME "mqtt"
|
||||
#define BROKER_PORT 1883
|
||||
#endif
|
||||
#define BROKER_URL BROKER_SCHEME "://" BROKER_HOST
|
||||
|
||||
|
||||
static const char *TAG = "modem_client";
|
||||
static EventGroupHandle_t event_group = NULL;
|
||||
static const int CONNECT_BIT = BIT0;
|
||||
static const int GOT_DATA_BIT = BIT2;
|
||||
static const int DCE0_DONE = BIT3;
|
||||
static const int DCE1_DONE = BIT4;
|
||||
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
@@ -73,13 +84,15 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
|
||||
}
|
||||
}
|
||||
|
||||
static void perform(void* ctx);
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
|
||||
/* Init and register system/core components */
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
// Default to INFO; individual modules use DEBUG for verbose tracing
|
||||
esp_log_level_set("*", ESP_LOG_INFO);
|
||||
|
||||
event_group = xEventGroupCreate();
|
||||
|
||||
@@ -104,30 +117,60 @@ extern "C" void app_main(void)
|
||||
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_APN);
|
||||
|
||||
/* create the DCE and initialize network manually (using AT commands) */
|
||||
auto dce = sock_dce::create(&dce_config, std::move(dte));
|
||||
auto dce = sock_dce::create(&dce_config, dte);
|
||||
if (!dce->init()) {
|
||||
ESP_LOGE(TAG, "Failed to setup network");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_mqtt_client_config_t mqtt_config = {};
|
||||
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 = "mqtt://127.0.0.1";
|
||||
dce->start_listening(BROKER_PORT);
|
||||
#else
|
||||
mqtt_config.broker.address.uri = "mqtt://" BROKER_URL;
|
||||
esp_transport_handle_t at = esp_transport_at_init(dce.get());
|
||||
esp_transport_handle_t ssl = esp_transport_tls_init(at);
|
||||
xTaskCreate(perform, "perform", 4096, dce.get(), 4, nullptr);
|
||||
|
||||
mqtt_config.network.transport = ssl;
|
||||
/* create another DCE to serve a new connection */
|
||||
auto dce1 = sock_dce::create(&dce_config, dte);
|
||||
if (!dce1->init()) {
|
||||
ESP_LOGE(TAG, "Failed to setup network");
|
||||
return;
|
||||
}
|
||||
xTaskCreate(perform, "perform", 4096, dce1.get(), 4, nullptr);
|
||||
|
||||
xEventGroupWaitBits(event_group, DCE0_DONE | DCE1_DONE, pdFALSE, pdTRUE, portMAX_DELAY);
|
||||
#ifdef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
|
||||
// this example does never keeps both DCEs running and in tcp-transport option
|
||||
// we don't need a task to run so we exit main and keep DCE's "running"
|
||||
vTaskDelay(portMAX_DELAY);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void perform(void* ctx)
|
||||
{
|
||||
auto dce = static_cast<sock_dce::DCE*>(ctx);
|
||||
char mqtt_client_id[] = "MQTT_CLIENT_0";
|
||||
static int counter = 0;
|
||||
const int id = counter++;
|
||||
mqtt_client_id[sizeof(mqtt_client_id) - 2] += id; // assumes a different client id per each thread
|
||||
esp_mqtt_client_config_t mqtt_config = {};
|
||||
mqtt_config.session.message_retransmit_timeout = 10000;
|
||||
mqtt_config.credentials.client_id = mqtt_client_id;
|
||||
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
|
||||
mqtt_config.broker.address.port = BROKER_PORT + id;
|
||||
mqtt_config.broker.address.uri = BROKER_SCHEME "://127.0.0.1";
|
||||
dce->start_listening(BROKER_PORT + id);
|
||||
#else
|
||||
mqtt_config.broker.address.port = BROKER_PORT;
|
||||
mqtt_config.broker.address.uri = BROKER_URL;
|
||||
esp_transport_handle_t at = esp_transport_at_init(dce);
|
||||
#if USE_TLS
|
||||
esp_transport_handle_t ssl = esp_transport_tls_init(at);
|
||||
mqtt_config.network.transport = ssl;
|
||||
#else
|
||||
mqtt_config.network.transport = at;
|
||||
#endif // USE_TLS
|
||||
#endif // CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
|
||||
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, nullptr);
|
||||
esp_mqtt_client_start(mqtt_client);
|
||||
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
|
||||
if (!dce->connect(BROKER_URL, BROKER_PORT)) {
|
||||
if (!dce->connect(BROKER_HOST, BROKER_PORT)) {
|
||||
ESP_LOGE(TAG, "Failed to start DCE");
|
||||
return;
|
||||
}
|
||||
@@ -135,18 +178,17 @@ extern "C" void app_main(void)
|
||||
while (dce->perform_sock()) {
|
||||
ESP_LOGV(TAG, "...performing");
|
||||
}
|
||||
ESP_LOGE(TAG, "Loop exit.. retrying");
|
||||
ESP_LOGD(TAG, "Loop exit.. retrying");
|
||||
// handle disconnections errors
|
||||
if (!dce->init()) {
|
||||
ESP_LOGE(TAG, "Failed to reinit network");
|
||||
return;
|
||||
}
|
||||
if (!dce->connect(BROKER_URL, BROKER_PORT)) {
|
||||
if (!dce->connect(BROKER_HOST, BROKER_PORT)) {
|
||||
ESP_LOGI(TAG, "Network reinitialized, retrying");
|
||||
}
|
||||
}
|
||||
#else
|
||||
vTaskDelay(portMAX_DELAY);
|
||||
#endif
|
||||
|
||||
xEventGroupSetBits(event_group, id ? DCE0_DONE : DCE1_DONE);
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
@@ -109,17 +109,17 @@ void Responder::start_sending(size_t len)
|
||||
{
|
||||
data_to_send = len;
|
||||
send_stat = 0;
|
||||
send_cmd("AT+QISEND=0," + std::to_string(len) + "\r");
|
||||
send_cmd("AT+QISEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r");
|
||||
}
|
||||
|
||||
void Responder::start_receiving(size_t len)
|
||||
{
|
||||
send_cmd("AT+QIRD=0," + std::to_string(len) + "\r");
|
||||
send_cmd("AT+QIRD=" + std::to_string(link_id) + "," + std::to_string(len) + "\r");
|
||||
}
|
||||
|
||||
bool Responder::start_connecting(std::string host, int port)
|
||||
{
|
||||
send_cmd(R"(AT+QIOPEN=1,0,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
|
||||
send_cmd(std::string("AT+QIOPEN=1,") + std::to_string(link_id) + R"(,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -130,16 +130,24 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
|
||||
auto *recv_data = (char *)data;
|
||||
if (data_to_recv == 0) {
|
||||
const std::string_view head = "+QIRD: ";
|
||||
again:
|
||||
const std::string_view recv_data_view = std::string_view(recv_data, len);
|
||||
auto head_pos_found = recv_data_view.find(head);
|
||||
if (head_pos_found == std::string_view::npos) {
|
||||
return ret::FAIL;
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
auto *head_pos = recv_data + head_pos_found;
|
||||
auto next_nl = (char *)memchr(head_pos + head.size(), '\n', MIN_MESSAGE);
|
||||
|
||||
if (next_nl == nullptr) {
|
||||
if (head_pos + head.size() + 1 < recv_data + len) {
|
||||
// might be that we misinterpreted the URC +QIRD: <>,<>,<> (notification) with the +QIRD: <> (read data)
|
||||
// so we try to find the next +QIRD:
|
||||
recv_data = head_pos + head.size() + 1;
|
||||
goto again;
|
||||
}
|
||||
ESP_LOGD(TAG, "no new line found");
|
||||
return ret::FAIL;
|
||||
}
|
||||
|
||||
@@ -151,7 +159,9 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
|
||||
ESP_LOGD(TAG, "Received: actual len=%d", actual_len);
|
||||
if (actual_len == 0) {
|
||||
ESP_LOGD(TAG, "no data received");
|
||||
return ret::FAIL;
|
||||
data_to_recv = 0;
|
||||
// return OK here, as BG96 would keep unacked data and notifies us with +QIRD: 0
|
||||
return ret::OK;
|
||||
}
|
||||
|
||||
if (actual_len > buffer_size) {
|
||||
@@ -182,6 +192,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
|
||||
last_pos = (char *)memchr(recv_data + 1 + actual_len, 'O', MIN_MESSAGE);
|
||||
if (last_pos == nullptr || last_pos[1] != 'K') {
|
||||
data_to_recv = 0;
|
||||
ESP_LOGD(TAG, "no OK after data");
|
||||
return ret::FAIL;
|
||||
}
|
||||
}
|
||||
@@ -222,7 +233,7 @@ Responder::ret Responder::send(std::string_view response)
|
||||
{
|
||||
if (send_stat == 3) {
|
||||
if (response.find("SEND OK") != std::string::npos) {
|
||||
send_cmd("AT+QISEND=0,0\r");
|
||||
send_cmd(std::string("AT+QISEND=") + std::to_string(link_id) + ",0\r");
|
||||
send_stat++;
|
||||
return ret::IN_PROGRESS;
|
||||
} else if (response.find("SEND FAIL") != std::string::npos) {
|
||||
@@ -267,7 +278,7 @@ Responder::ret Responder::send(std::string_view response)
|
||||
if (ack < total) {
|
||||
ESP_LOGD(TAG, "all sending data are not ack (missing %d bytes acked)", (total - ack));
|
||||
if (total - ack > 64) {
|
||||
ESP_LOGW(TAG, "Need a pause: missing %d bytes acked", (total - ack));
|
||||
ESP_LOGD(TAG, "Need a pause: missing %d bytes acked", (total - ack));
|
||||
return ret::NEED_MORE_TIME;
|
||||
}
|
||||
}
|
||||
@@ -284,7 +295,8 @@ Responder::ret Responder::send(std::string_view response)
|
||||
|
||||
Responder::ret Responder::connect(std::string_view response)
|
||||
{
|
||||
if (response.find("+QIOPEN: 0,0") != std::string::npos) {
|
||||
std::string open_response = "+QIOPEN: " + std::to_string(link_id) + ",0";
|
||||
if (response.find(open_response) != std::string::npos) {
|
||||
ESP_LOGI(TAG, "Connected!");
|
||||
return ret::OK;
|
||||
}
|
||||
@@ -295,10 +307,9 @@ Responder::ret Responder::connect(std::string_view response)
|
||||
return Responder::ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
Responder::ret Responder::check_async_replies(status state, std::string_view &response)
|
||||
Responder::ret Responder::check_urc(status state, std::string_view &response)
|
||||
{
|
||||
ESP_LOGD(TAG, "response %.*s", static_cast<int>(response.size()), response.data());
|
||||
if (response.find("+QIURC: \"recv\",0") != std::string::npos) {
|
||||
if (response.find(std::string("+QIURC: \"recv\",") + std::to_string(link_id)) != std::string::npos) {
|
||||
uint64_t data_ready = 1;
|
||||
write(data_ready_fd, &data_ready, sizeof(data_ready));
|
||||
ESP_LOGD(TAG, "Got data on modem!");
|
||||
@@ -309,6 +320,9 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re
|
||||
response = response.substr(head_pos + head.size());
|
||||
int next_cr = response.find('\r');
|
||||
if (next_cr != std::string::npos) {
|
||||
if (next_cr < 2) {
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
response = response.substr(next_cr - 2, next_cr);
|
||||
if (response.find(",0") != std::string::npos) {
|
||||
ESP_LOGV(TAG, "Receiving done");
|
||||
@@ -318,12 +332,21 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re
|
||||
ESP_LOGD(TAG, "Got data on modem!");
|
||||
}
|
||||
}
|
||||
} else if (response.find("+QIURC: \"closed\",0") != std::string::npos) {
|
||||
} else if (response.find(std::string("+QIURC: \"closed\",") + std::to_string(link_id)) != std::string::npos) {
|
||||
ESP_LOGE(TAG, "Connection closed");
|
||||
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());
|
||||
if (state == status::SENDING) {
|
||||
return send(response);
|
||||
} else if (state == status::CONNECTING) {
|
||||
}
|
||||
if (state == status::CONNECTING) {
|
||||
return connect(response);
|
||||
}
|
||||
return ret::IN_PROGRESS;
|
||||
@@ -342,7 +365,7 @@ Responder::ret Responder::process_data(status state, uint8_t *data, size_t len)
|
||||
|
||||
status Responder::pending()
|
||||
{
|
||||
send_cmd("AT+QISEND=0,0\r");
|
||||
send_cmd(std::string("AT+QISEND=") + std::to_string(link_id) + ",0\r");
|
||||
return status::SENDING;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,12 +58,19 @@ command_result net_open(CommandableIf *t)
|
||||
}
|
||||
ESP_LOGI(TAG, "WiFi connected successfully");
|
||||
|
||||
// Set passive receive mode (1) for better control
|
||||
ret = set_rx_mode(t, 1);
|
||||
// Enable multiple connections mode
|
||||
ret = dce_commands::generic_command(t, "AT+CIPMUX=1\r\n", "OK", "ERROR", 1000);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGE(TAG, "Failed to set preferred Rx mode");
|
||||
ESP_LOGE(TAG, "Failed to enable multiple connections mode");
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGD(TAG, "Multiple connections mode enabled");
|
||||
|
||||
// Set passive receive mode (1) for better control
|
||||
for (int i = 0; i < 2; i++) {
|
||||
std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(i) + ",1\r\n";
|
||||
dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000);
|
||||
}
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
@@ -78,49 +85,20 @@ command_result net_close(CommandableIf *t)
|
||||
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)
|
||||
{
|
||||
return command_result::OK;
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
return dce_commands::generic_command(t, "AT+CIPCLOSE\r\n", "CLOSED", "ERROR", 5000);
|
||||
// Use link ID 0 for closing connection
|
||||
const int link_id = 0;
|
||||
std::string close_cmd = "AT+CIPCLOSE=" + std::to_string(link_id) + "\r\n";
|
||||
|
||||
// In multiple connections mode, response format is: <link ID>,CLOSED
|
||||
std::string expected_response = std::to_string(link_id) + ",CLOSED";
|
||||
|
||||
return dce_commands::generic_command(t, close_cmd, expected_response, "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)
|
||||
{
|
||||
@@ -150,9 +128,11 @@ command_result get_ip(CommandableIf *t, std::string &ip)
|
||||
|
||||
command_result set_rx_mode(CommandableIf *t, int mode)
|
||||
{
|
||||
ESP_LOGE(TAG, "%s", __func__);
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
// For multiple connections mode, set receive mode for link ID 0
|
||||
const int link_id = 0;
|
||||
// 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";
|
||||
std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(link_id) + "," + std::to_string(mode) + "\r\n";
|
||||
return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000);
|
||||
}
|
||||
|
||||
@@ -164,17 +144,20 @@ void Responder::start_sending(size_t len)
|
||||
{
|
||||
data_to_send = len;
|
||||
send_stat = 0;
|
||||
send_cmd("AT+CIPSEND=" + std::to_string(len) + "\r\n");
|
||||
// For multiple connections mode, include link ID
|
||||
send_cmd("AT+CIPSEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r\n");
|
||||
}
|
||||
|
||||
void Responder::start_receiving(size_t len)
|
||||
{
|
||||
send_cmd("AT+CIPRECVDATA=" + std::to_string(len) + "\r\n");
|
||||
// For multiple connections mode, include link ID
|
||||
send_cmd("AT+CIPRECVDATA=" + std::to_string(link_id) + "," + 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";
|
||||
// For multiple connections mode, include link ID
|
||||
std::string cmd = "AT+CIPSTART=" + std::to_string(link_id) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n";
|
||||
send_cmd(cmd);
|
||||
return true;
|
||||
}
|
||||
@@ -187,16 +170,12 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
|
||||
|
||||
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;
|
||||
const std::string_view recv_data_view(recv_data, len);
|
||||
const auto head_pos_found = recv_data_view.find(head);
|
||||
if (head_pos_found == std::string_view::npos) {
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
const auto *head_pos = recv_data + head_pos_found;
|
||||
// Find the end of the length field
|
||||
auto next_comma = (char *)memchr(head_pos + head.size(), ',', MIN_MESSAGE);
|
||||
if (next_comma == nullptr) {
|
||||
@@ -245,12 +224,25 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
|
||||
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') {
|
||||
if (ok_pos == nullptr) { // || ok_pos[1] != 'K') {
|
||||
data_to_recv = 0;
|
||||
ESP_LOGE(TAG, "Missed 'OK' marker");
|
||||
return ret::OK;
|
||||
return ret::FAIL;
|
||||
}
|
||||
if (ok_pos + 1 < recv_data + len && ok_pos[1] != 'K') {
|
||||
// we ignore the condition when receiving 'O' as the last character in the last batch,
|
||||
// don't wait for the 'K' in the next run, assume the data are valid and let higher layers deal with it.
|
||||
data_to_recv = 0;
|
||||
ESP_LOGE(TAG, "Missed 'OK' marker2");
|
||||
return ret::FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok_pos != nullptr && (char *)data + len - ok_pos - 2 > MIN_MESSAGE) {
|
||||
// check for async replies after the Recv header
|
||||
std::string_view response((char *)ok_pos + 2 /* OK */, (char *)data + len - ok_pos);
|
||||
check_urc(status::RECEIVING, response);
|
||||
}
|
||||
// Reset and prepare for next receive
|
||||
data_to_recv = 0;
|
||||
return ret::OK;
|
||||
@@ -299,7 +291,8 @@ Responder::ret Responder::send(std::string_view response)
|
||||
|
||||
Responder::ret Responder::connect(std::string_view response)
|
||||
{
|
||||
if (response.find("CONNECT") != std::string::npos) {
|
||||
// In multiple connections mode, response format is: <link ID>,CONNECT
|
||||
if (response.find(",CONNECT") != std::string::npos || response.find("CONNECT") != std::string::npos) {
|
||||
ESP_LOGI(TAG, "TCP connected!");
|
||||
return ret::OK;
|
||||
}
|
||||
@@ -309,6 +302,17 @@ Responder::ret Responder::connect(std::string_view response)
|
||||
}
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
Responder::ret Responder::check_urc(status state, std::string_view &response)
|
||||
{
|
||||
// Handle data notifications - in multiple connections mode, format is +IPD,<link ID>,<len>
|
||||
std::string expected_urc = "+IPD," + std::to_string(link_id);
|
||||
if (response.find(expected_urc) != std::string::npos) {
|
||||
uint64_t data_ready = 1;
|
||||
write(data_ready_fd, &data_ready, sizeof(data_ready));
|
||||
ESP_LOGD(TAG, "Data available notification");
|
||||
}
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
Responder::ret Responder::check_async_replies(status state, std::string_view &response)
|
||||
{
|
||||
@@ -318,24 +322,17 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re
|
||||
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");
|
||||
ESP_LOGD(TAG, "WiFi disconnected");
|
||||
}
|
||||
|
||||
// Handle TCP status messages
|
||||
// Handle TCP status messages (multiple connections format: <link ID>,CONNECT or <link ID>,CLOSED)
|
||||
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");
|
||||
ESP_LOGD(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) {
|
||||
|
||||
@@ -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: Apache-2.0
|
||||
*/
|
||||
@@ -18,13 +18,13 @@ using namespace esp_modem;
|
||||
|
||||
command_result net_open(CommandableIf *term)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
std::string response;
|
||||
auto ret = dce_commands::generic_get_string(term, "AT+NETOPEN?\r", response, 1000);
|
||||
if (ret != command_result::OK) {
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGV(TAG, "%s", response.data() );
|
||||
ESP_LOGV(TAG, "%s", response.data());
|
||||
if (response.find("+NETOPEN: 1") != std::string::npos) {
|
||||
ESP_LOGD(TAG, "Already there");
|
||||
ret = command_result::OK;
|
||||
@@ -42,23 +42,23 @@ command_result net_open(CommandableIf *term)
|
||||
|
||||
command_result net_close(CommandableIf *term)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
return dce_commands::generic_command(term, "AT+NETCLOSE\r", "+NETCLOSE:", "ERROR", 30000);
|
||||
}
|
||||
|
||||
command_result tcp_open(CommandableIf *term, const std::string &host, int port, int timeout)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
auto ret = dce_commands::generic_command(term, "AT+CIPRXGET=1\r", "OK", "ERROR", 50000);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGE(TAG, "Setting Rx mode failed!");
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
std::string ip_open = R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r";
|
||||
ret = dce_commands::generic_command(term, ip_open, "+CIPOPEN: 0,0", "ERROR", timeout);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGE(TAG, "%s Failed", __func__ );
|
||||
ESP_LOGE(TAG, "%s Failed", __func__);
|
||||
return ret;
|
||||
}
|
||||
return command_result::OK;
|
||||
@@ -66,13 +66,13 @@ command_result tcp_open(CommandableIf *term, const std::string &host, int port,
|
||||
|
||||
command_result tcp_close(CommandableIf *term)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
return dce_commands::generic_command(term, "AT+CIPCLOSE=0\r", "+CIPCLOSE:", "ERROR", 10000);
|
||||
}
|
||||
|
||||
command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
std::string send = "AT+CIPSEND=0," + std::to_string(len) + "\r";
|
||||
auto ret = term->command(send, [&](uint8_t *data, size_t len) {
|
||||
std::string_view response((char *)data, len);
|
||||
@@ -86,10 +86,10 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
|
||||
return ret;
|
||||
}
|
||||
ret = command_result::TIMEOUT;
|
||||
ESP_LOGW(TAG, "Before setting...");
|
||||
ESP_LOGD(TAG, "Before setting...");
|
||||
term->on_read([&ret](uint8_t *cmd_data, size_t cmd_len) {
|
||||
std::string_view response((char *)cmd_data, cmd_len);
|
||||
ESP_LOGW(TAG, "CIPSEND response %.*s", static_cast<int>(response.size()), response.data());
|
||||
ESP_LOGD(TAG, "CIPSEND response %.*s", static_cast<int>(response.size()), response.data());
|
||||
|
||||
if (response.find("+CIPSEND:") != std::string::npos) {
|
||||
ret = command_result::OK;
|
||||
@@ -98,7 +98,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
ESP_LOGW(TAG, "Before writing...");
|
||||
ESP_LOGD(TAG, "Before writing...");
|
||||
auto written = term->write(data, len);
|
||||
if (written != len) {
|
||||
ESP_LOGE(TAG, "written %d (%d)...", written, len);
|
||||
@@ -107,7 +107,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
|
||||
uint8_t ctrl_z = '\x1A';
|
||||
term->write(&ctrl_z, 1);
|
||||
int count = 0;
|
||||
while (ret == command_result::TIMEOUT && count++ < 1000 ) {
|
||||
while (ret == command_result::TIMEOUT && count++ < 1000) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
term->on_read(nullptr);
|
||||
@@ -116,7 +116,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
|
||||
|
||||
command_result tcp_recv(CommandableIf *term, uint8_t *data, size_t len, size_t &out_len)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
std::string out;
|
||||
auto ret = dce_commands::generic_get_string(term, "AT+CIPRXGET=4,0\r", out);
|
||||
if (ret != command_result::OK) {
|
||||
@@ -195,17 +195,17 @@ void Responder::start_sending(size_t len)
|
||||
{
|
||||
data_to_send = len;
|
||||
send_stat = 0;
|
||||
send_cmd("AT+CIPSEND=0," + std::to_string(len) + "\r");
|
||||
send_cmd("AT+CIPSEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r");
|
||||
}
|
||||
|
||||
void Responder::start_receiving(size_t len)
|
||||
{
|
||||
send_cmd("AT+CIPRXGET=2,0," + std::to_string(len) + "\r");
|
||||
send_cmd("AT+CIPRXGET=2," + std::to_string(link_id) + "," + std::to_string(len) + "\r");
|
||||
}
|
||||
|
||||
bool Responder::start_connecting(std::string host, int port)
|
||||
{
|
||||
send_cmd(R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
|
||||
send_cmd(std::string("AT+CIPOPEN=") + std::to_string(link_id) + R"(,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
|
||||
size_t actual_len = 0;
|
||||
auto *recv_data = (char *)data;
|
||||
if (data_to_recv == 0) {
|
||||
static constexpr std::string_view head = "+CIPRXGET: 2,0,";
|
||||
const std::string head = std::string("+CIPRXGET: 2,") + std::to_string(link_id) + ",";
|
||||
auto head_pos = std::search(recv_data, recv_data + len, head.begin(), head.end());
|
||||
if (head_pos == recv_data + len) {
|
||||
return ret::FAIL;
|
||||
@@ -329,7 +329,8 @@ Responder::ret Responder::send(std::string_view response)
|
||||
|
||||
Responder::ret Responder::connect(std::string_view response)
|
||||
{
|
||||
if (response.find("+CIPOPEN: 0,0") != std::string::npos) {
|
||||
std::string open_response = "+CIPOPEN: " + std::to_string(link_id) + ",0";
|
||||
if (response.find(open_response) != std::string::npos) {
|
||||
ESP_LOGI(TAG, "Connected!");
|
||||
return ret::OK;
|
||||
}
|
||||
@@ -340,14 +341,22 @@ Responder::ret Responder::connect(std::string_view response)
|
||||
return Responder::ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
Responder::ret Responder::check_async_replies(status state, std::string_view &response)
|
||||
Responder::ret Responder::check_urc(status state, std::string_view &response)
|
||||
{
|
||||
ESP_LOGD(TAG, "response %.*s", static_cast<int>(response.size()), response.data());
|
||||
if (response.find("+CIPRXGET: 1") != std::string::npos) {
|
||||
// 1. When <mode> is set to 1 and the 2-4 mode will take effect.
|
||||
// 2. If AT+CIPRXGET=1, it will report +CIPRXGET: 1,<cid>(multi client) when
|
||||
const std::string expected = std::string("+CIPRXGET: 1,") + std::to_string(link_id);
|
||||
if (response.find(expected) != std::string::npos) {
|
||||
uint64_t data_ready = 1;
|
||||
write(data_ready_fd, &data_ready, sizeof(data_ready));
|
||||
ESP_LOGD(TAG, "Got data on modem!");
|
||||
}
|
||||
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());
|
||||
if (state == status::SENDING) {
|
||||
return send(response);
|
||||
} else if (state == status::CONNECTING) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
## Required IDF version
|
||||
idf: ">=4.1.0"
|
||||
idf: '>=4.1.0'
|
||||
espressif/esp_modem:
|
||||
version: "^1.0.0"
|
||||
override_path: "../../../"
|
||||
version: ^2
|
||||
override_path: ../../../
|
||||
espressif/esp_modem_usb_dte:
|
||||
version: "^1.2.0"
|
||||
version: ^1.2.0
|
||||
rules:
|
||||
- if: "idf_version >=4.4"
|
||||
- if: "target in [esp32s2, esp32s3, esp32p4]"
|
||||
- if: idf_version >=4.4
|
||||
- if: target in [esp32s2, esp32s3, esp32p4]
|
||||
console_cmd_ping:
|
||||
version: '*'
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -16,8 +16,10 @@
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_netif_ppp.h"
|
||||
#include "mqtt_client.h"
|
||||
#include "esp_modem_api.h"
|
||||
#include "esp_console.h"
|
||||
#include "console_ping.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
@@ -35,7 +37,6 @@ static const char *TAG = "pppos_example";
|
||||
static EventGroupHandle_t event_group = NULL;
|
||||
static const int CONNECT_BIT = BIT0;
|
||||
static const int DISCONNECT_BIT = BIT1;
|
||||
static const int GOT_DATA_BIT = BIT2;
|
||||
static const int USB_DISCONNECTED_BIT = BIT3; // Used only with USB DTE but we define it unconditionally, to avoid too many #ifdefs in the code
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM
|
||||
@@ -64,47 +65,6 @@ if ((xEventGroupGetBits(event_group) & USB_DISCONNECTED_BIT) == USB_DISCONNECTED
|
||||
#define CHECK_USB_DISCONNECTION(event_group)
|
||||
#endif
|
||||
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIu32, base, event_id);
|
||||
esp_mqtt_event_handle_t event = event_data;
|
||||
esp_mqtt_client_handle_t client = event->client;
|
||||
int msg_id;
|
||||
switch ((esp_mqtt_event_id_t)event_id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
|
||||
msg_id = esp_mqtt_client_subscribe(client, CONFIG_EXAMPLE_MQTT_TEST_TOPIC, 0);
|
||||
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||
break;
|
||||
case MQTT_EVENT_SUBSCRIBED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
|
||||
msg_id = esp_mqtt_client_publish(client, CONFIG_EXAMPLE_MQTT_TEST_TOPIC, CONFIG_EXAMPLE_MQTT_TEST_DATA, 0, 0, 0);
|
||||
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_UNSUBSCRIBED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_PUBLISHED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_DATA:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
|
||||
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
|
||||
printf("DATA=%.*s\r\n", event->data_len, event->data);
|
||||
xEventGroupSetBits(event_group, GOT_DATA_BIT);
|
||||
break;
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "MQTT other event id: %d", event->event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void on_ppp_changed(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
@@ -160,6 +120,11 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &on_ip_event, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, NULL));
|
||||
|
||||
// Initialize console REPL, register ping and start it
|
||||
ESP_ERROR_CHECK(console_cmd_init());
|
||||
ESP_ERROR_CHECK(console_cmd_ping_register());
|
||||
ESP_ERROR_CHECK(console_cmd_start());
|
||||
|
||||
/* Configure the PPP netif */
|
||||
esp_err_t err;
|
||||
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_PPP_APN);
|
||||
@@ -251,7 +216,7 @@ void app_main(void)
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT
|
||||
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
|
||||
xEventGroupClearBits(event_group, CONNECT_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
|
||||
|
||||
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_DETECT);
|
||||
if (err != ESP_OK) {
|
||||
@@ -270,7 +235,7 @@ void app_main(void)
|
||||
}
|
||||
#endif // CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT
|
||||
|
||||
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
|
||||
xEventGroupClearBits(event_group, CONNECT_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
|
||||
|
||||
/* Run the modem demo app */
|
||||
#if CONFIG_EXAMPLE_NEED_SIM_PIN == 1
|
||||
@@ -340,15 +305,11 @@ void app_main(void)
|
||||
}
|
||||
|
||||
/* Config MQTT */
|
||||
esp_mqtt_client_config_t mqtt_config = {
|
||||
.broker.address.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
|
||||
};
|
||||
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
|
||||
esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
|
||||
esp_mqtt_client_start(mqtt_client);
|
||||
int ping_ret_val;
|
||||
ESP_ERROR_CHECK(esp_console_run("ping www.espressif.com", &ping_ret_val));
|
||||
ESP_LOGI(TAG, "Ping command finished with return value: %d", ping_ret_val);
|
||||
|
||||
#if CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
|
||||
xEventGroupWaitBits(event_group, GOT_DATA_BIT, pdTRUE, pdFALSE, portMAX_DELAY);
|
||||
esp_modem_pause_net(dce, true);
|
||||
err = esp_modem_get_signal_quality(dce, &rssi, &ber);
|
||||
if (err != ESP_OK) {
|
||||
@@ -357,14 +318,15 @@ void app_main(void)
|
||||
}
|
||||
ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d", rssi, ber);
|
||||
esp_modem_pause_net(dce, false);
|
||||
esp_mqtt_client_publish(mqtt_client, CONFIG_EXAMPLE_MQTT_TEST_TOPIC, CONFIG_EXAMPLE_MQTT_TEST_DATA, 0, 0, 0);
|
||||
ESP_ERROR_CHECK(esp_console_run("ping www.espressif.com", &ping_ret_val));
|
||||
ESP_LOGI(TAG, "Ping command finished with return value: %d", ping_ret_val);
|
||||
#endif // CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
|
||||
|
||||
ESP_LOGI(TAG, "Waiting for MQTT data");
|
||||
xEventGroupWaitBits(event_group, GOT_DATA_BIT | USB_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
|
||||
if (ping_ret_val != 0) {
|
||||
ESP_LOGE(TAG, "Ping command failed with return value: %d", ping_ret_val);
|
||||
}
|
||||
CHECK_USB_DISCONNECTION(event_group);
|
||||
|
||||
esp_mqtt_client_destroy(mqtt_client);
|
||||
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_COMMAND);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_modem_set_mode(ESP_MODEM_MODE_COMMAND) failed with %d", err);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 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
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
@@ -8,16 +8,12 @@ def test_pppos_connect(dut):
|
||||
steps:
|
||||
1. initializes connection with SIM800
|
||||
2. checks we get an IP
|
||||
3. checks for the MQTT events
|
||||
3. checks that the ping command works
|
||||
4. checks that the client cleanly disconnects
|
||||
"""
|
||||
# Check the sequence of connecting, publishing, disconnecting
|
||||
dut.expect('Modem Connect to PPP Server', timeout=90)
|
||||
# Check for MQTT connection and the data event
|
||||
dut.expect('MQTT_EVENT_CONNECTED')
|
||||
dut.expect('MQTT_EVENT_DATA')
|
||||
dut.expect('TOPIC=/ci/esp-modem/pppos-client')
|
||||
dut.expect('DATA=esp32-pppos')
|
||||
dut.expect('Ping command finished with return value: 0', timeout=30)
|
||||
# Check that we have disconnected
|
||||
dut.expect('User interrupted event')
|
||||
# And can use commands again
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
idf_component_register(SRCS "simple_cmux_client_main.cpp" "simple_mqtt_client.cpp"
|
||||
idf_component_register(SRCS "simple_cmux_client_main.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
dependencies:
|
||||
espressif/esp_modem:
|
||||
version: "^1.0.1"
|
||||
version: "^2"
|
||||
override_path: "../../../"
|
||||
console_cmd_ping:
|
||||
version: '*'
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -21,11 +21,13 @@
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "esp_modem_config.h"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include "simple_mqtt_client.hpp"
|
||||
#include "esp_vfs_dev.h" // For optional VFS support
|
||||
#include "esp_https_ota.h" // For potential OTA configuration
|
||||
#include "vfs_resource/vfs_create.hpp"
|
||||
#include "SIM7070_gnss.hpp"
|
||||
#include "esp_console.h"
|
||||
#include "console_ping.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#if defined(CONFIG_EXAMPLE_FLOW_CONTROL_NONE)
|
||||
#define EXAMPLE_FLOW_CONTROL ESP_MODEM_FLOW_CONTROL_NONE
|
||||
@@ -35,18 +37,13 @@
|
||||
#define EXAMPLE_FLOW_CONTROL ESP_MODEM_FLOW_CONTROL_HW
|
||||
#endif
|
||||
|
||||
#define BROKER_URL CONFIG_BROKER_URI
|
||||
|
||||
|
||||
using namespace esp_modem;
|
||||
|
||||
static const char *TAG = "cmux_example";
|
||||
|
||||
class StatusHandler {
|
||||
public:
|
||||
static constexpr auto IP_Event = SignalGroup::bit0;
|
||||
static constexpr auto MQTT_Connect = SignalGroup::bit1;
|
||||
static constexpr auto MQTT_Data = SignalGroup::bit2;
|
||||
static constexpr auto IP_Event = SignalGroup::bit0;
|
||||
|
||||
StatusHandler()
|
||||
{
|
||||
@@ -58,12 +55,6 @@ public:
|
||||
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, on_event);
|
||||
}
|
||||
|
||||
void handle_mqtt(MqttClient *client)
|
||||
{
|
||||
mqtt_client = client;
|
||||
client->register_handler(ESP_EVENT_ANY_ID, on_event, this);
|
||||
}
|
||||
|
||||
esp_err_t wait_for(decltype(IP_Event) event, int milliseconds)
|
||||
{
|
||||
return signal.wait_any(event, milliseconds);
|
||||
@@ -80,8 +71,6 @@ private:
|
||||
auto *handler = static_cast<StatusHandler *>(arg);
|
||||
if (base == IP_EVENT) {
|
||||
handler->ip_event(event, data);
|
||||
} else {
|
||||
handler->mqtt_event(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,27 +81,16 @@ private:
|
||||
ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask));
|
||||
ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw));
|
||||
ip_event_type = static_cast<ip_event_t>(id);
|
||||
signal.set(IP_Event);
|
||||
} else if (id == IP_EVENT_PPP_LOST_IP) {
|
||||
ip_event_type = static_cast<ip_event_t>(id);
|
||||
signal.set(IP_Event);
|
||||
}
|
||||
ip_event_type = static_cast<ip_event_t>(id);
|
||||
}
|
||||
|
||||
void mqtt_event(int32_t event, void *data)
|
||||
{
|
||||
if (mqtt_client && event == mqtt_client->get_event(MqttClient::Event::CONNECT)) {
|
||||
signal.set(MQTT_Connect);
|
||||
} else if (mqtt_client && event == mqtt_client->get_event(MqttClient::Event::DATA)) {
|
||||
ESP_LOGI(TAG, " TOPIC: %s", mqtt_client->get_topic(data).c_str());
|
||||
ESP_LOGI(TAG, " DATA: %s", mqtt_client->get_data(data).c_str());
|
||||
signal.set(MQTT_Data);
|
||||
}
|
||||
}
|
||||
|
||||
esp_modem::SignalGroup signal{};
|
||||
MqttClient *mqtt_client{nullptr};
|
||||
ip_event_t ip_event_type;
|
||||
ip_event_t ip_event_type{};
|
||||
};
|
||||
|
||||
|
||||
@@ -122,6 +100,11 @@ extern "C" void app_main(void)
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
|
||||
// Initialize console REPL, register ping and start it
|
||||
ESP_ERROR_CHECK(console_cmd_init());
|
||||
ESP_ERROR_CHECK(console_cmd_ping_register());
|
||||
ESP_ERROR_CHECK(console_cmd_start());
|
||||
|
||||
/* Configure and create the DTE */
|
||||
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
|
||||
/* setup UART specific configuration based on kconfig options */
|
||||
@@ -175,7 +158,7 @@ extern "C" void app_main(void)
|
||||
#endif
|
||||
assert(dce);
|
||||
|
||||
/* Try to connect to the network and publish an mqtt topic */
|
||||
/* Try to connect to the network */
|
||||
StatusHandler handler;
|
||||
|
||||
if (dte_config.uart_config.flow_control == ESP_MODEM_FLOW_CONTROL_HW) {
|
||||
@@ -224,24 +207,14 @@ extern "C" void app_main(void)
|
||||
} else if (handler.get_ip_event_type() == IP_EVENT_PPP_GOT_IP) {
|
||||
std::cout << "Got IP address" << std::endl;
|
||||
|
||||
/* When connected to network, subscribe and publish some MQTT data */
|
||||
MqttClient mqtt(BROKER_URL);
|
||||
handler.handle_mqtt(&mqtt);
|
||||
mqtt.connect();
|
||||
if (!handler.wait_for(StatusHandler::MQTT_Connect, 60000)) {
|
||||
ESP_LOGE(TAG, "Cannot connect to %s within specified timeout... exiting", BROKER_URL);
|
||||
/* When connected to network, we can ping the internet */
|
||||
int ping_ret_val;
|
||||
ESP_ERROR_CHECK(esp_console_run("ping www.espressif.com", &ping_ret_val));
|
||||
ESP_LOGI(TAG, "Ping command finished with return value: %d", ping_ret_val);
|
||||
if (ping_ret_val != 0) {
|
||||
ESP_LOGE(TAG, "Ping command failed with return value: %d", ping_ret_val);
|
||||
return;
|
||||
}
|
||||
std::cout << "Connected" << std::endl;
|
||||
|
||||
mqtt.subscribe(CONFIG_EXAMPLE_MQTT_TEST_TOPIC);
|
||||
mqtt.publish(CONFIG_EXAMPLE_MQTT_TEST_TOPIC, CONFIG_EXAMPLE_MQTT_TEST_DATA);
|
||||
if (!handler.wait_for(StatusHandler::MQTT_Data, 60000)) {
|
||||
ESP_LOGE(TAG, "Didn't receive published data within specified timeout... exiting");
|
||||
return;
|
||||
}
|
||||
std::cout << "Received MQTT data" << std::endl;
|
||||
|
||||
} else if (handler.get_ip_event_type() == IP_EVENT_PPP_LOST_IP) {
|
||||
ESP_LOGE(TAG, "PPP client has lost connection... exiting");
|
||||
return;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* PPPoS Client Example
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include "mqtt_client.h"
|
||||
#include "simple_mqtt_client.hpp"
|
||||
|
||||
/**
|
||||
* Reference to the MQTT event base
|
||||
*/
|
||||
ESP_EVENT_DECLARE_BASE(MQTT_EVENTS);
|
||||
|
||||
/**
|
||||
* Thin wrapper around C mqtt_client
|
||||
*/
|
||||
struct MqttClientHandle {
|
||||
explicit MqttClientHandle(const std::string &uri)
|
||||
{
|
||||
esp_mqtt_client_config_t config = { };
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
config.broker.address.uri = uri.c_str();
|
||||
#else
|
||||
config.uri = uri.c_str();
|
||||
#endif
|
||||
client = esp_mqtt_client_init(&config);
|
||||
}
|
||||
|
||||
~MqttClientHandle()
|
||||
{
|
||||
esp_mqtt_client_destroy(client);
|
||||
}
|
||||
|
||||
esp_mqtt_client_handle_t client;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Definitions of MqttClient methods
|
||||
*/
|
||||
MqttClient::MqttClient(const std::string &uri):
|
||||
h(std::unique_ptr<MqttClientHandle>(new MqttClientHandle(uri)))
|
||||
{}
|
||||
|
||||
void MqttClient::connect()
|
||||
{
|
||||
esp_mqtt_client_start(h->client);
|
||||
}
|
||||
|
||||
int32_t MqttClient::get_event(MqttClient::Event ev)
|
||||
{
|
||||
switch (ev) {
|
||||
case Event::CONNECT: {
|
||||
return MQTT_EVENT_CONNECTED;
|
||||
}
|
||||
case Event::DATA:
|
||||
return MQTT_EVENT_DATA;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int MqttClient::publish(const std::string &topic, const std::string &data, int qos)
|
||||
{
|
||||
return esp_mqtt_client_publish(h->client, topic.c_str(), data.c_str(), 0, qos, 0);
|
||||
}
|
||||
|
||||
int MqttClient::subscribe(const std::string &topic, int qos)
|
||||
{
|
||||
return esp_mqtt_client_subscribe(h->client, topic.c_str(), qos);
|
||||
}
|
||||
|
||||
std::string MqttClient::get_topic(void *event_data)
|
||||
{
|
||||
auto event = (esp_mqtt_event_handle_t)event_data;
|
||||
if (event == nullptr || event->client != h->client)
|
||||
return {};
|
||||
|
||||
return std::string(event->topic, event->topic_len);
|
||||
}
|
||||
|
||||
std::string MqttClient::get_data(void *event_data)
|
||||
{
|
||||
auto event = (esp_mqtt_event_handle_t)event_data;
|
||||
if (event == nullptr || event->client != h->client)
|
||||
return {};
|
||||
|
||||
return std::string(event->data, event->data_len);
|
||||
}
|
||||
|
||||
void MqttClient::register_handler(int32_t event_id, esp_event_handler_t event_handler, void *arg)
|
||||
{
|
||||
ESP_ERROR_CHECK(esp_mqtt_client_register_event(h->client, MQTT_EVENT_ANY, event_handler, arg));
|
||||
}
|
||||
|
||||
MqttClient::~MqttClient() = default;
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* PPPoS Client Example
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
struct MqttClientHandle;
|
||||
|
||||
/**
|
||||
* @brief Simple MQTT client wrapper
|
||||
*/
|
||||
class MqttClient {
|
||||
public:
|
||||
enum class Event {
|
||||
CONNECT,
|
||||
DATA,
|
||||
};
|
||||
|
||||
explicit MqttClient(const std::string &uri);
|
||||
~MqttClient();
|
||||
|
||||
/**
|
||||
* @brief Start the mqtt-client
|
||||
*/
|
||||
void connect();
|
||||
|
||||
/**
|
||||
* @brief Publish to topic
|
||||
* @param topic Topic to publish
|
||||
* @param data Data to publish
|
||||
* @param qos QoS (0 by default)
|
||||
* @return message id
|
||||
*/
|
||||
int publish(const std::string &topic, const std::string &data, int qos = 0);
|
||||
|
||||
/**
|
||||
* @brief Subscribe to a topic
|
||||
* @param topic Topic to subscribe
|
||||
* @param qos QoS (0 by default)
|
||||
* @return message id
|
||||
*/
|
||||
int subscribe(const std::string &topic, int qos = 0);
|
||||
|
||||
/**
|
||||
* @brief Get topic from event data
|
||||
* @return String topic
|
||||
*/
|
||||
std::string get_topic(void *);
|
||||
|
||||
/**
|
||||
* @brief Get published data from event
|
||||
* @return String representation of the data
|
||||
*/
|
||||
std::string get_data(void *);
|
||||
|
||||
/**
|
||||
* @brief Register MQTT event
|
||||
* @param id Event id
|
||||
* @param event_handler Event handler
|
||||
* @param event_handler_arg Event handler parameters
|
||||
*/
|
||||
void register_handler(int32_t id, esp_event_handler_t event_handler, void *event_handler_arg);
|
||||
|
||||
/**
|
||||
* @brief Convert internal MQTT event to standard ESPEvent
|
||||
* @param ev internal mqtt event
|
||||
* @return corresponding esp_event id
|
||||
*/
|
||||
static int32_t get_event(Event ev);
|
||||
|
||||
private:
|
||||
std::unique_ptr<MqttClientHandle> h;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
@@ -6,21 +6,10 @@ from __future__ import print_function, unicode_literals
|
||||
def test_cmux_connection(dut):
|
||||
"""
|
||||
steps:
|
||||
1. initializes connection with SIM800
|
||||
2. checks we get an IP
|
||||
3. checks for the MQTT events
|
||||
1. checks we're in CMUX mode and get an IP
|
||||
2. checks for ping command
|
||||
"""
|
||||
# Get topic and data from Kconfig
|
||||
topic = ''
|
||||
data = ''
|
||||
try:
|
||||
topic = dut.app.sdkconfig.get('EXAMPLE_MQTT_TEST_TOPIC')
|
||||
data = dut.app.sdkconfig.get('EXAMPLE_MQTT_TEST_DATA')
|
||||
except Exception:
|
||||
print('ENV_TEST_FAILURE: Cannot find broker url in sdkconfig')
|
||||
raise
|
||||
# Check the sequence of connecting, publishing, disconnecting
|
||||
dut.expect('Modem has correctly entered multiplexed')
|
||||
# Check for MQTT connection and the data event
|
||||
dut.expect(f'TOPIC: {topic}')
|
||||
dut.expect(f'DATA: {data}')
|
||||
# Check we're in CMUX mode and get an IP
|
||||
dut.expect('Modem has correctly entered multiplexed command/data mode', timeout=60)
|
||||
# Check for ping command
|
||||
dut.expect('Ping command finished with return value: 0', timeout=30)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: "1.4.0"
|
||||
version: "2.0.0"
|
||||
description: Library for communicating with cellular modems in command and data modes
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem
|
||||
issues: https://github.com/espressif/esp-protocols/issues
|
||||
|
||||
@@ -103,6 +103,11 @@ public:
|
||||
{
|
||||
dte->set_urc_cb(on_read_cb);
|
||||
}
|
||||
|
||||
void set_enhanced_urc(esp_modem::DTE::enhanced_urc_cb enhanced_cb)
|
||||
{
|
||||
dte->set_enhanced_urc_cb(enhanced_cb);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -115,6 +115,42 @@ public:
|
||||
{
|
||||
command_cb.urc_handler = std::move(line_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enhanced URC handler with buffer consumption control
|
||||
* @param buffer_info Information about the current buffer state
|
||||
* @return Information about how much of the buffer to consume
|
||||
*/
|
||||
struct UrcBufferInfo {
|
||||
const uint8_t* buffer_start; // Start of entire buffer
|
||||
size_t buffer_total_size; // Total buffer size
|
||||
size_t processed_offset; // Offset of already processed data
|
||||
size_t new_data_size; // Size of new data since last call
|
||||
const uint8_t* new_data_start; // Pointer to start of new data
|
||||
bool is_command_active; // Whether a command is currently waiting for response
|
||||
};
|
||||
|
||||
enum class UrcConsumeResult {
|
||||
CONSUME_NONE, // Don't consume anything, continue waiting
|
||||
CONSUME_PARTIAL, // Consume only part of the buffer
|
||||
CONSUME_ALL // Consume entire buffer
|
||||
};
|
||||
|
||||
struct UrcConsumeInfo {
|
||||
UrcConsumeResult result;
|
||||
size_t consume_size; // How many bytes to consume (0 = none, SIZE_MAX = all)
|
||||
};
|
||||
|
||||
typedef std::function<UrcConsumeInfo(const UrcBufferInfo &)> enhanced_urc_cb;
|
||||
|
||||
/**
|
||||
* @brief Set enhanced URC callback with buffer consumption control
|
||||
* @param enhanced_cb Enhanced callback that can control buffer consumption
|
||||
*/
|
||||
void set_enhanced_urc_cb(enhanced_urc_cb enhanced_cb)
|
||||
{
|
||||
command_cb.enhanced_urc_handler = std::move(enhanced_cb);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -171,6 +207,33 @@ private:
|
||||
[[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode and cleanup */
|
||||
void exit_cmux_internal(); /*!< Cleanup CMUX */
|
||||
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
/**
|
||||
* @brief Buffer state tracking for enhanced URC processing
|
||||
*/
|
||||
struct BufferState {
|
||||
size_t total_processed = 0; /*!< Total bytes processed by URC handlers */
|
||||
size_t last_urc_processed = 0; /*!< Last offset processed by URC */
|
||||
bool command_waiting = false; /*!< Whether command is waiting for response */
|
||||
size_t command_start_offset = 0; /*!< Where current command response started */
|
||||
} buffer_state;
|
||||
|
||||
/**
|
||||
* @brief Update buffer state when new data arrives
|
||||
* @param new_data_size Size of new data added to buffer
|
||||
*/
|
||||
void update_buffer_state(size_t new_data_size);
|
||||
|
||||
/**
|
||||
* @brief Create URC buffer information for enhanced handlers
|
||||
* @param data Buffer data pointer
|
||||
* @param consumed Already consumed bytes
|
||||
* @param len New data length
|
||||
* @return UrcBufferInfo structure with complete buffer context
|
||||
*/
|
||||
UrcBufferInfo create_urc_info(uint8_t* data, size_t consumed, size_t len);
|
||||
#endif
|
||||
|
||||
Lock internal_lock{}; /*!< Locks DTE operations */
|
||||
unique_buffer buffer; /*!< DTE buffer */
|
||||
std::shared_ptr<CMux> cmux_term; /*!< Primary terminal for this DTE */
|
||||
@@ -216,6 +279,7 @@ private:
|
||||
struct command_cb {
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
got_line_cb urc_handler {}; /*!< URC callback if enabled */
|
||||
enhanced_urc_cb enhanced_urc_handler {}; /*!< Enhanced URC callback with consumption control */
|
||||
#endif
|
||||
static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */
|
||||
got_line_cb got_line; /*!< Supplied command callback */
|
||||
@@ -223,7 +287,7 @@ private:
|
||||
char separator{}; /*!< Command reply separator (end of line/processing unit) */
|
||||
command_result result{}; /*!< Command return code */
|
||||
SignalGroup signal; /*!< Event group used to signal request-response operations */
|
||||
bool process_line(uint8_t *data, size_t consumed, size_t len); /*!< Lets the processing callback handle one line (processing unit) */
|
||||
bool process_line(uint8_t *data, size_t consumed, size_t len, DTE* dte = nullptr); /*!< Lets the processing callback handle one line (processing unit) */
|
||||
bool wait_for_line(uint32_t time_ms) /*!< Waiting for command processing */
|
||||
{
|
||||
return signal.wait_any(command_cb::GOT_LINE, time_ms);
|
||||
|
||||
@@ -163,6 +163,15 @@ extern "C" esp_err_t esp_modem_read_pin(esp_modem_dce_t *dce_wrap, bool *pin)
|
||||
return command_response_to_esp_err(dce_wrap->dce->read_pin(*pin));
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_set_echo(esp_modem_dce_t *dce_wrap, const bool echo_on)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return command_response_to_esp_err(dce_wrap->dce->set_echo(echo_on));
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_sms_txt_mode(esp_modem_dce_t *dce_wrap, bool txt)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
|
||||
@@ -65,6 +65,10 @@ void DTE::set_command_callbacks()
|
||||
{
|
||||
primary_term->set_read_cb([this](uint8_t *data, size_t len) {
|
||||
Scoped<Lock> l(command_cb.line_lock);
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
// Update buffer state when new data arrives
|
||||
update_buffer_state(len);
|
||||
#endif
|
||||
#ifndef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
if (command_cb.got_line == nullptr || command_cb.result != command_result::TIMEOUT) {
|
||||
return false; // this line has been processed already (got OK or FAIL previously)
|
||||
@@ -80,7 +84,7 @@ void DTE::set_command_callbacks()
|
||||
std::memcpy(inflatable.current(), data, len);
|
||||
data = inflatable.begin();
|
||||
}
|
||||
if (command_cb.process_line(data, inflatable.consumed, len)) {
|
||||
if (command_cb.process_line(data, inflatable.consumed, len, this)) {
|
||||
return true;
|
||||
}
|
||||
// at this point we're sure that the data processing hasn't finished,
|
||||
@@ -92,7 +96,7 @@ void DTE::set_command_callbacks()
|
||||
inflatable.consumed += len;
|
||||
return false;
|
||||
#else
|
||||
if (command_cb.process_line(data, 0, len)) {
|
||||
if (command_cb.process_line(data, 0, len, this)) {
|
||||
return true;
|
||||
}
|
||||
// cannot inflate and the processing hasn't finishes in the first iteration, but continue
|
||||
@@ -105,7 +109,7 @@ void DTE::set_command_callbacks()
|
||||
if (buffer.size > buffer.consumed) {
|
||||
data = buffer.get();
|
||||
len = primary_term->read(data + buffer.consumed, buffer.size - buffer.consumed);
|
||||
if (command_cb.process_line(data, buffer.consumed, len)) {
|
||||
if (command_cb.process_line(data, buffer.consumed, len, this)) {
|
||||
return true;
|
||||
}
|
||||
buffer.consumed += len;
|
||||
@@ -121,7 +125,7 @@ void DTE::set_command_callbacks()
|
||||
inflatable.grow(inflatable.consumed + len);
|
||||
}
|
||||
len = primary_term->read(inflatable.current(), len);
|
||||
if (command_cb.process_line(inflatable.begin(), inflatable.consumed, len)) {
|
||||
if (command_cb.process_line(inflatable.begin(), inflatable.consumed, len, this)) {
|
||||
return true;
|
||||
}
|
||||
inflatable.consumed += len;
|
||||
@@ -150,10 +154,19 @@ void DTE::set_command_callbacks()
|
||||
command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator)
|
||||
{
|
||||
Scoped<Lock> l1(internal_lock);
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
// Track command start
|
||||
buffer_state.command_waiting = true;
|
||||
buffer_state.command_start_offset = buffer_state.total_processed;
|
||||
#endif
|
||||
command_cb.set(got_line, separator);
|
||||
primary_term->write((uint8_t *)command.c_str(), command.length());
|
||||
command_cb.wait_for_line(time_ms);
|
||||
command_cb.set(nullptr);
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
// Track command end
|
||||
buffer_state.command_waiting = false;
|
||||
#endif
|
||||
buffer.consumed = 0;
|
||||
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
|
||||
inflatable.deflate();
|
||||
@@ -365,18 +378,54 @@ void DTE::on_read(got_line_cb on_read_cb)
|
||||
});
|
||||
}
|
||||
|
||||
bool DTE::command_cb::process_line(uint8_t *data, size_t consumed, size_t len)
|
||||
bool DTE::command_cb::process_line(uint8_t *data, size_t consumed, size_t len, DTE* dte)
|
||||
{
|
||||
// returning true indicates that the processing finished and lower layers can destroy the accumulated buffer
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
bool consume_buffer = false;
|
||||
if (urc_handler) {
|
||||
consume_buffer = urc_handler(data, consumed + len) != command_result::TIMEOUT;
|
||||
// Call enhanced URC handler if registered
|
||||
if (enhanced_urc_handler && dte) {
|
||||
// Create buffer info for enhanced URC handler
|
||||
UrcBufferInfo buffer_info = dte->create_urc_info(data, consumed, len);
|
||||
|
||||
// Call enhanced URC handler
|
||||
UrcConsumeInfo consume_info = enhanced_urc_handler(buffer_info);
|
||||
|
||||
// Handle consumption control
|
||||
switch (consume_info.result) {
|
||||
case UrcConsumeResult::CONSUME_NONE:
|
||||
// Don't consume anything, continue with command processing
|
||||
break;
|
||||
|
||||
case UrcConsumeResult::CONSUME_PARTIAL:
|
||||
// Consume only specified amount
|
||||
dte->buffer_state.last_urc_processed += consume_info.consume_size;
|
||||
// Adjust data pointers for command processing
|
||||
data += consume_info.consume_size;
|
||||
consumed = (consumed + len) - consume_info.consume_size;
|
||||
len = 0;
|
||||
break;
|
||||
|
||||
case UrcConsumeResult::CONSUME_ALL:
|
||||
// Consume entire buffer
|
||||
dte->buffer_state.last_urc_processed = consumed + len;
|
||||
return true; // Signal buffer consumption
|
||||
}
|
||||
}
|
||||
if (result != command_result::TIMEOUT || got_line == nullptr) {
|
||||
return consume_buffer; // this line has been processed already (got OK or FAIL previously)
|
||||
|
||||
// Fallback to legacy URC handler if enhanced handler not set
|
||||
if (urc_handler) {
|
||||
bool consume_buffer = urc_handler(data, consumed + len) != command_result::TIMEOUT;
|
||||
if (result != command_result::TIMEOUT || got_line == nullptr) {
|
||||
return consume_buffer; // this line has been processed already (got OK or FAIL previously)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Continue with normal command processing
|
||||
if (result != command_result::TIMEOUT || got_line == nullptr) {
|
||||
return false; // Command processing continues
|
||||
}
|
||||
|
||||
if (memchr(data + consumed, separator, len)) {
|
||||
result = got_line(data, consumed + len);
|
||||
if (result == command_result::OK || result == command_result::FAIL) {
|
||||
@@ -423,3 +472,22 @@ void DTE::extra_buffer::grow(size_t need_size)
|
||||
*/
|
||||
unique_buffer::unique_buffer(size_t size):
|
||||
data(std::make_unique<uint8_t[]>(size)), size(size), consumed(0) {}
|
||||
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
void DTE::update_buffer_state(size_t new_data_size)
|
||||
{
|
||||
buffer_state.total_processed += new_data_size;
|
||||
}
|
||||
|
||||
DTE::UrcBufferInfo DTE::create_urc_info(uint8_t* data, size_t consumed, size_t len)
|
||||
{
|
||||
return {
|
||||
.buffer_start = data,
|
||||
.buffer_total_size = consumed + len,
|
||||
.processed_offset = buffer_state.last_urc_processed,
|
||||
.new_data_size = (consumed + len) - buffer_state.last_urc_processed,
|
||||
.new_data_start = data + buffer_state.last_urc_processed,
|
||||
.is_command_active = buffer_state.command_waiting
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "esp_system.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_idf_version.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_netif_ppp.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@@ -123,7 +124,7 @@ TEST_CASE("Disconnection test", "[esp_modem]")
|
||||
CHECK(b == 2);
|
||||
}
|
||||
|
||||
|
||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
extern "C" {
|
||||
|
||||
static void handle(int nr)
|
||||
@@ -131,10 +132,10 @@ extern "C" {
|
||||
ESP_LOGE(TAG, "Signal handler %d", nr);
|
||||
}
|
||||
|
||||
_sig_func_ptr signal (int nr, _sig_func_ptr)
|
||||
_sig_func_ptr signal(int nr, _sig_func_ptr)
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
#endif // ESP_IDF_VERSION < v6
|
||||
|
||||
@@ -223,9 +223,9 @@ static int ppp_cmd_iperf(int argc, char **argv)
|
||||
cfg.flag & IPERF_FLAG_TCP ? "tcp" : "udp",
|
||||
cfg.flag & IPERF_FLAG_SERVER ? "server" : "client",
|
||||
(uint16_t) cfg.source_ip4 & 0xFF,
|
||||
(uint16_t) (cfg.source_ip4 >> 8) & 0xFF,
|
||||
(uint16_t) (cfg.source_ip4 >> 16) & 0xFF,
|
||||
(uint16_t) (cfg.source_ip4 >> 24) & 0xFF,
|
||||
(uint16_t)(cfg.source_ip4 >> 8) & 0xFF,
|
||||
(uint16_t)(cfg.source_ip4 >> 16) & 0xFF,
|
||||
(uint16_t)(cfg.source_ip4 >> 24) & 0xFF,
|
||||
cfg.sport,
|
||||
cfg.destination_ip4 & 0xFF, (cfg.destination_ip4 >> 8) & 0xFF,
|
||||
(cfg.destination_ip4 >> 16) & 0xFF, (cfg.destination_ip4 >> 24) & 0xFF, cfg.dport,
|
||||
@@ -234,6 +234,18 @@ static int ppp_cmd_iperf(int argc, char **argv)
|
||||
iperf_start(&cfg);
|
||||
return 0;
|
||||
}
|
||||
static int restart(int argc, char **argv)
|
||||
{
|
||||
ESP_LOGI("main", "Restarting");
|
||||
esp_restart();
|
||||
return 0;
|
||||
}
|
||||
static int heap_size(int argc, char **argv)
|
||||
{
|
||||
uint32_t heap_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
|
||||
ESP_LOGI(TAG, "min heap size: %" PRIu32, heap_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_pppd(void)
|
||||
{
|
||||
@@ -286,4 +298,25 @@ void register_pppd(void)
|
||||
.argtable = &iperf_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&iperf_cmd));
|
||||
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "restart",
|
||||
.help = "Restart the program",
|
||||
.hint = NULL,
|
||||
.func = &restart,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
|
||||
|
||||
}
|
||||
{
|
||||
const esp_console_cmd_t heap_cmd = {
|
||||
.command = "heap",
|
||||
.help = "Get minimum size of free heap memory that was available during program execution",
|
||||
.hint = NULL,
|
||||
.func = &heap_size,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&heap_cmd));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/esp_modem:
|
||||
version: "^1.1.0"
|
||||
version: "^2"
|
||||
override_path: "../../.."
|
||||
espressif/iperf-cmd: "^0.1.1"
|
||||
|
||||
@@ -2,7 +2,5 @@
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../.." "../../../mbedtls_cxx")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(ota_test)
|
||||
|
||||
14
components/esp_modem/test/target_ota/main/idf_component.yml
Normal file
14
components/esp_modem/test/target_ota/main/idf_component.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/esp_modem:
|
||||
version: '^2'
|
||||
override_path: ../../..
|
||||
espressif/mbedtls_cxx:
|
||||
version: '*'
|
||||
override_path: ../../../../mbedtls_cxx
|
||||
idf:
|
||||
version: '>=4.1.0'
|
||||
espressif/mqtt:
|
||||
rules:
|
||||
- if: idf_version >=6.0
|
||||
version: ^1.0.0
|
||||
@@ -1,4 +1,4 @@
|
||||
CONFIG_TEST_DEVICE_MODEM_GENERIC=y
|
||||
CONFIG_TEST_OTA_URI="https://raw.githubusercontent.com/espressif/esp-protocols/master/components/esp_modem/test/target_ota/bin/blink.bin"
|
||||
CONFIG_TEST_OTA_CA_CERT="MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBSU0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6aqXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddng9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuWraKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGBAfr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21reacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNVHR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEBMAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IBAQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3zax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7hqG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbCEXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697EA7sKPPcw7+uvTPyLNhBzPvOk"
|
||||
CONFIG_TEST_OTA_CA_CERT="MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTflMrY="
|
||||
CONFIG_TEST_OTA_CN="github.com"
|
||||
|
||||
152
components/esp_modem/test/target_urc/README.md
Normal file
152
components/esp_modem/test/target_urc/README.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# ESP Modem Enhanced URC Test
|
||||
|
||||
## Overview
|
||||
|
||||
This test validates the enhanced URC (Unsolicited Result Code) interface with buffer consumption control. It demonstrates the new `set_enhanced_urc()` API that provides granular control over buffer consumption and complete buffer visibility.
|
||||
|
||||
## Test Configuration
|
||||
|
||||
- **Target**: ESP-AT device with HTTP server
|
||||
- **UART**: 115200 baud, 8N1, TX=17, RX=18
|
||||
- **Buffer Size**: 1024 bytes
|
||||
- **Timeout**: 15 seconds
|
||||
|
||||
## Test Cases
|
||||
|
||||
### 1. Enhanced URC Handler Registration
|
||||
- **Objective**: Verify enhanced URC handler can be registered
|
||||
- **Method**: Call `set_enhanced_urc()` with custom handler
|
||||
- **Expected**: Handler receives `UrcBufferInfo` with complete buffer context
|
||||
|
||||
### 2. Buffer Visibility
|
||||
- **Objective**: Verify URC handler receives complete buffer information
|
||||
- **Method**: Log buffer state information in handler
|
||||
- **Expected**: Handler receives `buffer_start`, `buffer_total_size`, `processed_offset`, `new_data_size`, `is_command_active`
|
||||
|
||||
### 3. Granular Consumption Control
|
||||
- **Objective**: Verify handler can consume partial buffer data
|
||||
- **Method**: Process HTTP URCs line-by-line using `CONSUME_PARTIAL`
|
||||
- **Expected**: Each complete line is consumed individually, remaining data preserved
|
||||
|
||||
### 4. Multi-part Response Handling
|
||||
- **Objective**: Verify handling of chunked HTTP responses
|
||||
- **Method**: Process 9 HTTP chunks from ESP-AT server
|
||||
- **Expected**: All chunks processed correctly with proper offset tracking
|
||||
|
||||
### 5. Transfer Completion Detection
|
||||
- **Objective**: Verify detection of transfer completion message
|
||||
- **Method**: Search for "Transfer completed" in buffer
|
||||
- **Expected**: Completion detected and event group signaled
|
||||
|
||||
### 6. Command State Awareness
|
||||
- **Objective**: Verify handler knows command state
|
||||
- **Method**: Check `is_command_active` flag during processing
|
||||
- **Expected**: Flag correctly reflects command state
|
||||
|
||||
## Test Implementation
|
||||
|
||||
The test uses an ESP-AT device running an HTTP server that sends chunked responses. The enhanced URC handler processes each HTTP URC line individually and detects completion.
|
||||
|
||||
### Key Components
|
||||
|
||||
```cpp
|
||||
// Enhanced URC handler registration
|
||||
set_enhanced_urc(handle_enhanced_urc);
|
||||
|
||||
// Handler implementation
|
||||
static esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo& info)
|
||||
{
|
||||
// Process HTTP URCs with granular consumption control
|
||||
if (line.starts_with("+HTTPCGET:")) {
|
||||
// Consume this line only
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_PARTIAL, line_end + 1};
|
||||
}
|
||||
|
||||
// Check for completion
|
||||
if (buffer.find("Transfer completed") != std::string_view::npos) {
|
||||
xEventGroupSetBits(s_event_group, transfer_completed);
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_ALL, 0};
|
||||
}
|
||||
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
}
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
### Successful Test Run
|
||||
```
|
||||
I (908) urc_test: Starting Enhanced URC Test
|
||||
I (938) urc_test: Start HTTP server...(0)
|
||||
I (948) urc_test: HTTP GET...(43)
|
||||
I (1228) urc_test: HTTP URC: +HTTPCGET:27,=== Async Response #4 ===
|
||||
I (2778) urc_test: HTTP URC: +HTTPCGET:61,[1/9] [633135 ms] This is a simulated slow server response.
|
||||
I (4288) urc_test: HTTP URC: +HTTPCGET:71,[2/9] [634639 ms] Chunk 1: The ESP-AT HTTP server is demonstrating...
|
||||
I (5788) urc_test: HTTP URC: +HTTPCGET:73,[3/9] [636143 ms] Chunk 2: ...asynchronous chunked transfer encoding...
|
||||
I (7288) urc_test: HTTP URC: +HTTPCGET:72,[4/9] [637647 ms] Chunk 3: ...with artificial delays between chunks...
|
||||
I (8788) urc_test: HTTP URC: +HTTPCGET:74,[5/9] [639151 ms] Chunk 4: ...to simulate real-world network conditions.
|
||||
I (10288) urc_test: HTTP URC: +HTTPCGET:62,[6/9] [640655 ms] Chunk 5: Processing data... please wait...
|
||||
I (11788) urc_test: HTTP URC: +HTTPCGET:63,[7/9] [642159 ms] Chunk 6: Still processing... almost done...
|
||||
I (13288) urc_test: HTTP URC: +HTTPCGET:61,[8/9] [643663 ms] Chunk 7: Final chunk - transfer complete!
|
||||
I (14758) urc_test: HTTP URC: +HTTPCGET:43,[9/9] [645168 ms] === END OF RESPONSE ===
|
||||
I (15258) urc_test: Transfer completed detected in buffer!
|
||||
I (15298) urc_test: Enhanced URC test completed successfully!
|
||||
I (15308) urc_test: The enhanced URC handler successfully processed all HTTP chunks
|
||||
I (15308) urc_test: with granular buffer consumption control
|
||||
```
|
||||
|
||||
### Debug Output (with ESP_LOG_LEVEL_DEBUG)
|
||||
```
|
||||
D (958) urc_test: URC Buffer Info: total_size=43, processed_offset=0, new_data_size=43, command_active=false
|
||||
D (958) urc_test: Buffer content (first 43 chars): AT+HTTPCGET="http://127.0.0.1:8080/async"
|
||||
D (968) urc_test: Other data: AT+HTTPCGET="http://127.0.0.1:8080/async"
|
||||
D (1218) urc_test: URC Buffer Info: total_size=85, processed_offset=43, new_data_size=42, command_active=false
|
||||
D (1228) urc_test: Consuming 40 bytes (line_end=82, processed_offset=43)
|
||||
D (2778) urc_test: Consuming 76 bytes (line_end=158, processed_offset=83)
|
||||
```
|
||||
|
||||
### Failed Test (Timeout)
|
||||
```
|
||||
I (908) urc_test: Starting Enhanced URC Test
|
||||
I (948) urc_test: HTTP GET...(43)
|
||||
E (15385) urc_test: Enhanced URC test timed out
|
||||
I (15385) urc_test: Enhanced URC test done
|
||||
```
|
||||
|
||||
## Test Validation
|
||||
|
||||
### Success Criteria
|
||||
- All 9 HTTP chunks processed successfully
|
||||
- Transfer completion detected within 15 seconds
|
||||
- No buffer arithmetic errors (no negative `new_data_size`)
|
||||
- Proper consumption control (line-by-line processing)
|
||||
|
||||
### Failure Indicators
|
||||
- Test timeout (15 seconds)
|
||||
- Buffer arithmetic errors (negative `new_data_size`)
|
||||
- Missing HTTP chunks
|
||||
- Transfer completion not detected
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-AT device with HTTP server capability
|
||||
- UART connection configured
|
||||
- `CONFIG_ESP_MODEM_URC_HANDLER=y`
|
||||
- FreeRTOS event groups
|
||||
|
||||
## Build and Run
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
idf.py flash monitor
|
||||
```
|
||||
|
||||
## Comparison with Legacy URC
|
||||
|
||||
| Feature | Legacy URC | Enhanced URC |
|
||||
|---------|------------|--------------|
|
||||
| Buffer Consumption | All or nothing | Granular (partial) |
|
||||
| Buffer Visibility | None | Complete context |
|
||||
| Command State | Unknown | Known (`is_command_active`) |
|
||||
| Processing State | Unknown | Tracked (`processed_offset`) |
|
||||
| Multi-part Support | Limited | Full support |
|
||||
@@ -3,5 +3,5 @@ dependencies:
|
||||
## Required IDF version
|
||||
idf: ">=4.1.0"
|
||||
espressif/esp_modem:
|
||||
version: "^1.0.0"
|
||||
version: "^2"
|
||||
override_path: "../../../"
|
||||
|
||||
@@ -3,7 +3,26 @@
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file urc_test.cpp
|
||||
* @brief Enhanced URC (Unsolicited Result Code) Test
|
||||
*
|
||||
* This test demonstrates the new enhanced URC interface with buffer consumption control.
|
||||
* It tests the following features:
|
||||
*
|
||||
* 1. Enhanced URC Handler Registration: Uses set_enhanced_urc() instead of set_urc()
|
||||
* 2. Buffer Visibility: URC handler receives complete buffer information
|
||||
* 3. Granular Consumption Control: Handler can consume none, partial, or all buffer data
|
||||
* 4. Processing State Awareness: Handler knows what data is new vs. already processed
|
||||
* 5. Command State Awareness: Handler knows if a command is currently active
|
||||
*
|
||||
* The test works with ESP-AT HTTP server that sends chunked responses, demonstrating
|
||||
* how the enhanced URC handler can process multi-part responses with precise control
|
||||
* over buffer consumption.
|
||||
*/
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_netif.h"
|
||||
@@ -67,7 +86,7 @@ public:
|
||||
bool http_get(const std::string &url)
|
||||
{
|
||||
std::string command = "AT+HTTPCGET=\"" + url + "\"\r\n";
|
||||
set_urc(handle_urc);
|
||||
set_enhanced_urc(handle_enhanced_urc);
|
||||
auto ret = dte->write(esp_modem::DTE_Command(command));
|
||||
ESP_LOGI(TAG, "HTTP GET...(%d)", static_cast<int>(ret));
|
||||
return ret > 0;
|
||||
@@ -82,25 +101,88 @@ public:
|
||||
|
||||
static constexpr int transfer_completed = 1;
|
||||
private:
|
||||
static esp_modem::command_result handle_urc(uint8_t *data, size_t len)
|
||||
static esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo &info)
|
||||
{
|
||||
static int start_chunk = 0;
|
||||
static int end_chunk = 0;
|
||||
std::string_view chunk((const char*)data + start_chunk, len - start_chunk);
|
||||
int newline = chunk.find('\n');
|
||||
if (newline == std::string_view::npos) {
|
||||
end_chunk = len; // careful, this grows buffer usage
|
||||
printf(".");
|
||||
return esp_modem::command_result::TIMEOUT;
|
||||
// Log buffer information for debugging
|
||||
ESP_LOGD(TAG, "URC Buffer Info: total_size=%zu, processed_offset=%zu, new_data_size=%zu, command_active=%s",
|
||||
info.buffer_total_size, info.processed_offset, info.new_data_size,
|
||||
info.is_command_active ? "true" : "false");
|
||||
|
||||
// Debug: Show buffer content (first 200 chars)
|
||||
if (info.buffer_total_size > 0) {
|
||||
size_t debug_len = std::min(info.buffer_total_size, (size_t)200);
|
||||
ESP_LOGD(TAG, "Buffer content (first %zu chars): %.*s",
|
||||
debug_len, (int)debug_len, (const char*)info.buffer_start);
|
||||
}
|
||||
printf("%.*s\n", newline, (char*)data + start_chunk);
|
||||
start_chunk = end_chunk;
|
||||
// check for the last one
|
||||
constexpr char last_chunk[] = "Transfer completed";
|
||||
if (memmem(data, len, last_chunk, sizeof(last_chunk) - 1) != nullptr) {
|
||||
|
||||
// Create string view of the entire buffer for processing
|
||||
std::string_view buffer((const char*)info.buffer_start, info.buffer_total_size);
|
||||
|
||||
// First, check if we have the completion message anywhere in the buffer
|
||||
if (buffer.find("Transfer completed") != std::string_view::npos) {
|
||||
ESP_LOGI(TAG, "Transfer completed detected in buffer!");
|
||||
xEventGroupSetBits(s_event_group, transfer_completed);
|
||||
// Consume everything
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_ALL, 0};
|
||||
}
|
||||
return esp_modem::command_result::OK;
|
||||
|
||||
// Process from the last processed offset
|
||||
size_t search_start = info.processed_offset;
|
||||
|
||||
// Look for complete lines starting from the processed offset
|
||||
while (search_start < info.buffer_total_size) {
|
||||
size_t line_end = buffer.find('\n', search_start);
|
||||
|
||||
if (line_end == std::string_view::npos) {
|
||||
// No complete line found, wait for more data
|
||||
ESP_LOGD(TAG, "Waiting for more data... (search_start=%zu, total_size=%zu)",
|
||||
search_start, info.buffer_total_size);
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
}
|
||||
|
||||
// Found a complete line, process it
|
||||
std::string_view line = buffer.substr(search_start, line_end - search_start);
|
||||
|
||||
// Remove carriage return if present
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.remove_suffix(1);
|
||||
}
|
||||
|
||||
// Check if this is an HTTP URC
|
||||
if (line.starts_with("+HTTPCGET:")) {
|
||||
ESP_LOGI(TAG, "HTTP URC: %.*s", (int)line.length(), line.data());
|
||||
|
||||
// Check for transfer completion - look for "Transfer completed" anywhere in the line
|
||||
if (line.find("Transfer completed") != std::string_view::npos) {
|
||||
ESP_LOGI(TAG, "Transfer completed detected!");
|
||||
xEventGroupSetBits(s_event_group, transfer_completed);
|
||||
}
|
||||
|
||||
// Consume this line (including the newline)
|
||||
size_t consume_size = line_end + 1 - info.processed_offset;
|
||||
ESP_LOGD(TAG, "Consuming %zu bytes (line_end=%zu, processed_offset=%zu)",
|
||||
consume_size, line_end, info.processed_offset);
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_PARTIAL, consume_size};
|
||||
|
||||
} else if (line.starts_with("+HTTPCGET")) {
|
||||
// Partial HTTP URC, don't consume yet
|
||||
ESP_LOGD(TAG, "Partial HTTP URC: %.*s", (int)line.length(), line.data());
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
|
||||
} else if (!line.empty()) {
|
||||
// Other data, log and consume
|
||||
ESP_LOGD(TAG, "Other data: %.*s", (int)line.length(), line.data());
|
||||
size_t consume_size = line_end + 1 - info.processed_offset;
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_PARTIAL, consume_size};
|
||||
}
|
||||
|
||||
// Move to next line
|
||||
search_start = line_end + 1;
|
||||
}
|
||||
|
||||
// Processed all available data
|
||||
ESP_LOGD(TAG, "Processed all available data");
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,8 +213,8 @@ extern "C" void app_main(void)
|
||||
|
||||
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
|
||||
dte_config.dte_buffer_size = 1024;
|
||||
dte_config.uart_config.tx_io_num = 18;
|
||||
dte_config.uart_config.rx_io_num = 17;
|
||||
dte_config.uart_config.tx_io_num = 17;
|
||||
dte_config.uart_config.rx_io_num = 18;
|
||||
auto uart_dte = esp_modem::create_uart_dte(&dte_config);
|
||||
if (uart_dte == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create UART DTE");
|
||||
@@ -144,15 +226,24 @@ extern "C" void app_main(void)
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting Enhanced URC Test");
|
||||
ESP_LOGI(TAG, "This test demonstrates the new enhanced URC interface with buffer consumption control");
|
||||
|
||||
dce->start_http_server();
|
||||
|
||||
ESP_LOGI(TAG, "Sending HTTP GET request with enhanced URC handler");
|
||||
dce->http_get("http://127.0.0.1:8080/async");
|
||||
|
||||
EventBits_t bits = xEventGroupWaitBits(s_event_group, 1, pdTRUE, pdFALSE, pdMS_TO_TICKS(15000));
|
||||
if (bits & DCE::transfer_completed) {
|
||||
ESP_LOGI(TAG, "Request finished!");
|
||||
ESP_LOGI(TAG, "Enhanced URC test completed successfully!");
|
||||
ESP_LOGI(TAG, "The enhanced URC handler successfully processed all HTTP chunks");
|
||||
ESP_LOGI(TAG, "with granular buffer consumption control");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Enhanced URC test timed out");
|
||||
}
|
||||
|
||||
dce->sync();
|
||||
vEventGroupDelete(s_event_group);
|
||||
ESP_LOGI(TAG, "Done");
|
||||
ESP_LOGI(TAG, "Enhanced URC test done");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(mqtt_cxx): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py esp_mqtt_cxx
|
||||
tag_format: mqtt_cxx-v$version
|
||||
version: 0.4.0
|
||||
version: 0.5.0
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [0.5.0](https://github.com/espressif/esp-protocols/commits/mqtt_cxx-v0.5.0)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Implement simple unit tests ([f41c4a0a](https://github.com/espressif/esp-protocols/commit/f41c4a0a))
|
||||
- Fix to construct in two steps ([d979e1b3](https://github.com/espressif/esp-protocols/commit/d979e1b3), [#631](https://github.com/espressif/esp-protocols/issues/631))
|
||||
- Add explicit dependency on esp-mqtt if needed ([3d5e11b8](https://github.com/espressif/esp-protocols/commit/3d5e11b8))
|
||||
|
||||
## [0.4.0](https://github.com/espressif/esp-protocols/commits/mqtt_cxx-v0.4.0)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -163,7 +163,20 @@ Client::Client(esp_mqtt_client_config_t const &config) : handler(esp_mqtt_clien
|
||||
throw MQTTException(ESP_FAIL);
|
||||
};
|
||||
CHECK_THROW_SPECIFIC(esp_mqtt_client_register_event(handler.get(), MQTT_EVENT_ANY, mqtt_event_handler, this), mqtt::MQTTException);
|
||||
}
|
||||
|
||||
void Client::start()
|
||||
{
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
CHECK_THROW_SPECIFIC(esp_mqtt_client_start(handler.get()), mqtt::MQTTException);
|
||||
started = true;
|
||||
}
|
||||
|
||||
bool Client::is_started() const noexcept
|
||||
{
|
||||
return started;
|
||||
}
|
||||
|
||||
void Client::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) noexcept
|
||||
|
||||
@@ -83,8 +83,9 @@ extern "C" void app_main(void)
|
||||
idf::mqtt::Configuration config{};
|
||||
|
||||
MyClient client{broker, credentials, config};
|
||||
client.start();
|
||||
while (true) {
|
||||
constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS;
|
||||
vTaskDelay( xDelay );
|
||||
vTaskDelay(xDelay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ extern "C" void app_main(void)
|
||||
mqtt::Configuration config{};
|
||||
|
||||
MyClient client{broker, credentials, config};
|
||||
client.start();
|
||||
|
||||
while (true) {
|
||||
constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: "0.4.0"
|
||||
version: "0.5.0"
|
||||
description: C++ APIs for ESP-MQTT library
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_mqtt_cxx
|
||||
issues: https://github.com/espressif/esp-protocols/issues
|
||||
@@ -8,4 +8,8 @@ dependencies:
|
||||
espressif/esp-idf-cxx: "^1.0.0-beta"
|
||||
# Required IDF version
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
version: ">=5.0,<6.0"
|
||||
espressif/mqtt:
|
||||
rules:
|
||||
- if: idf_version >=6.0
|
||||
version: ^1.0.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -165,6 +165,19 @@ public:
|
||||
*/
|
||||
Client(const esp_mqtt_client_config_t &config);
|
||||
|
||||
/**
|
||||
* @brief Start the underlying esp-mqtt client
|
||||
*
|
||||
* Must be called after the derived class has finished constructing to avoid
|
||||
* events being dispatched to partially constructed objects.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief Check whether start() has been called
|
||||
*/
|
||||
[[nodiscard]] bool is_started() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Subscribe to topic
|
||||
*
|
||||
@@ -245,13 +258,13 @@ protected:
|
||||
*/
|
||||
virtual void on_error(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an disconnection event
|
||||
* @brief Called if there is a disconnection event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
virtual void on_disconnected(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an subscribed event
|
||||
* @brief Called if there is a subscribed event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
@@ -263,26 +276,26 @@ protected:
|
||||
*/
|
||||
virtual void on_unsubscribed(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an published event
|
||||
* @brief Called if there is a published event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
virtual void on_published(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an before connect event
|
||||
* @brief Called if there is a before connect event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
virtual void on_before_connect(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an connected event
|
||||
* @brief Called if there is a connected event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*
|
||||
*/
|
||||
virtual void on_connected(const esp_mqtt_event_handle_t event) = 0;
|
||||
/**
|
||||
* @brief Called if there is an data event
|
||||
* @brief Called if there is a data event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*
|
||||
@@ -292,5 +305,6 @@ private:
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id,
|
||||
void *event_data) noexcept;
|
||||
void init(const esp_mqtt_client_config_t &config);
|
||||
bool started{false};
|
||||
};
|
||||
} // namespace idf::mqtt
|
||||
|
||||
10
components/esp_mqtt_cxx/test/unit/CMakeLists.txt
Normal file
10
components/esp_mqtt_cxx/test/unit/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
if(${IDF_TARGET} STREQUAL "linux")
|
||||
set(EXTRA_COMPONENT_DIRS "../../../../common_components/linux_compat")
|
||||
set(COMPONENTS main)
|
||||
endif()
|
||||
|
||||
project(esp_mqtt_cxx_host_test)
|
||||
24
components/esp_mqtt_cxx/test/unit/README.md
Normal file
24
components/esp_mqtt_cxx/test/unit/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Test basic mqtt_cxx wrapper operations
|
||||
|
||||
## Warning: Linux target not supported, this test works only on target
|
||||
|
||||
## Example output
|
||||
```
|
||||
I (588) main_task: Started on CPU0
|
||||
I (598) main_task: Calling app_main()
|
||||
Randomness seeded to: 374196253
|
||||
I (608) mqtt_client_cpp: MQTT_EVENT_BEFORE_CONNECT
|
||||
E (618) esp-tls: [sock=54] delayed connect error: Connection reset by peer
|
||||
E (618) transport_base: Failed to open a new connection: 32772
|
||||
E (618) mqtt_client: Error transport connect
|
||||
I (618) mqtt_client_cpp: MQTT_EVENT_ERROR
|
||||
E (628) mqtt_client_cpp: Last error reported from esp-tls: 0x8004
|
||||
E (628) mqtt_client_cpp: Last error captured as transport's socket errno: 0x68
|
||||
I (638) mqtt_client_cpp: Last errno string (Connection reset by peer)
|
||||
I (648) mqtt_client_cpp: MQTT_EVENT_DISCONNECTED
|
||||
===============================================================================
|
||||
All tests passed (6 assertions in 1 test case)
|
||||
|
||||
Test passed!
|
||||
I (5658) main_task: Returned from app_main()
|
||||
```
|
||||
2
components/esp_mqtt_cxx/test/unit/main/CMakeLists.txt
Normal file
2
components/esp_mqtt_cxx/test/unit/main/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "test_esp_mqtt_cxx.cpp"
|
||||
WHOLE_ARCHIVE)
|
||||
5
components/esp_mqtt_cxx/test/unit/main/idf_component.yml
Normal file
5
components/esp_mqtt_cxx/test/unit/main/idf_component.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
dependencies:
|
||||
espressif/catch2: "^3.4.0"
|
||||
esp_mqtt_cxx:
|
||||
version: "*"
|
||||
override_path: '../../../'
|
||||
92
components/esp_mqtt_cxx/test/unit/main/test_esp_mqtt_cxx.cpp
Normal file
92
components/esp_mqtt_cxx/test/unit/main/test_esp_mqtt_cxx.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "catch2/catch_session.hpp"
|
||||
#include "catch2/catch_test_macros.hpp"
|
||||
#include "esp_mqtt.hpp"
|
||||
#include "esp_mqtt_client_config.hpp"
|
||||
#include "esp_netif.h"
|
||||
|
||||
namespace mqtt = idf::mqtt;
|
||||
|
||||
namespace {
|
||||
class TestClient final : public mqtt::Client {
|
||||
public:
|
||||
using mqtt::Client::Client;
|
||||
|
||||
bool constructed{false};
|
||||
bool before_connect{false};
|
||||
bool disconnected{false};
|
||||
|
||||
TestClient(const mqtt::BrokerConfiguration &broker, const mqtt::ClientCredentials &credentials, const mqtt::Configuration &config) :
|
||||
mqtt::Client(broker, credentials, config)
|
||||
{
|
||||
constructed = true;
|
||||
}
|
||||
|
||||
private:
|
||||
void on_connected(esp_mqtt_event_handle_t const event) override
|
||||
{
|
||||
CHECK(constructed);
|
||||
}
|
||||
|
||||
void on_data(esp_mqtt_event_handle_t const event) override
|
||||
{
|
||||
CHECK(constructed);
|
||||
}
|
||||
|
||||
void on_before_connect(esp_mqtt_event_handle_t const event) override
|
||||
{
|
||||
CHECK(constructed);
|
||||
before_connect = true;
|
||||
}
|
||||
|
||||
void on_disconnected(const esp_mqtt_event_handle_t event) override
|
||||
{
|
||||
CHECK(constructed);
|
||||
disconnected = true;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Client does not auto-start and can dispatch events after construction", "[esp_mqtt_cxx]")
|
||||
{
|
||||
mqtt::BrokerConfiguration broker{
|
||||
.address = mqtt::URI{std::string{"mqtt://127.0.0.1:1883"}},
|
||||
.security = mqtt::Insecure{}
|
||||
};
|
||||
mqtt::ClientCredentials credentials{};
|
||||
mqtt::Configuration config{};
|
||||
|
||||
TestClient client{broker, credentials, config};
|
||||
|
||||
REQUIRE(client.is_started() == false);
|
||||
|
||||
// start the client and expect disconnection (reset by peer)
|
||||
// since no server's running on this ESP32
|
||||
client.start();
|
||||
CHECK(client.is_started() == true);
|
||||
|
||||
CHECK(client.before_connect);
|
||||
usleep(10000);
|
||||
CHECK(client.disconnected);
|
||||
}
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
Catch::Session session;
|
||||
|
||||
int failures = session.run();
|
||||
if (failures > 0) {
|
||||
printf("TEST FAILED! number of failures=%d\n", failures);
|
||||
return;
|
||||
}
|
||||
printf("Test passed!\n");
|
||||
}
|
||||
4
components/esp_mqtt_cxx/test/unit/sdkconfig.defaults
Normal file
4
components/esp_mqtt_cxx/test/unit/sdkconfig.defaults
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_LWIP_PPP_SUPPORT=y
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
@@ -241,9 +241,29 @@ static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle
|
||||
return esp_event_loop_run(client->event_handle, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Abort the WebSocket connection and initiate reconnection or shutdown
|
||||
*
|
||||
* @param client WebSocket client handle
|
||||
* @param error_type Type of error that caused the abort
|
||||
*
|
||||
* @return ESP_OK on success, ESP_FAIL on failure
|
||||
*
|
||||
* @note PRECONDITION: client->lock MUST be held by the calling thread before calling this function.
|
||||
* This function does NOT acquire the lock itself. Calling without the lock will result in
|
||||
* race conditions and undefined behavior.
|
||||
*/
|
||||
static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client, esp_websocket_error_type_t error_type)
|
||||
{
|
||||
ESP_WS_CLIENT_STATE_CHECK(TAG, client, return ESP_FAIL);
|
||||
|
||||
|
||||
if (client->state == WEBSOCKET_STATE_CLOSING || client->state == WEBSOCKET_STATE_UNKNOW ||
|
||||
client->state == WEBSOCKET_STATE_WAIT_TIMEOUT) {
|
||||
ESP_LOGW(TAG, "Connection already closing/closed, skipping abort");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
esp_transport_close(client->transport);
|
||||
|
||||
if (!client->config->auto_reconnect) {
|
||||
@@ -256,6 +276,18 @@ static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_hand
|
||||
}
|
||||
client->error_handle.error_type = error_type;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DISCONNECTED, NULL, 0);
|
||||
|
||||
cleanup:
|
||||
if (client->errormsg_buffer) {
|
||||
ESP_LOGD(TAG, "Freeing error buffer (%d bytes) - Free heap before: %" PRIu32 " bytes",
|
||||
client->errormsg_size, esp_get_free_heap_size());
|
||||
free(client->errormsg_buffer);
|
||||
client->errormsg_buffer = NULL;
|
||||
client->errormsg_size = 0;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Disconnect - Free heap: %" PRIu32 " bytes", esp_get_free_heap_size());
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -453,6 +485,8 @@ static void destroy_and_free_resources(esp_websocket_client_handle_t client)
|
||||
esp_websocket_client_destroy_config(client);
|
||||
if (client->transport_list) {
|
||||
esp_transport_list_destroy(client->transport_list);
|
||||
client->transport_list = NULL;
|
||||
client->transport = NULL;
|
||||
}
|
||||
vSemaphoreDelete(client->lock);
|
||||
#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
|
||||
@@ -671,6 +705,11 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
|
||||
if (wlen < 0 || (wlen == 0 && need_write != 0)) {
|
||||
ret = wlen;
|
||||
esp_websocket_free_buf(client, true);
|
||||
|
||||
#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
|
||||
xSemaphoreGiveRecursive(client->tx_lock);
|
||||
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
|
||||
#endif
|
||||
esp_tls_error_handle_t error_handle = esp_transport_get_error_handle(client->transport);
|
||||
if (error_handle) {
|
||||
esp_websocket_client_error(client, "esp_transport_write() returned %d, transport_error=%s, tls_error_code=%i, tls_flags=%i, errno=%d",
|
||||
@@ -679,8 +718,16 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
|
||||
} else {
|
||||
esp_websocket_client_error(client, "esp_transport_write() returned %d, errno=%d", ret, errno);
|
||||
}
|
||||
ESP_LOGD(TAG, "Calling abort_connection due to send error");
|
||||
#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
return ret;
|
||||
#else
|
||||
// Already holding client->lock, safe to call
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
goto unlock_and_return;
|
||||
#endif
|
||||
}
|
||||
opcode = 0;
|
||||
widx += wlen;
|
||||
@@ -1019,7 +1066,6 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
|
||||
esp_websocket_free_buf(client, false);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen);
|
||||
|
||||
client->payload_offset += rlen;
|
||||
@@ -1030,15 +1076,35 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
|
||||
const char *data = (client->payload_len == 0) ? NULL : client->rx_buffer;
|
||||
ESP_LOGD(TAG, "Sending PONG with payload len=%d", client->payload_len);
|
||||
#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
|
||||
// Now acquire tx_lock with timeout (consistent with PING/CLOSE handling)
|
||||
if (xSemaphoreTakeRecursive(client->tx_lock, WEBSOCKET_TX_LOCK_TIMEOUT_MS) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Could not lock ws-client within %d timeout", WEBSOCKET_TX_LOCK_TIMEOUT_MS);
|
||||
ESP_LOGE(TAG, "Could not lock ws-client within %d timeout for PONG", WEBSOCKET_TX_LOCK_TIMEOUT_MS);
|
||||
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY); // Re-acquire client->lock before returning
|
||||
esp_websocket_free_buf(client, false);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Re-acquire client->lock to maintain consistency
|
||||
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
|
||||
|
||||
|
||||
// Another thread may have closed it while we didn't hold client->lock
|
||||
if (client->state == WEBSOCKET_STATE_CLOSING || client->state == WEBSOCKET_STATE_UNKNOW ||
|
||||
client->state == WEBSOCKET_STATE_WAIT_TIMEOUT || client->transport == NULL) {
|
||||
ESP_LOGW(TAG, "Transport closed while preparing PONG, skipping send");
|
||||
xSemaphoreGiveRecursive(client->tx_lock);
|
||||
esp_websocket_free_buf(client, false);
|
||||
return ESP_OK; // Caller expects client->lock to be held, which it is
|
||||
}
|
||||
|
||||
esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len,
|
||||
client->config->network_timeout_ms);
|
||||
#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
|
||||
xSemaphoreGiveRecursive(client->tx_lock);
|
||||
#else
|
||||
esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len,
|
||||
client->config->network_timeout_ms);
|
||||
#endif
|
||||
} else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) {
|
||||
client->wait_for_pong_resp = false;
|
||||
@@ -1136,7 +1202,29 @@ static void esp_websocket_client_task(void *pv)
|
||||
client->state = WEBSOCKET_STATE_CONNECTED;
|
||||
client->wait_for_pong_resp = false;
|
||||
client->error_handle.error_type = WEBSOCKET_ERROR_TYPE_NONE;
|
||||
client->payload_len = 0;
|
||||
client->payload_offset = 0;
|
||||
client->last_fin = false;
|
||||
client->last_opcode = WS_TRANSPORT_OPCODES_NONE;
|
||||
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CONNECTED, NULL, 0);
|
||||
|
||||
// Check if there is data pending to be read (e.g. piggybacked with handshake)
|
||||
if (esp_transport_poll_read(client->transport, 0) > 0) {
|
||||
esp_err_t recv_result = esp_websocket_client_recv(client);
|
||||
if (recv_result == ESP_OK) {
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
esp_event_loop_run(client->event_handle, 0);
|
||||
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
|
||||
if (client->state != WEBSOCKET_STATE_CONNECTED || client->transport == NULL) {
|
||||
ESP_LOGD(TAG, "Connection state changed during handshake data processing");
|
||||
break;
|
||||
}
|
||||
} else if (recv_result == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Error receive data during initial connection");
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WEBSOCKET_STATE_CONNECTED:
|
||||
if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) { // only send and check for PING
|
||||
@@ -1145,8 +1233,23 @@ static void esp_websocket_client_task(void *pv)
|
||||
client->ping_tick_ms = _tick_get_ms();
|
||||
ESP_LOGD(TAG, "Sending PING...");
|
||||
#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
|
||||
// Release client->lock first to avoid deadlock with send error path
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
|
||||
// Now acquire tx_lock with timeout (consistent with PONG handling)
|
||||
if (xSemaphoreTakeRecursive(client->tx_lock, WEBSOCKET_TX_LOCK_TIMEOUT_MS) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Could not lock ws-client within %d timeout", WEBSOCKET_TX_LOCK_TIMEOUT_MS);
|
||||
ESP_LOGE(TAG, "Could not lock ws-client within %d timeout for PING", WEBSOCKET_TX_LOCK_TIMEOUT_MS);
|
||||
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY); // Re-acquire client->lock before break
|
||||
break;
|
||||
}
|
||||
|
||||
// Re-acquire client->lock to check state
|
||||
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
|
||||
|
||||
// Another thread may have closed it while we didn't hold client->lock
|
||||
if (client->state != WEBSOCKET_STATE_CONNECTED || client->transport == NULL) {
|
||||
ESP_LOGW(TAG, "Transport closed while preparing PING, skipping send");
|
||||
xSemaphoreGiveRecursive(client->tx_lock);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
@@ -1182,8 +1285,23 @@ static void esp_websocket_client_task(void *pv)
|
||||
if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) {
|
||||
ESP_LOGD(TAG, "Closing initiated by the server, sending close frame");
|
||||
#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
|
||||
// Release client->lock first to avoid deadlock with send error path
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
|
||||
// Now acquire tx_lock with timeout (consistent with PONG/PING handling)
|
||||
if (xSemaphoreTakeRecursive(client->tx_lock, WEBSOCKET_TX_LOCK_TIMEOUT_MS) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Could not lock ws-client within %d timeout", WEBSOCKET_TX_LOCK_TIMEOUT_MS);
|
||||
ESP_LOGE(TAG, "Could not lock ws-client within %d timeout for CLOSE", WEBSOCKET_TX_LOCK_TIMEOUT_MS);
|
||||
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY); // Re-acquire client->lock before break
|
||||
break;
|
||||
}
|
||||
|
||||
// Re-acquire client->lock to check state
|
||||
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
|
||||
|
||||
// Another thread may have closed it while we didn't hold client->lock
|
||||
if (client->state != WEBSOCKET_STATE_CLOSING || client->transport == NULL) {
|
||||
ESP_LOGW(TAG, "Transport closed while preparing CLOSE frame, skipping send");
|
||||
xSemaphoreGiveRecursive(client->tx_lock);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
@@ -1202,6 +1320,7 @@ static void esp_websocket_client_task(void *pv)
|
||||
if (WEBSOCKET_STATE_CONNECTED == client->state) {
|
||||
read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms
|
||||
if (read_select < 0) {
|
||||
xSemaphoreTakeRecursive(client->lock, lock_timeout);
|
||||
esp_tls_error_handle_t error_handle = esp_transport_get_error_handle(client->transport);
|
||||
if (error_handle) {
|
||||
esp_websocket_client_error(client, "esp_transport_poll_read() returned %d, transport_error=%s, tls_error_code=%i, tls_flags=%i, errno=%d",
|
||||
@@ -1210,16 +1329,15 @@ static void esp_websocket_client_task(void *pv)
|
||||
} else {
|
||||
esp_websocket_client_error(client, "esp_transport_poll_read() returned %d, errno=%d", read_select, errno);
|
||||
}
|
||||
xSemaphoreTakeRecursive(client->lock, lock_timeout);
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
} else if (read_select > 0) {
|
||||
xSemaphoreTakeRecursive(client->lock, lock_timeout);
|
||||
if (esp_websocket_client_recv(client) == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Error receive data");
|
||||
xSemaphoreTakeRecursive(client->lock, lock_timeout);
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
}
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_poll_read().");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
CONFIG_IDF_TARGET_LINUX=n
|
||||
CONFIG_WEBSOCKET_URI_FROM_STDIN=n
|
||||
CONFIG_WEBSOCKET_URI_FROM_STRING=y
|
||||
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_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=y
|
||||
CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK=y
|
||||
CONFIG_ESP_WS_CLIENT_TX_LOCK_TIMEOUT_MS=2000
|
||||
@@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(lws): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py libwebsockets
|
||||
tag_format: lws-v$version
|
||||
version: 4.3.3
|
||||
version: 4.3.3~1
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## [4.3.3~1](https://github.com/espressif/esp-protocols/commits/lws-v4.3.3_1)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Remove lws support for IDF>=v6.0 ([b70cc3fc](https://github.com/espressif/esp-protocols/commit/b70cc3fc))
|
||||
- Update websocket Echo server (#894) ([318e41b3](https://github.com/espressif/esp-protocols/commit/318e41b3))
|
||||
- Adds missing license info ([7ea6879a](https://github.com/espressif/esp-protocols/commit/7ea6879a))
|
||||
|
||||
### Updated
|
||||
|
||||
- chore(lws): fixed formatting ([91e7e9fa](https://github.com/espressif/esp-protocols/commit/91e7e9fa))
|
||||
|
||||
## [4.3.3](https://github.com/espressif/esp-protocols/commits/lws-v4.3.3)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
version: "4.3.3"
|
||||
version: "4.3.3~1"
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/libwebsockets
|
||||
description: The component provides a simple ESP-IDF port of libwebsockets client.
|
||||
dependencies:
|
||||
idf: '>=5.3'
|
||||
idf: '>=5.3,<6.0'
|
||||
|
||||
@@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(mdns): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py mdns
|
||||
tag_format: mdns-v$version
|
||||
version: 1.9.0
|
||||
version: 1.9.1
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## [1.9.1](https://github.com/espressif/esp-protocols/commits/mdns-v1.9.1)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix to use tagged AFL image + minor format fix ([2b2f009a](https://github.com/espressif/esp-protocols/commit/2b2f009a))
|
||||
- Fix unused variable `dcst` warning for wifi-remote chips ([081eef88](https://github.com/espressif/esp-protocols/commit/081eef88))
|
||||
|
||||
## [1.9.0](https://github.com/espressif/esp-protocols/commits/mdns-v1.9.0)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -12,15 +12,18 @@ endif()
|
||||
|
||||
set(MDNS_MEMORY "mdns_mem_caps.c")
|
||||
|
||||
set(MDNS_CORE "mdns_responder.c" "mdns_receive.c" "mdns_utils.c" "mdns_debug.c" "mdns_browser.c" "mdns_send.c" "mdns_netif.c"
|
||||
"mdns_querier.c" "mdns_pcb.c" "mdns_service.c")
|
||||
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
if(${target} STREQUAL "linux")
|
||||
set(dependencies esp_netif_linux esp_event)
|
||||
set(private_dependencies esp_timer console esp_system)
|
||||
set(srcs "mdns.c" ${MDNS_MEMORY} ${MDNS_NETWORKING} ${MDNS_CONSOLE})
|
||||
set(srcs ${MDNS_CORE} ${MDNS_MEMORY} ${MDNS_NETWORKING} ${MDNS_CONSOLE})
|
||||
else()
|
||||
set(dependencies lwip console esp_netif)
|
||||
set(private_dependencies esp_timer esp_wifi)
|
||||
set(srcs "mdns.c" ${MDNS_MEMORY} ${MDNS_NETWORKING} ${MDNS_CONSOLE})
|
||||
set(srcs ${MDNS_CORE} ${MDNS_MEMORY} ${MDNS_NETWORKING} ${MDNS_CONSOLE})
|
||||
endif()
|
||||
|
||||
idf_component_register(
|
||||
|
||||
@@ -142,6 +142,20 @@ menu "mDNS"
|
||||
help
|
||||
Enable for the library to log received and sent mDNS packets to stdout.
|
||||
|
||||
config MDNS_DEBUG_USE_ESP_LOG
|
||||
bool "Route debug prints to ESP_LOG"
|
||||
depends on MDNS_ENABLE_DEBUG_PRINTS
|
||||
default y
|
||||
help
|
||||
Enable this option to route debug prints to ESP_LOG, which allows more flexibility.
|
||||
|
||||
config MDNS_DEBUG_BUFFER_SIZE
|
||||
int "Size of temporary buffer for debug prints"
|
||||
depends on MDNS_DEBUG_USE_ESP_LOG
|
||||
default 1024
|
||||
help
|
||||
This buffer is used to output mDNS packets in debug prints.
|
||||
|
||||
config MDNS_ENABLE_CONSOLE_CLI
|
||||
bool "Enable Command Line Interface on device console"
|
||||
default y
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: "1.9.0"
|
||||
version: "1.9.1"
|
||||
description: "Multicast UDP service used to provide local network service and host discovery."
|
||||
url: "https://github.com/espressif/esp-protocols/tree/master/components/mdns"
|
||||
issues: "https://github.com/espressif/esp-protocols/issues"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
656
components/mdns/mdns_browser.c
Normal file
656
components/mdns/mdns_browser.c
Normal file
@@ -0,0 +1,656 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "mdns_private.h"
|
||||
#include "mdns_browser.h"
|
||||
#include "mdns_mem_caps.h"
|
||||
#include "mdns_debug.h"
|
||||
#include "mdns_utils.h"
|
||||
#include "mdns_querier.h"
|
||||
#include "mdns_responder.h"
|
||||
#include "mdns_netif.h"
|
||||
#include "mdns_service.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "mdns_browser";
|
||||
|
||||
static mdns_browse_t *s_browse;
|
||||
|
||||
/**
|
||||
* @brief Browse action
|
||||
*/
|
||||
static esp_err_t send_browse_action(mdns_action_type_t type, mdns_browse_t *browse)
|
||||
{
|
||||
mdns_action_t *action = NULL;
|
||||
|
||||
action = (mdns_action_t *)mdns_mem_malloc(sizeof(mdns_action_t));
|
||||
|
||||
if (!action) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
action->type = type;
|
||||
action->data.browse_add.browse = browse;
|
||||
if (!mdns_priv_queue_action(action)) {
|
||||
mdns_mem_free(action);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free a browse item (Not free the list).
|
||||
*/
|
||||
static void browse_item_free(mdns_browse_t *browse)
|
||||
{
|
||||
mdns_mem_free(browse->service);
|
||||
mdns_mem_free(browse->proto);
|
||||
if (browse->result) {
|
||||
mdns_priv_query_results_free(browse->result);
|
||||
}
|
||||
mdns_mem_free(browse);
|
||||
}
|
||||
|
||||
static void browse_sync(mdns_browse_sync_t *browse_sync)
|
||||
{
|
||||
mdns_browse_t *browse = browse_sync->browse;
|
||||
mdns_browse_result_sync_t *sync_result = browse_sync->sync_result;
|
||||
while (sync_result) {
|
||||
mdns_result_t *result = sync_result->result;
|
||||
DBG_BROWSE_RESULTS(result, browse_sync->browse);
|
||||
browse->notifier(result);
|
||||
if (result->ttl == 0) {
|
||||
queueDetach(mdns_result_t, browse->result, result);
|
||||
// Just free current result
|
||||
result->next = NULL;
|
||||
mdns_query_results_free(result);
|
||||
}
|
||||
sync_result = sync_result->next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send PTR query packet to all available interfaces for browsing.
|
||||
*/
|
||||
static void browse_send(mdns_browse_t *browse, mdns_if_t interface)
|
||||
{
|
||||
// Using search once for sending the PTR query
|
||||
mdns_search_once_t search = {0};
|
||||
|
||||
search.instance = NULL;
|
||||
search.service = browse->service;
|
||||
search.proto = browse->proto;
|
||||
search.type = MDNS_TYPE_PTR;
|
||||
search.unicast = false;
|
||||
search.result = NULL;
|
||||
search.next = NULL;
|
||||
|
||||
for (uint8_t protocol_idx = 0; protocol_idx < MDNS_IP_PROTOCOL_MAX; protocol_idx++) {
|
||||
mdns_priv_query_send(&search, interface, (mdns_ip_protocol_t) protocol_idx);
|
||||
}
|
||||
}
|
||||
|
||||
void mdns_priv_browse_send_all(mdns_if_t mdns_if)
|
||||
{
|
||||
mdns_browse_t *browse = s_browse;
|
||||
while (browse) {
|
||||
browse_send(browse, mdns_if);
|
||||
browse = browse->next;
|
||||
}
|
||||
}
|
||||
|
||||
void mdns_priv_browse_free(void)
|
||||
{
|
||||
while (s_browse) {
|
||||
mdns_browse_t *b = s_browse;
|
||||
s_browse = s_browse->next;
|
||||
browse_item_free(b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mark browse as finished, remove and free it from browse chain
|
||||
*/
|
||||
static void browse_finish(mdns_browse_t *browse)
|
||||
{
|
||||
browse->state = BROWSE_OFF;
|
||||
mdns_browse_t *b = s_browse;
|
||||
mdns_browse_t *target_free = NULL;
|
||||
while (b) {
|
||||
if (strlen(b->service) == strlen(browse->service) && memcmp(b->service, browse->service, strlen(b->service)) == 0 &&
|
||||
strlen(b->proto) == strlen(browse->proto) && memcmp(b->proto, browse->proto, strlen(b->proto)) == 0) {
|
||||
target_free = b;
|
||||
b = b->next;
|
||||
queueDetach(mdns_browse_t, s_browse, target_free);
|
||||
browse_item_free(target_free);
|
||||
} else {
|
||||
b = b->next;
|
||||
}
|
||||
}
|
||||
browse_item_free(browse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate new browse structure
|
||||
*/
|
||||
static mdns_browse_t *browse_init(const char *service, const char *proto, mdns_browse_notify_t notifier)
|
||||
{
|
||||
mdns_browse_t *browse = (mdns_browse_t *)mdns_mem_malloc(sizeof(mdns_browse_t));
|
||||
|
||||
if (!browse) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
memset(browse, 0, sizeof(mdns_browse_t));
|
||||
|
||||
browse->state = BROWSE_INIT;
|
||||
if (!mdns_utils_str_null_or_empty(service)) {
|
||||
browse->service = mdns_mem_strndup(service, MDNS_NAME_BUF_LEN - 1);
|
||||
if (!browse->service) {
|
||||
browse_item_free(browse);
|
||||
HOOK_MALLOC_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mdns_utils_str_null_or_empty(proto)) {
|
||||
browse->proto = mdns_mem_strndup(proto, MDNS_NAME_BUF_LEN - 1);
|
||||
if (!browse->proto) {
|
||||
browse_item_free(browse);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
browse->notifier = notifier;
|
||||
return browse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add new browse to the browse chain
|
||||
*/
|
||||
static void browse_add(mdns_browse_t *browse)
|
||||
{
|
||||
browse->state = BROWSE_RUNNING;
|
||||
mdns_browse_t *queue = s_browse;
|
||||
bool found = false;
|
||||
// looking for this browse in active browses
|
||||
while (queue) {
|
||||
if (strlen(queue->service) == strlen(browse->service) && memcmp(queue->service, browse->service, strlen(queue->service)) == 0 &&
|
||||
strlen(queue->proto) == strlen(browse->proto) && memcmp(queue->proto, browse->proto, strlen(queue->proto)) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
queue = queue->next;
|
||||
}
|
||||
if (!found) {
|
||||
browse->next = s_browse;
|
||||
s_browse = browse;
|
||||
}
|
||||
for (uint8_t interface_idx = 0; interface_idx < MDNS_MAX_INTERFACES; interface_idx++) {
|
||||
browse_send(browse, (mdns_if_t) interface_idx);
|
||||
}
|
||||
if (found) {
|
||||
browse_item_free(browse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from packet parser to find matching running search
|
||||
*/
|
||||
mdns_browse_t *mdns_priv_browse_find(mdns_name_t *name, uint16_t type, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
mdns_browse_t *b = s_browse;
|
||||
// For browse, we only care about the SRV, TXT, A and AAAA
|
||||
if (type != MDNS_TYPE_SRV && type != MDNS_TYPE_A && type != MDNS_TYPE_AAAA && type != MDNS_TYPE_TXT) {
|
||||
return NULL;
|
||||
}
|
||||
mdns_result_t *r = NULL;
|
||||
while (b) {
|
||||
if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) {
|
||||
if (strcasecmp(name->service, b->service)
|
||||
|| strcasecmp(name->proto, b->proto)) {
|
||||
b = b->next;
|
||||
continue;
|
||||
}
|
||||
return b;
|
||||
} else if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA) {
|
||||
r = b->result;
|
||||
while (r) {
|
||||
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol && !mdns_utils_str_null_or_empty(r->hostname) && !strcasecmp(name->host, r->hostname)) {
|
||||
return b;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
b = b->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
static void sync_browse_result_link_free(mdns_browse_sync_t *browse_sync)
|
||||
{
|
||||
mdns_browse_result_sync_t *current = browse_sync->sync_result;
|
||||
mdns_browse_result_sync_t *need_free;
|
||||
while (current) {
|
||||
need_free = current;
|
||||
current = current->next;
|
||||
mdns_mem_free(need_free);
|
||||
}
|
||||
mdns_mem_free(browse_sync);
|
||||
}
|
||||
|
||||
void mdns_priv_browse_action(mdns_action_t *action, mdns_action_subtype_t type)
|
||||
{
|
||||
if (type == ACTION_RUN) {
|
||||
switch (action->type) {
|
||||
case ACTION_BROWSE_ADD:
|
||||
browse_add(action->data.browse_add.browse);
|
||||
break;
|
||||
case ACTION_BROWSE_SYNC:
|
||||
browse_sync(action->data.browse_sync.browse_sync);
|
||||
sync_browse_result_link_free(action->data.browse_sync.browse_sync);
|
||||
break;
|
||||
case ACTION_BROWSE_END:
|
||||
browse_finish(action->data.browse_add.browse);
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type == ACTION_CLEANUP) {
|
||||
switch (action->type) {
|
||||
case ACTION_BROWSE_ADD:
|
||||
//fallthrough
|
||||
case ACTION_BROWSE_END:
|
||||
browse_item_free(action->data.browse_add.browse);
|
||||
break;
|
||||
case ACTION_BROWSE_SYNC:
|
||||
sync_browse_result_link_free(action->data.browse_sync.browse_sync);
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add result to browse, only add when the result is a new one.
|
||||
*/
|
||||
static esp_err_t add_browse_result(mdns_browse_sync_t *sync_browse, mdns_result_t *r)
|
||||
{
|
||||
mdns_browse_result_sync_t *sync_r = sync_browse->sync_result;
|
||||
while (sync_r) {
|
||||
if (sync_r->result == r) {
|
||||
break;
|
||||
}
|
||||
sync_r = sync_r->next;
|
||||
}
|
||||
if (!sync_r) {
|
||||
// Do not find, need to add the result to the list
|
||||
mdns_browse_result_sync_t *new = (mdns_browse_result_sync_t *)mdns_mem_malloc(sizeof(mdns_browse_result_sync_t));
|
||||
|
||||
if (!new) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
new->result = r;
|
||||
new->next = sync_browse->sync_result;
|
||||
sync_browse->sync_result = new;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from parser to add A/AAAA data to search result
|
||||
*/
|
||||
void mdns_priv_browse_result_add_ip(mdns_browse_t *browse, const char *hostname, esp_ip_addr_t *ip,
|
||||
mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl, mdns_browse_sync_t *out_sync_browse)
|
||||
{
|
||||
if (out_sync_browse->browse == NULL) {
|
||||
return;
|
||||
} else {
|
||||
if (out_sync_browse->browse != browse) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mdns_result_t *r = NULL;
|
||||
mdns_ip_addr_t *r_a = NULL;
|
||||
if (browse) {
|
||||
r = browse->result;
|
||||
while (r) {
|
||||
if (r->ip_protocol == ip_protocol) {
|
||||
// Find the target result in browse result.
|
||||
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && !mdns_utils_str_null_or_empty(r->hostname) && !strcasecmp(hostname, r->hostname)) {
|
||||
r_a = r->addr;
|
||||
// Check if the address has already added in result.
|
||||
while (r_a) {
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (r_a->addr.type == ip->type && r_a->addr.type == ESP_IPADDR_TYPE_V4 && r_a->addr.u_addr.ip4.addr == ip->u_addr.ip4.addr) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (r_a->addr.type == ip->type && r_a->addr.type == ESP_IPADDR_TYPE_V6 && !memcmp(r_a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16)) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
r_a = r_a->next;
|
||||
}
|
||||
if (!r_a) {
|
||||
// The current IP is a new one, add it to the link list.
|
||||
mdns_ip_addr_t *a = NULL;
|
||||
a = mdns_priv_result_addr_create_ip(ip);
|
||||
if (!a) {
|
||||
return;
|
||||
}
|
||||
a->next = r->addr;
|
||||
r->addr = a;
|
||||
if (r->ttl != ttl) {
|
||||
if (r->ttl == 0) {
|
||||
r->ttl = ttl;
|
||||
} else {
|
||||
mdns_priv_query_update_result_ttl(r, ttl);
|
||||
}
|
||||
}
|
||||
if (add_browse_result(out_sync_browse, r) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_txt_item_in_list(mdns_txt_item_t txt, uint8_t txt_value_len, mdns_txt_item_t *txt_list, uint8_t *txt_value_len_list, size_t txt_count)
|
||||
{
|
||||
for (size_t i = 0; i < txt_count; i++) {
|
||||
if (strcmp(txt.key, txt_list[i].key) == 0) {
|
||||
if (txt_value_len == txt_value_len_list[i] && memcmp(txt.value, txt_list[i].value, txt_value_len) == 0) {
|
||||
return true;
|
||||
} else {
|
||||
// The key value is unique, so there is no need to continue searching.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from parser to add TXT data to search result
|
||||
*/
|
||||
void mdns_priv_browse_result_add_txt(mdns_browse_t *browse, const char *instance, const char *service, const char *proto,
|
||||
mdns_txt_item_t *txt, uint8_t *txt_value_len, size_t txt_count, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol,
|
||||
uint32_t ttl, mdns_browse_sync_t *out_sync_browse)
|
||||
{
|
||||
if (out_sync_browse->browse == NULL) {
|
||||
return;
|
||||
} else {
|
||||
if (out_sync_browse->browse != browse) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mdns_result_t *r = browse->result;
|
||||
while (r) {
|
||||
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol &&
|
||||
!mdns_utils_str_null_or_empty(r->instance_name) && !strcasecmp(instance, r->instance_name) &&
|
||||
!mdns_utils_str_null_or_empty(r->service_type) && !strcasecmp(service, r->service_type) &&
|
||||
!mdns_utils_str_null_or_empty(r->proto) && !strcasecmp(proto, r->proto)) {
|
||||
bool should_update = false;
|
||||
if (r->txt) {
|
||||
// Check if txt changed
|
||||
if (txt_count != r->txt_count) {
|
||||
should_update = true;
|
||||
} else {
|
||||
for (size_t txt_index = 0; txt_index < txt_count; txt_index++) {
|
||||
if (!is_txt_item_in_list(txt[txt_index], txt_value_len[txt_index], r->txt, r->txt_value_len, r->txt_count)) {
|
||||
should_update = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the result has a previous txt entry, we delete it and re-add.
|
||||
for (size_t i = 0; i < r->txt_count; i++) {
|
||||
mdns_mem_free((char *)(r->txt[i].key));
|
||||
mdns_mem_free((char *)(r->txt[i].value));
|
||||
}
|
||||
mdns_mem_free(r->txt);
|
||||
mdns_mem_free(r->txt_value_len);
|
||||
}
|
||||
r->txt = txt;
|
||||
r->txt_value_len = txt_value_len;
|
||||
r->txt_count = txt_count;
|
||||
if (r->ttl != ttl) {
|
||||
uint32_t previous_ttl = r->ttl;
|
||||
if (r->ttl == 0) {
|
||||
r->ttl = ttl;
|
||||
} else {
|
||||
mdns_priv_query_update_result_ttl(r, ttl);
|
||||
}
|
||||
if (previous_ttl != r->ttl) {
|
||||
should_update = true;
|
||||
}
|
||||
}
|
||||
if (should_update) {
|
||||
if (add_browse_result(out_sync_browse, r) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
r = (mdns_result_t *)mdns_mem_malloc(sizeof(mdns_result_t));
|
||||
if (!r) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
goto free_txt;
|
||||
}
|
||||
memset(r, 0, sizeof(mdns_result_t));
|
||||
r->instance_name = mdns_mem_strdup(instance);
|
||||
r->service_type = mdns_mem_strdup(service);
|
||||
r->proto = mdns_mem_strdup(proto);
|
||||
if (!r->instance_name || !r->service_type || !r->proto) {
|
||||
mdns_mem_free(r->instance_name);
|
||||
mdns_mem_free(r->service_type);
|
||||
mdns_mem_free(r->proto);
|
||||
mdns_mem_free(r);
|
||||
return;
|
||||
}
|
||||
r->txt = txt;
|
||||
r->txt_value_len = txt_value_len;
|
||||
r->txt_count = txt_count;
|
||||
r->esp_netif = mdns_priv_get_esp_netif(tcpip_if);
|
||||
r->ip_protocol = ip_protocol;
|
||||
r->ttl = ttl;
|
||||
r->next = browse->result;
|
||||
browse->result = r;
|
||||
add_browse_result(out_sync_browse, r);
|
||||
return;
|
||||
|
||||
free_txt:
|
||||
for (size_t i = 0; i < txt_count; i++) {
|
||||
mdns_mem_free((char *)(txt[i].key));
|
||||
mdns_mem_free((char *)(txt[i].value));
|
||||
}
|
||||
mdns_mem_free(txt);
|
||||
mdns_mem_free(txt_value_len);
|
||||
return;
|
||||
}
|
||||
|
||||
static esp_err_t copy_address_in_previous_result(mdns_result_t *result_list, mdns_result_t *r)
|
||||
{
|
||||
while (result_list) {
|
||||
if (!mdns_utils_str_null_or_empty(result_list->hostname) && !mdns_utils_str_null_or_empty(r->hostname) && !strcasecmp(result_list->hostname, r->hostname) &&
|
||||
result_list->ip_protocol == r->ip_protocol && result_list->addr && !r->addr) {
|
||||
// If there is a same hostname in previous result, we need to copy the address here.
|
||||
r->addr = mdns_utils_copy_address_list(result_list->addr);
|
||||
if (!r->addr) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
result_list = result_list->next;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from parser to add SRV data to search result
|
||||
*/
|
||||
void mdns_priv_browse_result_add_srv(mdns_browse_t *browse, const char *hostname, const char *instance, const char *service, const char *proto,
|
||||
uint16_t port, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl, mdns_browse_sync_t *out_sync_browse)
|
||||
{
|
||||
if (out_sync_browse->browse == NULL) {
|
||||
return;
|
||||
} else {
|
||||
if (out_sync_browse->browse != browse) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mdns_result_t *r = browse->result;
|
||||
while (r) {
|
||||
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol &&
|
||||
!mdns_utils_str_null_or_empty(r->instance_name) && !strcasecmp(instance, r->instance_name) &&
|
||||
!mdns_utils_str_null_or_empty(r->service_type) && !strcasecmp(service, r->service_type) &&
|
||||
!mdns_utils_str_null_or_empty(r->proto) && !strcasecmp(proto, r->proto)) {
|
||||
if (mdns_utils_str_null_or_empty(r->hostname) || strcasecmp(hostname, r->hostname)) {
|
||||
r->hostname = mdns_mem_strdup(hostname);
|
||||
r->port = port;
|
||||
if (!r->hostname) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return;
|
||||
}
|
||||
if (!r->addr) {
|
||||
esp_err_t err = copy_address_in_previous_result(browse->result, r);
|
||||
if (err == ESP_ERR_NO_MEM) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (add_browse_result(out_sync_browse, r) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (r->ttl != ttl) {
|
||||
uint32_t previous_ttl = r->ttl;
|
||||
if (r->ttl == 0) {
|
||||
r->ttl = ttl;
|
||||
} else {
|
||||
mdns_priv_query_update_result_ttl(r, ttl);
|
||||
}
|
||||
if (previous_ttl != r->ttl) {
|
||||
if (add_browse_result(out_sync_browse, r) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
r = (mdns_result_t *)mdns_mem_malloc(sizeof(mdns_result_t));
|
||||
if (!r) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return;
|
||||
}
|
||||
|
||||
memset(r, 0, sizeof(mdns_result_t));
|
||||
r->hostname = mdns_mem_strdup(hostname);
|
||||
r->instance_name = mdns_mem_strdup(instance);
|
||||
r->service_type = mdns_mem_strdup(service);
|
||||
r->proto = mdns_mem_strdup(proto);
|
||||
if (!r->hostname || !r->instance_name || !r->service_type || !r->proto) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
mdns_mem_free(r->hostname);
|
||||
mdns_mem_free(r->instance_name);
|
||||
mdns_mem_free(r->service_type);
|
||||
mdns_mem_free(r->proto);
|
||||
mdns_mem_free(r);
|
||||
return;
|
||||
}
|
||||
r->port = port;
|
||||
r->esp_netif = mdns_priv_get_esp_netif(tcpip_if);
|
||||
r->ip_protocol = ip_protocol;
|
||||
r->ttl = ttl;
|
||||
r->next = browse->result;
|
||||
browse->result = r;
|
||||
add_browse_result(out_sync_browse, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Browse sync result
|
||||
*/
|
||||
esp_err_t mdns_priv_browse_sync(mdns_browse_sync_t *browse_sync)
|
||||
{
|
||||
mdns_action_t *action = NULL;
|
||||
|
||||
action = (mdns_action_t *)mdns_mem_malloc(sizeof(mdns_action_t));
|
||||
if (!action) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
action->type = ACTION_BROWSE_SYNC;
|
||||
action->data.browse_sync.browse_sync = browse_sync;
|
||||
if (!mdns_priv_queue_action(action)) {
|
||||
mdns_mem_free(action);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @defgroup MDNS_PUBCLIC_API
|
||||
*/
|
||||
mdns_browse_t *mdns_browse_new(const char *service, const char *proto, mdns_browse_notify_t notifier)
|
||||
{
|
||||
mdns_browse_t *browse = NULL;
|
||||
|
||||
if (mdns_priv_is_server_init() || mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
browse = browse_init(service, proto, notifier);
|
||||
if (!browse) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (send_browse_action(ACTION_BROWSE_ADD, browse)) {
|
||||
browse_item_free(browse);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return browse;
|
||||
}
|
||||
|
||||
esp_err_t mdns_browse_delete(const char *service, const char *proto)
|
||||
{
|
||||
mdns_browse_t *browse = NULL;
|
||||
|
||||
if (!mdns_priv_is_server_init() || mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
browse = browse_init(service, proto, NULL);
|
||||
if (!browse) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (send_browse_action(ACTION_BROWSE_END, browse)) {
|
||||
browse_item_free(browse);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
416
components/mdns/mdns_debug.c
Normal file
416
components/mdns/mdns_debug.c
Normal file
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "mdns_private.h"
|
||||
#include "mdns_utils.h"
|
||||
|
||||
#ifdef CONFIG_MDNS_DEBUG_USE_ESP_LOG
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
#define MDNS_DBG_MAX_LINE CONFIG_MDNS_DEBUG_BUFFER_SIZE
|
||||
|
||||
static char s_mdns_dbg_buf[MDNS_DBG_MAX_LINE];
|
||||
static size_t s_mdns_dbg_pos = 0;
|
||||
|
||||
static void mdns_dbg_puts(const char *str)
|
||||
{
|
||||
ESP_LOGI("mdns", "%s", str);
|
||||
}
|
||||
|
||||
static inline void mdns_dbg_flush(void)
|
||||
{
|
||||
if (s_mdns_dbg_pos > 0) {
|
||||
s_mdns_dbg_buf[s_mdns_dbg_pos] = '\0';
|
||||
mdns_dbg_puts(s_mdns_dbg_buf);
|
||||
s_mdns_dbg_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void mdns_dbg_vprintf(const char *fmt, va_list args)
|
||||
{
|
||||
// Try to format directly into the buffer
|
||||
int len = vsnprintf(s_mdns_dbg_buf + s_mdns_dbg_pos,
|
||||
MDNS_DBG_MAX_LINE - s_mdns_dbg_pos,
|
||||
fmt, args);
|
||||
|
||||
if (len < 0) {
|
||||
return; // Error in formatting
|
||||
}
|
||||
|
||||
// Check if the entire formatted string fit in the buffer
|
||||
if (len < (MDNS_DBG_MAX_LINE - s_mdns_dbg_pos)) {
|
||||
// If it fit, just update the position
|
||||
s_mdns_dbg_pos += len;
|
||||
} else {
|
||||
// The formatted string was truncated because it didn't fit
|
||||
// First, flush what we have (the partial string)
|
||||
mdns_dbg_flush();
|
||||
|
||||
// Create a new va_list copy and try again with the full buffer
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
|
||||
// Format again with the entire buffer available
|
||||
len = vsnprintf(s_mdns_dbg_buf, MDNS_DBG_MAX_LINE - 1, fmt, args_copy);
|
||||
va_end(args_copy);
|
||||
|
||||
if (len < 0) {
|
||||
return; // Error
|
||||
}
|
||||
|
||||
// Check if content will be lost (true truncation)
|
||||
if (len >= MDNS_DBG_MAX_LINE - 1) {
|
||||
// This is when actual content will be lost - log a warning
|
||||
ESP_LOGW("mdns", "Message truncated: length (%d) exceeds buffer size (%d). Consider increasing CONFIG_MDNS_DEBUG_BUFFER_SIZE.",
|
||||
len, MDNS_DBG_MAX_LINE - 1);
|
||||
|
||||
// Display what we could fit, then flush and return
|
||||
s_mdns_dbg_pos = MDNS_DBG_MAX_LINE - 1;
|
||||
mdns_dbg_flush();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get here, the whole message fit this time
|
||||
s_mdns_dbg_pos = len;
|
||||
}
|
||||
|
||||
// If buffer is nearly full after this operation, flush it
|
||||
if (s_mdns_dbg_pos >= MDNS_DBG_MAX_LINE - 1) {
|
||||
mdns_dbg_flush();
|
||||
}
|
||||
}
|
||||
|
||||
static void mdns_dbg_printf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
mdns_dbg_vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#define dbg_printf(...) mdns_dbg_printf(__VA_ARGS__)
|
||||
#else
|
||||
#define dbg_printf(...) printf(__VA_ARGS__)
|
||||
#define mdns_dbg_flush()
|
||||
#endif
|
||||
|
||||
void static dbg_packet(const uint8_t *data, size_t len)
|
||||
{
|
||||
static mdns_name_t n;
|
||||
mdns_header_t header;
|
||||
const uint8_t *content = data + MDNS_HEAD_LEN;
|
||||
uint64_t t = xTaskGetTickCount() * portTICK_PERIOD_MS;
|
||||
mdns_name_t *name = &n;
|
||||
memset(name, 0, sizeof(mdns_name_t));
|
||||
|
||||
dbg_printf("Packet[%" PRIu64 "]: ", t);
|
||||
|
||||
header.id = mdns_utils_read_u16(data, MDNS_HEAD_ID_OFFSET);
|
||||
header.flags = mdns_utils_read_u16(data, MDNS_HEAD_FLAGS_OFFSET);
|
||||
header.questions = mdns_utils_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET);
|
||||
header.answers = mdns_utils_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET);
|
||||
header.servers = mdns_utils_read_u16(data, MDNS_HEAD_SERVERS_OFFSET);
|
||||
header.additional = mdns_utils_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET);
|
||||
|
||||
dbg_printf("%s",
|
||||
(header.flags == MDNS_FLAGS_QR_AUTHORITATIVE) ? "AUTHORITATIVE\n" :
|
||||
(header.flags == MDNS_FLAGS_DISTRIBUTED) ? "DISTRIBUTED\n" :
|
||||
(header.flags == 0) ? "\n" : " "
|
||||
);
|
||||
if (header.flags && header.flags != MDNS_FLAGS_QR_AUTHORITATIVE) {
|
||||
dbg_printf("0x%04X\n", header.flags);
|
||||
}
|
||||
|
||||
if (header.questions) {
|
||||
uint8_t qs = header.questions;
|
||||
|
||||
while (qs--) {
|
||||
content = mdns_utils_parse_fqdn(data, content, name, len);
|
||||
if (!content || content + MDNS_CLASS_OFFSET + 1 >= data + len) {
|
||||
header.answers = 0;
|
||||
header.additional = 0;
|
||||
header.servers = 0;
|
||||
dbg_printf("ERROR: parse header questions\n");
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t type = mdns_utils_read_u16(content, MDNS_TYPE_OFFSET);
|
||||
uint16_t mdns_class = mdns_utils_read_u16(content, MDNS_CLASS_OFFSET);
|
||||
bool unicast = !!(mdns_class & 0x8000);
|
||||
mdns_class &= 0x7FFF;
|
||||
content = content + 4;
|
||||
|
||||
dbg_printf(" Q: ");
|
||||
if (unicast) {
|
||||
dbg_printf("*U* ");
|
||||
}
|
||||
if (type == MDNS_TYPE_PTR) {
|
||||
dbg_printf("%s.%s%s.%s.%s. PTR ", name->host, name->sub ? "_sub." : "", name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_SRV) {
|
||||
dbg_printf("%s.%s%s.%s.%s. SRV ", name->host, name->sub ? "_sub." : "", name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_TXT) {
|
||||
dbg_printf("%s.%s%s.%s.%s. TXT ", name->host, name->sub ? "_sub." : "", name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_A) {
|
||||
dbg_printf("%s.%s. A ", name->host, name->domain);
|
||||
} else if (type == MDNS_TYPE_AAAA) {
|
||||
dbg_printf("%s.%s. AAAA ", name->host, name->domain);
|
||||
} else if (type == MDNS_TYPE_NSEC) {
|
||||
dbg_printf("%s.%s%s.%s.%s. NSEC ", name->host, name->sub ? "_sub." : "", name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_ANY) {
|
||||
dbg_printf("%s.%s%s.%s.%s. ANY ", name->host, name->sub ? "_sub." : "", name->service, name->proto, name->domain);
|
||||
} else {
|
||||
dbg_printf("%s.%s%s.%s.%s. %04X ", name->host, name->sub ? "_sub." : "", name->service, name->proto, name->domain, type);
|
||||
}
|
||||
|
||||
if (mdns_class == 0x0001) {
|
||||
dbg_printf("IN");
|
||||
} else {
|
||||
dbg_printf("%04X", mdns_class);
|
||||
}
|
||||
dbg_printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (header.answers || header.servers || header.additional) {
|
||||
uint16_t recordIndex = 0;
|
||||
|
||||
while (content < (data + len)) {
|
||||
|
||||
content = mdns_utils_parse_fqdn(data, content, name, len);
|
||||
if (!content) {
|
||||
dbg_printf("ERROR: parse mdns records\n");
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t type = mdns_utils_read_u16(content, MDNS_TYPE_OFFSET);
|
||||
uint16_t mdns_class = mdns_utils_read_u16(content, MDNS_CLASS_OFFSET);
|
||||
uint32_t ttl = mdns_utils_read_u32(content, MDNS_TTL_OFFSET);
|
||||
uint16_t data_len = mdns_utils_read_u16(content, MDNS_LEN_OFFSET);
|
||||
const uint8_t *data_ptr = content + MDNS_DATA_OFFSET;
|
||||
bool flush = !!(mdns_class & 0x8000);
|
||||
mdns_class &= 0x7FFF;
|
||||
|
||||
content = data_ptr + data_len;
|
||||
if (content > (data + len)) {
|
||||
dbg_printf("ERROR: content length overflow\n");
|
||||
break;
|
||||
}
|
||||
|
||||
mdns_parsed_record_type_t record_type = MDNS_ANSWER;
|
||||
|
||||
if (recordIndex >= (header.answers + header.servers)) {
|
||||
record_type = MDNS_EXTRA;
|
||||
} else if (recordIndex >= (header.answers)) {
|
||||
record_type = MDNS_NS;
|
||||
}
|
||||
recordIndex++;
|
||||
|
||||
if (record_type == MDNS_EXTRA) {
|
||||
dbg_printf(" X");
|
||||
} else if (record_type == MDNS_NS) {
|
||||
dbg_printf(" S");
|
||||
} else {
|
||||
dbg_printf(" A");
|
||||
}
|
||||
|
||||
if (type == MDNS_TYPE_PTR) {
|
||||
dbg_printf(": %s%s%s.%s.%s. PTR ", name->host, name->host[0] ? "." : "", name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_SRV) {
|
||||
dbg_printf(": %s.%s.%s.%s. SRV ", name->host, name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_TXT) {
|
||||
dbg_printf(": %s.%s.%s.%s. TXT ", name->host, name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_A) {
|
||||
dbg_printf(": %s.%s. A ", name->host, name->domain);
|
||||
} else if (type == MDNS_TYPE_AAAA) {
|
||||
dbg_printf(": %s.%s. AAAA ", name->host, name->domain);
|
||||
} else if (type == MDNS_TYPE_NSEC) {
|
||||
dbg_printf(": %s.%s.%s.%s. NSEC ", name->host, name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_ANY) {
|
||||
dbg_printf(": %s.%s.%s.%s. ANY ", name->host, name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_OPT) {
|
||||
dbg_printf(": . OPT ");
|
||||
} else {
|
||||
dbg_printf(": %s.%s.%s.%s. %04X ", name->host, name->service, name->proto, name->domain, type);
|
||||
}
|
||||
|
||||
if (mdns_class == 0x0001) {
|
||||
dbg_printf("IN ");
|
||||
} else {
|
||||
dbg_printf("%04X ", mdns_class);
|
||||
}
|
||||
if (flush) {
|
||||
dbg_printf("FLUSH ");
|
||||
}
|
||||
dbg_printf("%" PRIu32, ttl);
|
||||
dbg_printf("[%u] ", data_len);
|
||||
if (type == MDNS_TYPE_PTR) {
|
||||
if (!mdns_utils_parse_fqdn(data, data_ptr, name, len)) {
|
||||
dbg_printf("ERROR: parse PTR\n");
|
||||
continue;
|
||||
}
|
||||
dbg_printf("%s.%s.%s.%s.\n", name->host, name->service, name->proto, name->domain);
|
||||
} else if (type == MDNS_TYPE_SRV) {
|
||||
if (!mdns_utils_parse_fqdn(data, data_ptr + MDNS_SRV_FQDN_OFFSET, name, len)) {
|
||||
dbg_printf("ERROR: parse SRV\n");
|
||||
continue;
|
||||
}
|
||||
uint16_t priority = mdns_utils_read_u16(data_ptr, MDNS_SRV_PRIORITY_OFFSET);
|
||||
uint16_t weight = mdns_utils_read_u16(data_ptr, MDNS_SRV_WEIGHT_OFFSET);
|
||||
uint16_t port = mdns_utils_read_u16(data_ptr, MDNS_SRV_PORT_OFFSET);
|
||||
dbg_printf("%u %u %u %s.%s.\n", priority, weight, port, name->host, name->domain);
|
||||
} else if (type == MDNS_TYPE_TXT) {
|
||||
uint16_t i = 0, y;
|
||||
while (i < data_len) {
|
||||
uint8_t partLen = data_ptr[i++];
|
||||
if ((i + partLen) > data_len) {
|
||||
dbg_printf("ERROR: parse TXT\n");
|
||||
break;
|
||||
}
|
||||
char txt[partLen + 1];
|
||||
for (y = 0; y < partLen; y++) {
|
||||
char d = data_ptr[i++];
|
||||
txt[y] = d;
|
||||
}
|
||||
txt[partLen] = 0;
|
||||
dbg_printf("%s", txt);
|
||||
if (i < data_len) {
|
||||
dbg_printf("; ");
|
||||
}
|
||||
}
|
||||
dbg_printf("\n");
|
||||
} else if (type == MDNS_TYPE_AAAA) {
|
||||
esp_ip6_addr_t ip6;
|
||||
memcpy(&ip6, data_ptr, sizeof(esp_ip6_addr_t));
|
||||
dbg_printf(IPV6STR "\n", IPV62STR(ip6));
|
||||
} else if (type == MDNS_TYPE_A) {
|
||||
esp_ip4_addr_t ip;
|
||||
memcpy(&ip, data_ptr, sizeof(esp_ip4_addr_t));
|
||||
dbg_printf(IPSTR "\n", IP2STR(&ip));
|
||||
} else if (type == MDNS_TYPE_NSEC) {
|
||||
const uint8_t *old_ptr = data_ptr;
|
||||
const uint8_t *new_ptr = mdns_utils_parse_fqdn(data, data_ptr, name, len);
|
||||
if (new_ptr) {
|
||||
dbg_printf("%s.%s.%s.%s. ", name->host, name->service, name->proto, name->domain);
|
||||
size_t diff = new_ptr - old_ptr;
|
||||
data_len -= diff;
|
||||
data_ptr = new_ptr;
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < data_len; i++) {
|
||||
dbg_printf(" %02x", data_ptr[i]);
|
||||
}
|
||||
dbg_printf("\n");
|
||||
} else if (type == MDNS_TYPE_OPT) {
|
||||
uint16_t opCode = mdns_utils_read_u16(data_ptr, 0);
|
||||
uint16_t opLen = mdns_utils_read_u16(data_ptr, 2);
|
||||
dbg_printf(" Code: %04x Data[%u]:", opCode, opLen);
|
||||
size_t i;
|
||||
for (i = 4; i < data_len; i++) {
|
||||
dbg_printf(" %02x", data_ptr[i]);
|
||||
}
|
||||
dbg_printf("\n");
|
||||
} else {
|
||||
size_t i;
|
||||
for (i = 0; i < data_len; i++) {
|
||||
dbg_printf(" %02x", data_ptr[i]);
|
||||
}
|
||||
dbg_printf("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
mdns_dbg_flush();
|
||||
}
|
||||
|
||||
void mdns_debug_tx_packet(mdns_tx_packet_t *p, uint8_t packet[MDNS_MAX_PACKET_SIZE], uint16_t index)
|
||||
{
|
||||
dbg_printf("\nTX[%lu][%lu]: ", (unsigned long)p->tcpip_if, (unsigned long)p->ip_protocol);
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (p->dst.type == ESP_IPADDR_TYPE_V4) {
|
||||
dbg_printf("To: " IPSTR ":%u, ", IP2STR(&p->dst.u_addr.ip4), p->port);
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (p->dst.type == ESP_IPADDR_TYPE_V6) {
|
||||
dbg_printf("To: " IPV6STR ":%u, ", IPV62STR(p->dst.u_addr.ip6), p->port);
|
||||
}
|
||||
#endif
|
||||
dbg_packet(packet, index);
|
||||
mdns_dbg_flush();
|
||||
}
|
||||
|
||||
void mdns_debug_rx_packet(mdns_rx_packet_t *packet, const uint8_t* data, uint16_t len)
|
||||
{
|
||||
dbg_printf("\nRX[%lu][%lu]: ", (unsigned long)packet->tcpip_if, (unsigned long)packet->ip_protocol);
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (packet->src.type == ESP_IPADDR_TYPE_V4) {
|
||||
dbg_printf("From: " IPSTR ":%u, To: " IPSTR ", ", IP2STR(&packet->src.u_addr.ip4), packet->src_port, IP2STR(&packet->dest.u_addr.ip4));
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (packet->src.type == ESP_IPADDR_TYPE_V6) {
|
||||
dbg_printf("From: " IPV6STR ":%u, To: " IPV6STR ", ", IPV62STR(packet->src.u_addr.ip6), packet->src_port, IPV62STR(packet->dest.u_addr.ip6));
|
||||
}
|
||||
#endif
|
||||
dbg_packet(data, len);
|
||||
mdns_dbg_flush();
|
||||
}
|
||||
|
||||
static void dbg_printf_result(mdns_result_t *r_t)
|
||||
{
|
||||
mdns_ip_addr_t *r_a = NULL;
|
||||
int addr_count = 0;
|
||||
dbg_printf("result esp_netif: %p\n", r_t->esp_netif);
|
||||
dbg_printf("result ip_protocol: %d\n", r_t->ip_protocol);
|
||||
dbg_printf("result hostname: %s\n", mdns_utils_str_null_or_empty(r_t->hostname) ? "NULL" : r_t->hostname);
|
||||
dbg_printf("result instance_name: %s\n", mdns_utils_str_null_or_empty(r_t->instance_name) ? "NULL" : r_t->instance_name);
|
||||
dbg_printf("result service_type: %s\n", mdns_utils_str_null_or_empty(r_t->service_type) ? "NULL" : r_t->service_type);
|
||||
dbg_printf("result proto: %s\n", mdns_utils_str_null_or_empty(r_t->proto) ? "NULL" : r_t->proto);
|
||||
dbg_printf("result port: %d\n", r_t->port);
|
||||
dbg_printf("result ttl: %" PRIu32 "\n", r_t->ttl);
|
||||
for (int i = 0; i < r_t->txt_count; i++) {
|
||||
dbg_printf("result txt item%d, key: %s, value: %s\n", i, r_t->txt[i].key, r_t->txt[i].value);
|
||||
}
|
||||
r_a = r_t->addr;
|
||||
while (r_a) {
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (r_a->addr.type == ESP_IPADDR_TYPE_V4) {
|
||||
dbg_printf("Addr%d: " IPSTR "\n", addr_count++, IP2STR(&r_a->addr.u_addr.ip4));
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (r_a->addr.type == ESP_IPADDR_TYPE_V6) {
|
||||
dbg_printf("Addr%d: " IPV6STR "\n", addr_count++, IPV62STR(r_a->addr.u_addr.ip6));
|
||||
}
|
||||
#endif
|
||||
r_a = r_a->next;
|
||||
}
|
||||
mdns_dbg_flush();
|
||||
}
|
||||
|
||||
void mdns_debug_printf_browse_result(mdns_result_t *r_t, mdns_browse_t *b_t)
|
||||
{
|
||||
dbg_printf("----------------sync browse %s.%s result---------------\n", b_t->service, b_t->proto);
|
||||
dbg_printf("browse pointer: %p\n", b_t);
|
||||
dbg_printf_result(r_t);
|
||||
mdns_dbg_flush();
|
||||
}
|
||||
|
||||
void mdns_debug_printf_browse_result_all(mdns_result_t *r_t)
|
||||
{
|
||||
int count = 0;
|
||||
while (r_t) {
|
||||
dbg_printf("----------------result %d---------------\n", count++);
|
||||
dbg_printf_result(r_t);
|
||||
r_t = r_t->next;
|
||||
}
|
||||
mdns_dbg_flush();
|
||||
}
|
||||
51
components/mdns/mdns_diagram.md
Normal file
51
components/mdns/mdns_diagram.md
Normal file
@@ -0,0 +1,51 @@
|
||||
```mermaid
|
||||
graph TB
|
||||
%% Housekeeping modules at the top
|
||||
subgraph Housekeeping [Support Modules]
|
||||
Service[mdns_service.c]
|
||||
Utils[mdns_utils.c]
|
||||
MemCaps[mdns_mem_caps.c]
|
||||
Debug[mdns_debug.c]
|
||||
end
|
||||
|
||||
%% Switch to LR direction for the main flow
|
||||
subgraph MainFlow [Main Data Flow]
|
||||
direction LR
|
||||
|
||||
%% Network on left side
|
||||
Network((Network)) <--> Networking
|
||||
|
||||
%% Networking layer
|
||||
subgraph Networking [Networking Layer]
|
||||
LWIP[mdns_networking_lwip.c]
|
||||
Socket[mdns_networking_socket.c]
|
||||
end
|
||||
|
||||
%% Traffic flow
|
||||
Networking --> |Incoming| Receive[mdns_receive.c]
|
||||
Send[mdns_send.c] --> |Outgoing| Networking
|
||||
|
||||
%% Core MDNS components
|
||||
Receive --> Responder[mdns_responder.c]
|
||||
Receive --> Browser[mdns_browser.c]
|
||||
Receive --> Querier[mdns_querier.c]
|
||||
|
||||
Responder --> Send
|
||||
Browser --> Send
|
||||
Querier --> Send
|
||||
|
||||
PCB[mdns_pcb.c] --> Send
|
||||
NetIF[mdns_netif.c]
|
||||
|
||||
%% Users on the right side, aligned vertically
|
||||
Responder --> Advertise((User: Advertising))
|
||||
Querier --> Search((User: Searching))
|
||||
Browser --> Browse((User: Browsing))
|
||||
end
|
||||
|
||||
%% Style user nodes
|
||||
style Advertise fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Search fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Browse fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Housekeeping fill:#e6f7ff,stroke:#333,stroke-width:1px
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user