mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-12-23 15:18:08 +01:00
Compare commits
75 Commits
websocket-
...
mqtt_cxx-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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,4 @@ 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
|
||||
|
||||
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())
|
||||
@@ -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: "../../../"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
433
components/mdns/mdns_netif.c
Normal file
433
components/mdns/mdns_netif.c
Normal file
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_event.h"
|
||||
#include "mdns.h"
|
||||
#include "mdns_private.h"
|
||||
#include "mdns_mem_caps.h"
|
||||
#include "mdns_utils.h"
|
||||
#include "mdns_debug.h"
|
||||
#include "mdns_browser.h"
|
||||
#include "mdns_netif.h"
|
||||
#include "mdns_pcb.h"
|
||||
#include "mdns_responder.h"
|
||||
#include "mdns_service.h"
|
||||
|
||||
static const char *TAG = "mdns_netif";
|
||||
|
||||
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
#include "esp_eth.h"
|
||||
#endif
|
||||
|
||||
#if ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(5, 1, 0)
|
||||
#define MDNS_ESP_WIFI_ENABLED CONFIG_SOC_WIFI_SUPPORTED
|
||||
#else
|
||||
#define MDNS_ESP_WIFI_ENABLED (CONFIG_ESP_WIFI_ENABLED || CONFIG_ESP_WIFI_REMOTE_ENABLED)
|
||||
#endif
|
||||
|
||||
#if MDNS_ESP_WIFI_ENABLED && (CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP)
|
||||
#include "esp_wifi.h"
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MDNS_IF_STA = 0,
|
||||
MDNS_IF_AP = 1,
|
||||
MDNS_IF_ETH = 2,
|
||||
} mdns_predef_if_t;
|
||||
|
||||
typedef struct mdns_interfaces mdns_interfaces_t;
|
||||
|
||||
struct mdns_interfaces {
|
||||
const bool predefined;
|
||||
esp_netif_t *netif;
|
||||
const mdns_predef_if_t predef_if;
|
||||
mdns_if_t duplicate;
|
||||
};
|
||||
|
||||
/*
|
||||
* @brief Internal collection of mdns supported interfaces
|
||||
*
|
||||
*/
|
||||
static mdns_interfaces_t s_esp_netifs[MDNS_MAX_INTERFACES] = {
|
||||
#if CONFIG_MDNS_PREDEF_NETIF_STA
|
||||
{ .predefined = true, .netif = NULL, .predef_if = MDNS_IF_STA, .duplicate = MDNS_MAX_INTERFACES },
|
||||
#endif
|
||||
#if CONFIG_MDNS_PREDEF_NETIF_AP
|
||||
{ .predefined = true, .netif = NULL, .predef_if = MDNS_IF_AP, .duplicate = MDNS_MAX_INTERFACES },
|
||||
#endif
|
||||
#if CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
{ .predefined = true, .netif = NULL, .predef_if = MDNS_IF_ETH, .duplicate = MDNS_MAX_INTERFACES },
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper to get either ETH or STA if the other is provided
|
||||
* Used when two interfaces are on the same subnet
|
||||
*/
|
||||
mdns_if_t mdns_priv_netif_get_other_interface(mdns_if_t tcpip_if)
|
||||
{
|
||||
if (tcpip_if < MDNS_MAX_INTERFACES) {
|
||||
return s_esp_netifs[tcpip_if].duplicate;
|
||||
}
|
||||
return MDNS_MAX_INTERFACES;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert Predefined interface to the netif id from the internal netif list
|
||||
* @param predef_if Predefined interface enum
|
||||
* @return Ordinal number of internal list of mdns network interface.
|
||||
* Returns MDNS_MAX_INTERFACES if the predefined interface wasn't found in the list
|
||||
*/
|
||||
static mdns_if_t mdns_if_from_preset(mdns_predef_if_t predef_if)
|
||||
{
|
||||
for (int i = 0; i < MDNS_MAX_INTERFACES; ++i) {
|
||||
if (s_esp_netifs[i].predefined && s_esp_netifs[i].predef_if == predef_if) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MDNS_MAX_INTERFACES;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert Predefined interface to esp-netif handle
|
||||
* @param predef_if Predefined interface enum
|
||||
* @return esp_netif pointer from system list of network interfaces
|
||||
*/
|
||||
static inline esp_netif_t *netif_from_preset(mdns_predef_if_t predef_if)
|
||||
{
|
||||
switch (predef_if) {
|
||||
case MDNS_IF_STA:
|
||||
return esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
case MDNS_IF_AP:
|
||||
return esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
|
||||
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
case MDNS_IF_ETH:
|
||||
return esp_netif_get_handle_from_ifkey("ETH_DEF");
|
||||
#endif
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
esp_netif_t *mdns_priv_get_esp_netif(mdns_if_t tcpip_if)
|
||||
{
|
||||
if (tcpip_if < MDNS_MAX_INTERFACES) {
|
||||
if (s_esp_netifs[tcpip_if].netif == NULL && s_esp_netifs[tcpip_if].predefined) {
|
||||
// If the local copy is NULL and this netif is predefined -> we can find it in the global netif list
|
||||
s_esp_netifs[tcpip_if].netif = netif_from_preset(s_esp_netifs[tcpip_if].predef_if);
|
||||
// failing to find it means that the netif is *not* available -> return NULL
|
||||
}
|
||||
return s_esp_netifs[tcpip_if].netif;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Clean internal mdns interface's pointer
|
||||
*/
|
||||
void mdns_priv_netif_disable(mdns_if_t tcpip_if)
|
||||
{
|
||||
if (tcpip_if < MDNS_MAX_INTERFACES) {
|
||||
s_esp_netifs[tcpip_if].netif = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Convert esp-netif handle to mdns if
|
||||
*/
|
||||
static mdns_if_t get_if_from_netif(esp_netif_t *esp_netif)
|
||||
{
|
||||
for (int i = 0; i < MDNS_MAX_INTERFACES; ++i) {
|
||||
// The predefined netifs in the static array are NULL when firstly calling this function
|
||||
// if IPv4 is disabled. Set these netifs here.
|
||||
if (s_esp_netifs[i].netif == NULL && s_esp_netifs[i].predefined) {
|
||||
s_esp_netifs[i].netif = netif_from_preset(s_esp_netifs[i].predef_if);
|
||||
}
|
||||
if (esp_netif == s_esp_netifs[i].netif) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MDNS_MAX_INTERFACES;
|
||||
}
|
||||
|
||||
static esp_err_t post_custom_action(mdns_if_t mdns_if, mdns_event_actions_t event_action)
|
||||
{
|
||||
if (!mdns_priv_is_server_init() || mdns_if >= MDNS_MAX_INTERFACES) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
mdns_action_t *action = (mdns_action_t *)mdns_mem_calloc(1, sizeof(mdns_action_t));
|
||||
if (!action) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
action->type = ACTION_SYSTEM_EVENT;
|
||||
action->data.sys_event.event_action = event_action;
|
||||
action->data.sys_event.interface = mdns_if;
|
||||
|
||||
if (!mdns_priv_queue_action(action)) {
|
||||
mdns_mem_free(action);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dispatch interface changes based on system events
|
||||
*/
|
||||
static inline void post_disable_pcb(mdns_predef_if_t preset_if, mdns_ip_protocol_t protocol)
|
||||
{
|
||||
post_custom_action(mdns_if_from_preset(preset_if),
|
||||
protocol == MDNS_IP_PROTOCOL_V4 ? MDNS_EVENT_DISABLE_IP4 : MDNS_EVENT_DISABLE_IP6);
|
||||
}
|
||||
|
||||
static inline void post_enable_pcb(mdns_predef_if_t preset_if, mdns_ip_protocol_t protocol)
|
||||
{
|
||||
post_custom_action(mdns_if_from_preset(preset_if),
|
||||
protocol == MDNS_IP_PROTOCOL_V4 ? MDNS_EVENT_ENABLE_IP4 : MDNS_EVENT_ENABLE_IP6);
|
||||
}
|
||||
|
||||
static inline void post_announce_pcb(mdns_predef_if_t preset_if, mdns_ip_protocol_t protocol)
|
||||
{
|
||||
post_custom_action(mdns_if_from_preset(preset_if),
|
||||
protocol == MDNS_IP_PROTOCOL_V4 ? MDNS_EVENT_ANNOUNCE_IP4 : MDNS_EVENT_ANNOUNCE_IP6);
|
||||
}
|
||||
|
||||
#if CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP || CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
static void handle_system_event_for_preset(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
if (!mdns_priv_is_server_init()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if MDNS_ESP_WIFI_ENABLED && (CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP)
|
||||
if (event_base == WIFI_EVENT) {
|
||||
esp_netif_dhcp_status_t dcst;
|
||||
switch (event_id) {
|
||||
case WIFI_EVENT_STA_CONNECTED:
|
||||
if (!esp_netif_dhcpc_get_status(netif_from_preset(MDNS_IF_STA), &dcst)) {
|
||||
if (dcst == ESP_NETIF_DHCP_STOPPED) {
|
||||
post_enable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V4);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WIFI_EVENT_STA_DISCONNECTED:
|
||||
post_disable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V4);
|
||||
post_disable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V6);
|
||||
break;
|
||||
case WIFI_EVENT_AP_START:
|
||||
post_enable_pcb(MDNS_IF_AP, MDNS_IP_PROTOCOL_V4);
|
||||
break;
|
||||
case WIFI_EVENT_AP_STOP:
|
||||
post_disable_pcb(MDNS_IF_AP, MDNS_IP_PROTOCOL_V4);
|
||||
post_disable_pcb(MDNS_IF_AP, MDNS_IP_PROTOCOL_V6);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
if (event_base == ETH_EVENT) {
|
||||
esp_netif_dhcp_status_t dcst;
|
||||
switch (event_id) {
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
if (!esp_netif_dhcpc_get_status(netif_from_preset(MDNS_IF_ETH), &dcst)) {
|
||||
if (dcst == ESP_NETIF_DHCP_STOPPED) {
|
||||
post_enable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V4);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
post_disable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V4);
|
||||
post_disable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V6);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if (event_base == IP_EVENT) {
|
||||
switch (event_id) {
|
||||
case IP_EVENT_STA_GOT_IP:
|
||||
post_enable_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V4);
|
||||
post_announce_pcb(MDNS_IF_STA, MDNS_IP_PROTOCOL_V6);
|
||||
break;
|
||||
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
case IP_EVENT_ETH_GOT_IP:
|
||||
post_enable_pcb(MDNS_IF_ETH, MDNS_IP_PROTOCOL_V4);
|
||||
break;
|
||||
#endif
|
||||
case IP_EVENT_GOT_IP6: {
|
||||
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
|
||||
mdns_if_t mdns_if = get_if_from_netif(event->esp_netif);
|
||||
if (mdns_if >= MDNS_MAX_INTERFACES) {
|
||||
return;
|
||||
}
|
||||
post_enable_pcb(mdns_if, MDNS_IP_PROTOCOL_V6);
|
||||
post_announce_pcb(mdns_if, MDNS_IP_PROTOCOL_V4);
|
||||
mdns_priv_browse_send_all(mdns_if);
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP || CONFIG_MDNS_PREDEF_NETIF_ETH */
|
||||
|
||||
static inline void set_default_duplicated_interfaces(void)
|
||||
{
|
||||
mdns_if_t wifi_sta_if = MDNS_MAX_INTERFACES;
|
||||
mdns_if_t eth_if = MDNS_MAX_INTERFACES;
|
||||
for (mdns_if_t i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
if (s_esp_netifs[i].predefined && s_esp_netifs[i].predef_if == MDNS_IF_STA) {
|
||||
wifi_sta_if = i;
|
||||
}
|
||||
if (s_esp_netifs[i].predefined && s_esp_netifs[i].predef_if == MDNS_IF_ETH) {
|
||||
eth_if = i;
|
||||
}
|
||||
}
|
||||
if (wifi_sta_if != MDNS_MAX_INTERFACES && eth_if != MDNS_MAX_INTERFACES) {
|
||||
s_esp_netifs[wifi_sta_if].duplicate = eth_if;
|
||||
s_esp_netifs[eth_if].duplicate = wifi_sta_if;
|
||||
}
|
||||
}
|
||||
|
||||
void mdns_priv_netif_unregister_predefined_handlers(void)
|
||||
{
|
||||
#if MDNS_ESP_WIFI_ENABLED && (CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP)
|
||||
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, handle_system_event_for_preset);
|
||||
#endif
|
||||
#if CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP || CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, handle_system_event_for_preset);
|
||||
#endif
|
||||
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, handle_system_event_for_preset);
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t mdns_priv_netif_init(void)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
// zero-out local copy of netifs to initiate a fresh search by interface key whenever a netif ptr is needed
|
||||
for (mdns_if_t i = 0; i < MDNS_MAX_INTERFACES; ++i) {
|
||||
s_esp_netifs[i].netif = NULL;
|
||||
}
|
||||
#if MDNS_ESP_WIFI_ENABLED && (CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP)
|
||||
if ((err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, handle_system_event_for_preset, NULL)) != ESP_OK) {
|
||||
goto free_event_handlers;
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP || CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
if ((err = esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, handle_system_event_for_preset, NULL)) != ESP_OK) {
|
||||
goto free_event_handlers;
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
if ((err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, handle_system_event_for_preset, NULL)) != ESP_OK) {
|
||||
goto free_event_handlers;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP || CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
set_default_duplicated_interfaces();
|
||||
#endif
|
||||
|
||||
uint8_t i;
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
esp_ip6_addr_t tmp_addr6;
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
esp_netif_ip_info_t if_ip_info;
|
||||
#endif
|
||||
|
||||
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (!esp_netif_get_ip6_linklocal(mdns_priv_get_esp_netif(i), &tmp_addr6) && !mdns_utils_ipv6_address_is_zero(tmp_addr6)) {
|
||||
mdns_priv_pcb_enable(i, MDNS_IP_PROTOCOL_V6);
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (!esp_netif_get_ip_info(mdns_priv_get_esp_netif(i), &if_ip_info) && if_ip_info.ip.addr) {
|
||||
mdns_priv_pcb_enable(i, MDNS_IP_PROTOCOL_V4);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return ESP_OK;
|
||||
#if CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP || CONFIG_MDNS_PREDEF_NETIF_ETH
|
||||
free_event_handlers:
|
||||
mdns_priv_netif_unregister_predefined_handlers();
|
||||
#endif
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t mdns_priv_netif_deinit(void)
|
||||
{
|
||||
for (int i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
mdns_priv_pcb_disable(i, MDNS_IP_PROTOCOL_V6);
|
||||
mdns_priv_pcb_disable(i, MDNS_IP_PROTOCOL_V4);
|
||||
s_esp_netifs[i].duplicate = MDNS_MAX_INTERFACES;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Public Methods
|
||||
* */
|
||||
esp_err_t mdns_netif_action(esp_netif_t *esp_netif, mdns_event_actions_t event_action)
|
||||
{
|
||||
return post_custom_action(get_if_from_netif(esp_netif), event_action);
|
||||
}
|
||||
|
||||
esp_err_t mdns_register_netif(esp_netif_t *esp_netif)
|
||||
{
|
||||
if (!mdns_priv_is_server_init()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t err = ESP_ERR_NO_MEM;
|
||||
mdns_priv_service_lock();
|
||||
for (mdns_if_t i = 0; i < MDNS_MAX_INTERFACES; ++i) {
|
||||
if (s_esp_netifs[i].netif == esp_netif) {
|
||||
mdns_priv_service_unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
for (mdns_if_t i = 0; i < MDNS_MAX_INTERFACES; ++i) {
|
||||
if (!s_esp_netifs[i].predefined && s_esp_netifs[i].netif == NULL) {
|
||||
s_esp_netifs[i].netif = esp_netif;
|
||||
err = ESP_OK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mdns_priv_service_unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t mdns_unregister_netif(esp_netif_t *esp_netif)
|
||||
{
|
||||
if (!mdns_priv_is_server_init()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t err = ESP_ERR_NOT_FOUND;
|
||||
mdns_priv_service_lock();
|
||||
for (mdns_if_t i = 0; i < MDNS_MAX_INTERFACES; ++i) {
|
||||
if (!s_esp_netifs[i].predefined && s_esp_netifs[i].netif == esp_netif) {
|
||||
s_esp_netifs[i].netif = NULL;
|
||||
err = ESP_OK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mdns_priv_service_lock();
|
||||
return err;
|
||||
}
|
||||
@@ -16,11 +16,12 @@
|
||||
#include "lwip/udp.h"
|
||||
#include "lwip/mld6.h"
|
||||
#include "lwip/priv/tcpip_priv.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_event.h"
|
||||
#include "mdns_networking.h"
|
||||
#include "esp_netif_net_stack.h"
|
||||
#include "mdns_mem_caps.h"
|
||||
#include "mdns_utils.h"
|
||||
#include "mdns_netif.h"
|
||||
#include "mdns_service.h"
|
||||
|
||||
/*
|
||||
* MDNS Server Networking
|
||||
@@ -38,56 +39,75 @@ typedef struct interfaces {
|
||||
|
||||
static interfaces_t s_interfaces[MDNS_MAX_INTERFACES];
|
||||
|
||||
static struct udp_pcb *_pcb_main = NULL;
|
||||
static struct udp_pcb *s_pcb_main = NULL;
|
||||
|
||||
static const char *TAG = "mdns_networking";
|
||||
|
||||
static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip_addr_t *raddr, uint16_t rport);
|
||||
static void receive(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip_addr_t *raddr, uint16_t rport);
|
||||
|
||||
static esp_err_t send_rx_action(mdns_rx_packet_t *packet)
|
||||
{
|
||||
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_RX_HANDLE;
|
||||
action->data.rx_handle.packet = packet;
|
||||
if (!mdns_priv_queue_action(action)) {
|
||||
mdns_mem_free(action);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Low level UDP PCB Initialize
|
||||
*/
|
||||
static esp_err_t _udp_pcb_main_init(void)
|
||||
static esp_err_t pcb_init(void)
|
||||
{
|
||||
if (_pcb_main) {
|
||||
if (s_pcb_main) {
|
||||
return ESP_OK;
|
||||
}
|
||||
_pcb_main = udp_new();
|
||||
if (!_pcb_main) {
|
||||
s_pcb_main = udp_new();
|
||||
if (!s_pcb_main) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
if (udp_bind(_pcb_main, IP_ANY_TYPE, MDNS_SERVICE_PORT) != 0) {
|
||||
udp_remove(_pcb_main);
|
||||
_pcb_main = NULL;
|
||||
if (udp_bind(s_pcb_main, IP_ANY_TYPE, MDNS_SERVICE_PORT) != 0) {
|
||||
udp_remove(s_pcb_main);
|
||||
s_pcb_main = NULL;
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
_pcb_main->mcast_ttl = 255;
|
||||
_pcb_main->remote_port = MDNS_SERVICE_PORT;
|
||||
ip_addr_copy(_pcb_main->remote_ip, *(IP_ANY_TYPE));
|
||||
udp_recv(_pcb_main, &_udp_recv, NULL);
|
||||
s_pcb_main->mcast_ttl = 255;
|
||||
s_pcb_main->remote_port = MDNS_SERVICE_PORT;
|
||||
ip_addr_copy(s_pcb_main->remote_ip, *(IP_ANY_TYPE));
|
||||
udp_recv(s_pcb_main, receive, NULL);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Low level UDP PCB Free
|
||||
*/
|
||||
static void _udp_pcb_main_deinit(void)
|
||||
static void pcb_deinit(void)
|
||||
{
|
||||
if (_pcb_main) {
|
||||
udp_recv(_pcb_main, NULL, NULL);
|
||||
udp_disconnect(_pcb_main);
|
||||
udp_remove(_pcb_main);
|
||||
_pcb_main = NULL;
|
||||
if (s_pcb_main) {
|
||||
udp_recv(s_pcb_main, NULL, NULL);
|
||||
udp_disconnect(s_pcb_main);
|
||||
udp_remove(s_pcb_main);
|
||||
s_pcb_main = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Low level UDP Multicast membership control
|
||||
*/
|
||||
static esp_err_t _udp_join_group(mdns_if_t if_inx, mdns_ip_protocol_t ip_protocol, bool join)
|
||||
static esp_err_t join_group(mdns_if_t if_inx, mdns_ip_protocol_t ip_protocol, bool join)
|
||||
{
|
||||
struct netif *netif = NULL;
|
||||
esp_netif_t *tcpip_if = _mdns_get_esp_netif(if_inx);
|
||||
esp_netif_t *tcpip_if = mdns_priv_get_esp_netif(if_inx);
|
||||
|
||||
if (!esp_netif_is_netif_up(tcpip_if)) {
|
||||
// Network interface went down before event propagated, skipping IGMP config
|
||||
@@ -135,7 +155,7 @@ static esp_err_t _udp_join_group(mdns_if_t if_inx, mdns_ip_protocol_t ip_protoco
|
||||
* @brief the receive callback of the raw udp api. Packets are received here
|
||||
*
|
||||
*/
|
||||
static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip_addr_t *raddr, uint16_t rport)
|
||||
static void receive(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip_addr_t *raddr, uint16_t rport)
|
||||
{
|
||||
|
||||
uint8_t i;
|
||||
@@ -188,7 +208,7 @@ static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip
|
||||
struct netif *netif = NULL;
|
||||
bool found = false;
|
||||
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
netif = esp_netif_get_netif_impl(_mdns_get_esp_netif(i));
|
||||
netif = esp_netif_get_netif_impl(mdns_priv_get_esp_netif(i));
|
||||
if (s_interfaces[i].proto && netif && netif == ip_current_input_netif()) {
|
||||
#if LWIP_IPV4
|
||||
if (packet->src.type == IPADDR_TYPE_V4) {
|
||||
@@ -204,7 +224,7 @@ static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip
|
||||
}
|
||||
}
|
||||
|
||||
if (!found || _mdns_send_rx_action(packet) != ESP_OK) {
|
||||
if (!found || send_rx_action(packet) != ESP_OK) {
|
||||
pbuf_free(this_pb);
|
||||
mdns_mem_free(packet);
|
||||
}
|
||||
@@ -212,7 +232,7 @@ static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip
|
||||
|
||||
}
|
||||
|
||||
bool mdns_is_netif_ready(mdns_if_t netif, mdns_ip_protocol_t ip_proto)
|
||||
bool mdns_priv_if_ready(mdns_if_t netif, mdns_ip_protocol_t ip_proto)
|
||||
{
|
||||
return s_interfaces[netif].ready &&
|
||||
s_interfaces[netif].proto & (ip_proto == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6);
|
||||
@@ -221,12 +241,12 @@ bool mdns_is_netif_ready(mdns_if_t netif, mdns_ip_protocol_t ip_proto)
|
||||
/**
|
||||
* @brief Check if any of the interfaces is up
|
||||
*/
|
||||
static bool _udp_pcb_is_in_use(void)
|
||||
static bool is_any_pcb_in_use(void)
|
||||
{
|
||||
int i, p;
|
||||
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
for (p = 0; p < MDNS_IP_PROTOCOL_MAX; p++) {
|
||||
if (mdns_is_netif_ready(i, p)) {
|
||||
if (mdns_priv_if_ready(i, p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -237,14 +257,14 @@ static bool _udp_pcb_is_in_use(void)
|
||||
/**
|
||||
* @brief Stop PCB Main code
|
||||
*/
|
||||
static void _udp_pcb_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
static void pcb_if_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
s_interfaces[tcpip_if].proto &= ~(ip_protocol == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6);
|
||||
if (s_interfaces[tcpip_if].proto == 0) {
|
||||
s_interfaces[tcpip_if].ready = false;
|
||||
_udp_join_group(tcpip_if, ip_protocol, false);
|
||||
if (!_udp_pcb_is_in_use()) {
|
||||
_udp_pcb_main_deinit();
|
||||
join_group(tcpip_if, ip_protocol, false);
|
||||
if (!is_any_pcb_in_use()) {
|
||||
pcb_deinit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,18 +272,18 @@ static void _udp_pcb_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
/**
|
||||
* @brief Start PCB Main code
|
||||
*/
|
||||
static esp_err_t _udp_pcb_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
static esp_err_t pcb_if_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
if (mdns_is_netif_ready(tcpip_if, ip_protocol)) {
|
||||
if (mdns_priv_if_ready(tcpip_if, ip_protocol)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t err = _udp_join_group(tcpip_if, ip_protocol, true);
|
||||
esp_err_t err = join_group(tcpip_if, ip_protocol, true);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = _udp_pcb_main_init();
|
||||
err = pcb_init();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
@@ -286,20 +306,20 @@ typedef struct {
|
||||
/**
|
||||
* @brief Start PCB from LwIP thread
|
||||
*/
|
||||
static err_t _mdns_pcb_init_api(struct tcpip_api_call_data *api_call_msg)
|
||||
static err_t pcb_if_init_lwip(struct tcpip_api_call_data *api_call_msg)
|
||||
{
|
||||
mdns_api_call_t *msg = (mdns_api_call_t *)api_call_msg;
|
||||
msg->err = _udp_pcb_init(msg->tcpip_if, msg->ip_protocol) == ESP_OK ? ERR_OK : ERR_IF;
|
||||
return msg->err;
|
||||
msg->err = pcb_if_init(msg->tcpip_if, msg->ip_protocol);
|
||||
return msg->err == ESP_OK ? ERR_OK : ERR_IF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop PCB from LwIP thread
|
||||
*/
|
||||
static err_t _mdns_pcb_deinit_api(struct tcpip_api_call_data *api_call_msg)
|
||||
static err_t pcb_if_deinit_lwip(struct tcpip_api_call_data *api_call_msg)
|
||||
{
|
||||
mdns_api_call_t *msg = (mdns_api_call_t *)api_call_msg;
|
||||
_udp_pcb_deinit(msg->tcpip_if, msg->ip_protocol);
|
||||
pcb_if_deinit(msg->tcpip_if, msg->ip_protocol);
|
||||
msg->err = ESP_OK;
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -309,43 +329,43 @@ static err_t _mdns_pcb_deinit_api(struct tcpip_api_call_data *api_call_msg)
|
||||
* - _mdns prefixed
|
||||
* - commented in mdns_networking.h header
|
||||
*/
|
||||
esp_err_t _mdns_pcb_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
esp_err_t mdns_priv_if_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
mdns_api_call_t msg = {
|
||||
.tcpip_if = tcpip_if,
|
||||
.ip_protocol = ip_protocol
|
||||
};
|
||||
tcpip_api_call(_mdns_pcb_init_api, &msg.call);
|
||||
tcpip_api_call(pcb_if_init_lwip, &msg.call);
|
||||
return msg.err;
|
||||
}
|
||||
|
||||
esp_err_t _mdns_pcb_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
esp_err_t mdns_priv_if_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
mdns_api_call_t msg = {
|
||||
.tcpip_if = tcpip_if,
|
||||
.ip_protocol = ip_protocol
|
||||
};
|
||||
tcpip_api_call(_mdns_pcb_deinit_api, &msg.call);
|
||||
tcpip_api_call(pcb_if_deinit_lwip, &msg.call);
|
||||
return msg.err;
|
||||
}
|
||||
|
||||
static err_t _mdns_udp_pcb_write_api(struct tcpip_api_call_data *api_call_msg)
|
||||
static err_t write_if_lwip(struct tcpip_api_call_data *api_call_msg)
|
||||
{
|
||||
void *nif = NULL;
|
||||
mdns_api_call_t *msg = (mdns_api_call_t *)api_call_msg;
|
||||
nif = esp_netif_get_netif_impl(_mdns_get_esp_netif(msg->tcpip_if));
|
||||
if (!nif || !mdns_is_netif_ready(msg->tcpip_if, msg->ip_protocol) || _pcb_main == NULL) {
|
||||
nif = esp_netif_get_netif_impl(mdns_priv_get_esp_netif(msg->tcpip_if));
|
||||
if (!nif || !mdns_priv_if_ready(msg->tcpip_if, msg->ip_protocol) || s_pcb_main == NULL) {
|
||||
pbuf_free(msg->pbt);
|
||||
msg->err = ERR_IF;
|
||||
return ERR_IF;
|
||||
}
|
||||
esp_err_t err = udp_sendto_if(_pcb_main, msg->pbt, msg->ip, msg->port, (struct netif *)nif);
|
||||
err_t err = udp_sendto_if(s_pcb_main, msg->pbt, msg->ip, msg->port, (struct netif *)nif);
|
||||
pbuf_free(msg->pbt);
|
||||
msg->err = err;
|
||||
msg->err = err == ERR_OK ? ESP_OK : ESP_FAIL;
|
||||
return err;
|
||||
}
|
||||
|
||||
size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t *data, size_t len)
|
||||
size_t mdns_priv_if_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t *data, size_t len)
|
||||
{
|
||||
struct pbuf *pbt = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
|
||||
if (pbt == NULL) {
|
||||
@@ -373,7 +393,7 @@ size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, c
|
||||
.ip = &ip_add_copy,
|
||||
.port = port
|
||||
};
|
||||
tcpip_api_call(_mdns_udp_pcb_write_api, &msg.call);
|
||||
tcpip_api_call(write_if_lwip, &msg.call);
|
||||
|
||||
if (msg.err) {
|
||||
return 0;
|
||||
@@ -381,17 +401,17 @@ size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, c
|
||||
return len;
|
||||
}
|
||||
|
||||
void *_mdns_get_packet_data(mdns_rx_packet_t *packet)
|
||||
void *mdns_priv_get_packet_data(mdns_rx_packet_t *packet)
|
||||
{
|
||||
return packet->pb->payload;
|
||||
}
|
||||
|
||||
size_t _mdns_get_packet_len(mdns_rx_packet_t *packet)
|
||||
size_t mdns_priv_get_packet_len(mdns_rx_packet_t *packet)
|
||||
{
|
||||
return packet->pb->len;
|
||||
}
|
||||
|
||||
void _mdns_packet_free(mdns_rx_packet_t *packet)
|
||||
void mdns_priv_packet_free(mdns_rx_packet_t *packet)
|
||||
{
|
||||
pbuf_free(packet->pb);
|
||||
mdns_mem_free(packet);
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include <sys/param.h>
|
||||
#include "esp_log.h"
|
||||
#include "mdns_mem_caps.h"
|
||||
#include "mdns_utils.h"
|
||||
#include "mdns_netif.h"
|
||||
#include "mdns_service.h"
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_LINUX)
|
||||
#include <sys/ioctl.h>
|
||||
@@ -58,6 +61,26 @@ struct pbuf {
|
||||
#define s6_addr32 un.u32_addr
|
||||
#endif // CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
static esp_err_t send_rx_action(mdns_rx_packet_t *packet)
|
||||
{
|
||||
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_RX_HANDLE;
|
||||
action->data.rx_handle.packet = packet;
|
||||
if (!mdns_priv_queue_action(action)) {
|
||||
mdns_mem_free(action);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
static void __attribute__((constructor)) ctor_networking_socket(void)
|
||||
{
|
||||
for (int i = 0; i < sizeof(s_interfaces) / sizeof(s_interfaces[0]); ++i) {
|
||||
@@ -71,29 +94,29 @@ static void delete_socket(int sock)
|
||||
close(sock);
|
||||
}
|
||||
|
||||
bool mdns_is_netif_ready(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
bool mdns_priv_if_ready(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
return s_interfaces[tcpip_if].proto & (ip_protocol == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6);
|
||||
}
|
||||
|
||||
void *_mdns_get_packet_data(mdns_rx_packet_t *packet)
|
||||
void *mdns_priv_get_packet_data(mdns_rx_packet_t *packet)
|
||||
{
|
||||
return packet->pb->payload;
|
||||
}
|
||||
|
||||
size_t _mdns_get_packet_len(mdns_rx_packet_t *packet)
|
||||
size_t mdns_priv_get_packet_len(mdns_rx_packet_t *packet)
|
||||
{
|
||||
return packet->pb->len;
|
||||
}
|
||||
|
||||
void _mdns_packet_free(mdns_rx_packet_t *packet)
|
||||
void mdns_priv_packet_free(mdns_rx_packet_t *packet)
|
||||
{
|
||||
mdns_mem_free(packet->pb->payload);
|
||||
mdns_mem_free(packet->pb);
|
||||
mdns_mem_free(packet);
|
||||
}
|
||||
|
||||
esp_err_t _mdns_pcb_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
esp_err_t mdns_priv_if_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
s_interfaces[tcpip_if].proto &= ~(ip_protocol == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6);
|
||||
if (s_interfaces[tcpip_if].proto == 0) {
|
||||
@@ -192,7 +215,7 @@ static inline size_t espaddr_to_inet(const esp_ip_addr_t *addr, const uint16_t p
|
||||
return ss_addr_len;
|
||||
}
|
||||
|
||||
size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t *data, size_t len)
|
||||
size_t mdns_priv_if_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t *data, size_t len)
|
||||
{
|
||||
if (!(s_interfaces[tcpip_if].proto & (ip_protocol == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6))) {
|
||||
return 0;
|
||||
@@ -210,7 +233,7 @@ size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, c
|
||||
ESP_LOGD(TAG, "[sock=%d]: Sending to IP %s port %d", sock, get_string_address(&in_addr), port);
|
||||
ssize_t actual_len = sendto(sock, data, len, 0, (struct sockaddr *)&in_addr, ss_size);
|
||||
if (actual_len < 0) {
|
||||
ESP_LOGE(TAG, "[sock=%d]: _mdns_udp_pcb_write sendto() has failed\n errno=%d: %s", sock, errno, strerror(errno));
|
||||
ESP_LOGE(TAG, "[sock=%d]: mdns_priv_if_write sendto() has failed\n errno=%d: %s", sock, errno, strerror(errno));
|
||||
}
|
||||
return actual_len;
|
||||
}
|
||||
@@ -325,8 +348,8 @@ void sock_recv_task(void *arg)
|
||||
packet->dest.type = packet->src.type;
|
||||
packet->ip_protocol =
|
||||
packet->src.type == ESP_IPADDR_TYPE_V4 ? MDNS_IP_PROTOCOL_V4 : MDNS_IP_PROTOCOL_V6;
|
||||
if (_mdns_send_rx_action(packet) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "_mdns_send_rx_action failed!");
|
||||
if (send_rx_action(packet) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "send_rx_action failed!");
|
||||
mdns_mem_free(packet->pb->payload);
|
||||
mdns_mem_free(packet->pb);
|
||||
mdns_mem_free(packet);
|
||||
@@ -338,7 +361,7 @@ void sock_recv_task(void *arg)
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void mdns_networking_init(void)
|
||||
static void networking_init(void)
|
||||
{
|
||||
if (s_run_sock_recv_task == false) {
|
||||
s_run_sock_recv_task = true;
|
||||
@@ -352,7 +375,7 @@ static bool create_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
return true;
|
||||
}
|
||||
int sock = s_interfaces[tcpip_if].sock;
|
||||
esp_netif_t *netif = _mdns_get_esp_netif(tcpip_if);
|
||||
esp_netif_t *netif = mdns_priv_get_esp_netif(tcpip_if);
|
||||
if (sock < 0) {
|
||||
sock = create_socket(netif);
|
||||
}
|
||||
@@ -369,14 +392,14 @@ static bool create_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t _mdns_pcb_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
esp_err_t mdns_priv_if_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
ESP_LOGI(TAG, "_mdns_pcb_init(tcpip_if=%lu, ip_protocol=%lu)", (unsigned long)tcpip_if, (unsigned long)ip_protocol);
|
||||
ESP_LOGI(TAG, "mdns_priv_if_init(tcpip_if=%lu, ip_protocol=%lu)", (unsigned long)tcpip_if, (unsigned long)ip_protocol);
|
||||
if (!create_pcb(tcpip_if, ip_protocol)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
mdns_networking_init();
|
||||
networking_init();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
462
components/mdns/mdns_pcb.c
Normal file
462
components/mdns/mdns_pcb.c
Normal file
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "mdns_private.h"
|
||||
#include "mdns_networking.h"
|
||||
#include "mdns_pcb.h"
|
||||
#include "mdns_netif.h"
|
||||
#include "mdns_utils.h"
|
||||
#include "mdns_send.h"
|
||||
#include "mdns_mem_caps.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
#include "mdns_responder.h"
|
||||
|
||||
#define PCB_STATE_IS_PROBING(s) (s->state > PCB_OFF && s->state < PCB_ANNOUNCE_1)
|
||||
#define PCB_STATE_IS_ANNOUNCING(s) (s->state > PCB_PROBE_3 && s->state < PCB_RUNNING)
|
||||
|
||||
typedef enum {
|
||||
PCB_OFF, PCB_DUP, PCB_INIT,
|
||||
PCB_PROBE_1, PCB_PROBE_2, PCB_PROBE_3,
|
||||
PCB_ANNOUNCE_1, PCB_ANNOUNCE_2, PCB_ANNOUNCE_3,
|
||||
PCB_RUNNING
|
||||
} mdns_pcb_state_t;
|
||||
|
||||
typedef struct {
|
||||
mdns_pcb_state_t state;
|
||||
mdns_srv_item_t **probe_services;
|
||||
uint8_t probe_services_len;
|
||||
uint8_t probe_ip;
|
||||
uint8_t probe_running;
|
||||
uint16_t failed_probes;
|
||||
} mdns_pcb_t;
|
||||
|
||||
static const char *TAG = "mdns_pcb";
|
||||
static mdns_pcb_t s_pcbs[MDNS_MAX_INTERFACES][MDNS_IP_PROTOCOL_MAX];
|
||||
|
||||
/**
|
||||
* @brief Send announcement on particular PCB
|
||||
*/
|
||||
void mdns_priv_pcb_announce(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t **services, size_t len, bool include_ip)
|
||||
{
|
||||
mdns_pcb_t *_pcb = &s_pcbs[tcpip_if][ip_protocol];
|
||||
size_t i;
|
||||
if (mdns_priv_if_ready(tcpip_if, ip_protocol)) {
|
||||
if (PCB_STATE_IS_PROBING(_pcb)) {
|
||||
mdns_priv_init_pcb_probe(tcpip_if, ip_protocol, services, len, include_ip);
|
||||
} else if (PCB_STATE_IS_ANNOUNCING(_pcb)) {
|
||||
mdns_tx_packet_t *p = mdns_priv_get_next_packet(tcpip_if, ip_protocol);
|
||||
if (p) {
|
||||
for (i = 0; i < len; i++) {
|
||||
if (!mdns_priv_create_answer(&p->answers, MDNS_TYPE_SDPTR, services[i]->service, NULL, false, false)
|
||||
|| !mdns_priv_create_answer(&p->answers, MDNS_TYPE_PTR, services[i]->service, NULL, false,
|
||||
false)
|
||||
|| !mdns_priv_create_answer(&p->answers, MDNS_TYPE_SRV, services[i]->service, NULL, true,
|
||||
false)
|
||||
|| !mdns_priv_create_answer(&p->answers, MDNS_TYPE_TXT, services[i]->service, NULL, true,
|
||||
false)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (include_ip) {
|
||||
mdns_priv_dealloc_answer(&p->additional, MDNS_TYPE_A, NULL);
|
||||
mdns_priv_dealloc_answer(&p->additional, MDNS_TYPE_AAAA, NULL);
|
||||
mdns_priv_append_host_list_in_services(&p->answers, services, len, true, false);
|
||||
}
|
||||
_pcb->state = PCB_ANNOUNCE_1;
|
||||
}
|
||||
} else if (_pcb->state == PCB_RUNNING) {
|
||||
|
||||
if (mdns_utils_str_null_or_empty(mdns_priv_get_global_hostname())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_pcb->state = PCB_ANNOUNCE_1;
|
||||
mdns_tx_packet_t *p = mdns_priv_create_announce_packet(tcpip_if, ip_protocol, services, len, include_ip);
|
||||
if (p) {
|
||||
mdns_priv_send_after(p, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if interface is duplicate (two interfaces on the same subnet)
|
||||
*/
|
||||
bool mdns_priv_pcb_check_for_duplicates(mdns_if_t tcpip_if)
|
||||
{
|
||||
mdns_if_t ifaces[MDNS_MAX_INTERFACES] = {tcpip_if, mdns_priv_netif_get_other_interface(tcpip_if) };
|
||||
if (ifaces[1] == MDNS_MAX_INTERFACES) {
|
||||
return false;
|
||||
}
|
||||
// check both this netif and the potential duplicate on all protocols
|
||||
// if any of them is in duplicate state, return true
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int proto = 0; proto < MDNS_IP_PROTOCOL_MAX; proto++) {
|
||||
if (s_pcbs[ifaces[i]][(mdns_ip_protocol_t) proto].state == PCB_DUP) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static esp_err_t deinit_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_proto)
|
||||
{
|
||||
esp_err_t err = mdns_priv_if_deinit(tcpip_if, ip_proto);
|
||||
mdns_pcb_t *pcb = &s_pcbs[tcpip_if][ip_proto];
|
||||
if (pcb == NULL || err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
mdns_mem_free(pcb->probe_services);
|
||||
pcb->state = PCB_OFF;
|
||||
pcb->probe_ip = false;
|
||||
pcb->probe_services = NULL;
|
||||
pcb->probe_services_len = 0;
|
||||
pcb->probe_running = false;
|
||||
pcb->failed_probes = 0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restart the responder on particular PCB
|
||||
*/
|
||||
static void restart_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
size_t srv_count = 0;
|
||||
mdns_srv_item_t *a = mdns_priv_get_services();
|
||||
while (a) {
|
||||
srv_count++;
|
||||
a = a->next;
|
||||
}
|
||||
if (srv_count == 0) {
|
||||
// proble only IP
|
||||
mdns_priv_init_pcb_probe(tcpip_if, ip_protocol, NULL, 0, true);
|
||||
return;
|
||||
}
|
||||
mdns_srv_item_t *services[srv_count];
|
||||
size_t i = 0;
|
||||
a = mdns_priv_get_services();
|
||||
while (a) {
|
||||
services[i++] = a;
|
||||
a = a->next;
|
||||
}
|
||||
mdns_priv_init_pcb_probe(tcpip_if, ip_protocol, services, srv_count, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable mDNS interface
|
||||
*/
|
||||
void mdns_priv_pcb_disable(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
mdns_priv_netif_disable(tcpip_if);
|
||||
|
||||
if (mdns_priv_if_ready(tcpip_if, ip_protocol)) {
|
||||
mdns_priv_clear_tx_queue_if(tcpip_if, ip_protocol);
|
||||
deinit_pcb(tcpip_if, ip_protocol);
|
||||
mdns_if_t other_if = mdns_priv_netif_get_other_interface(tcpip_if);
|
||||
if (other_if != MDNS_MAX_INTERFACES && s_pcbs[other_if][ip_protocol].state == PCB_DUP) {
|
||||
s_pcbs[other_if][ip_protocol].state = PCB_OFF;
|
||||
mdns_priv_pcb_enable(other_if, ip_protocol);
|
||||
}
|
||||
}
|
||||
s_pcbs[tcpip_if][ip_protocol].state = PCB_OFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable mDNS interface
|
||||
*/
|
||||
void mdns_priv_pcb_enable(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
if (!mdns_priv_if_ready(tcpip_if, ip_protocol)) {
|
||||
if (mdns_priv_if_init(tcpip_if, ip_protocol)) {
|
||||
s_pcbs[tcpip_if][ip_protocol].failed_probes = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
restart_pcb(tcpip_if, ip_protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set interface as duplicate if another is found on the same subnet
|
||||
*/
|
||||
void mdns_priv_pcb_set_duplicate(mdns_if_t tcpip_if)
|
||||
{
|
||||
uint8_t i;
|
||||
mdns_if_t other_if = mdns_priv_netif_get_other_interface(tcpip_if);
|
||||
if (other_if == MDNS_MAX_INTERFACES) {
|
||||
return; // no other interface found
|
||||
}
|
||||
for (i = 0; i < MDNS_IP_PROTOCOL_MAX; i++) {
|
||||
if (mdns_priv_if_ready(other_if, i)) {
|
||||
//stop this interface and mark as dup
|
||||
if (mdns_priv_if_ready(tcpip_if, i)) {
|
||||
mdns_priv_clear_tx_queue_if(tcpip_if, i);
|
||||
deinit_pcb(tcpip_if, i);
|
||||
}
|
||||
s_pcbs[tcpip_if][i].state = PCB_DUP;
|
||||
mdns_priv_pcb_announce(other_if, i, NULL, 0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mdns_priv_pcb_is_off(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
return s_pcbs[tcpip_if][ip_protocol].state == PCB_OFF;
|
||||
}
|
||||
|
||||
void mdns_priv_pcb_schedule_tx_packet(mdns_tx_packet_t *p)
|
||||
{
|
||||
mdns_pcb_t *pcb = &s_pcbs[p->tcpip_if][p->ip_protocol];
|
||||
mdns_out_question_t *q = NULL;
|
||||
mdns_tx_packet_t *a = NULL;
|
||||
uint32_t send_after = 1000;
|
||||
switch (pcb->state) {
|
||||
case PCB_PROBE_1:
|
||||
q = p->questions;
|
||||
while (q) {
|
||||
q->unicast = false;
|
||||
q = q->next;
|
||||
}
|
||||
//fallthrough
|
||||
case PCB_PROBE_2:
|
||||
mdns_priv_send_after(p, 250);
|
||||
pcb->state = (mdns_pcb_state_t)((uint8_t)(pcb->state) + 1);
|
||||
break;
|
||||
case PCB_PROBE_3:
|
||||
a = mdns_priv_create_announce_from_probe(p);
|
||||
if (!a) {
|
||||
mdns_priv_send_after(p, 250);
|
||||
break;
|
||||
}
|
||||
pcb->probe_running = false;
|
||||
pcb->probe_ip = false;
|
||||
pcb->probe_services_len = 0;
|
||||
pcb->failed_probes = 0;
|
||||
mdns_mem_free(pcb->probe_services);
|
||||
pcb->probe_services = NULL;
|
||||
mdns_priv_free_tx_packet(p);
|
||||
p = a;
|
||||
send_after = 250;
|
||||
//fallthrough
|
||||
case PCB_ANNOUNCE_1:
|
||||
//fallthrough
|
||||
case PCB_ANNOUNCE_2:
|
||||
mdns_priv_send_after(p, send_after);
|
||||
pcb->state = (mdns_pcb_state_t)((uint8_t)(pcb->state) + 1);
|
||||
break;
|
||||
case PCB_ANNOUNCE_3:
|
||||
pcb->state = PCB_RUNNING;
|
||||
mdns_priv_free_tx_packet(p);
|
||||
break;
|
||||
default:
|
||||
mdns_priv_free_tx_packet(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void mdns_priv_pcb_check_probing_services(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_service_t *service, bool removed_answers, bool *should_remove_questions)
|
||||
{
|
||||
mdns_pcb_t *_pcb = &s_pcbs[tcpip_if][ip_protocol];
|
||||
|
||||
if (PCB_STATE_IS_PROBING(_pcb)) {
|
||||
uint8_t i;
|
||||
//check if we are probing this service
|
||||
for (i = 0; i < _pcb->probe_services_len; i++) {
|
||||
mdns_srv_item_t *s = _pcb->probe_services[i];
|
||||
if (s->service == service) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i < _pcb->probe_services_len) {
|
||||
if (_pcb->probe_services_len > 1) {
|
||||
uint8_t n;
|
||||
for (n = (i + 1); n < _pcb->probe_services_len; n++) {
|
||||
_pcb->probe_services[n - 1] = _pcb->probe_services[n];
|
||||
}
|
||||
_pcb->probe_services_len--;
|
||||
} else {
|
||||
_pcb->probe_services_len = 0;
|
||||
mdns_mem_free(_pcb->probe_services);
|
||||
_pcb->probe_services = NULL;
|
||||
if (!_pcb->probe_ip) {
|
||||
_pcb->probe_running = false;
|
||||
_pcb->state = PCB_RUNNING;
|
||||
}
|
||||
}
|
||||
*should_remove_questions = true;
|
||||
return;
|
||||
}
|
||||
} else if (PCB_STATE_IS_ANNOUNCING(_pcb)) {
|
||||
//if answers were cleared, set to running
|
||||
if (removed_answers) {
|
||||
_pcb->state = PCB_RUNNING;
|
||||
}
|
||||
}
|
||||
*should_remove_questions = false;
|
||||
}
|
||||
|
||||
void mdns_priv_pcb_deinit(void)
|
||||
{
|
||||
for (int i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
for (int j = 0; j < MDNS_IP_PROTOCOL_MAX; j++) {
|
||||
deinit_pcb(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mdsn_priv_pcb_is_inited(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
return mdns_priv_if_ready(tcpip_if, ip_protocol) && s_pcbs[tcpip_if][ip_protocol].state > PCB_INIT;
|
||||
}
|
||||
|
||||
bool mdns_priv_pcb_is_duplicate(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
return s_pcbs[tcpip_if][ip_protocol].state == PCB_DUP;
|
||||
}
|
||||
|
||||
bool mdns_priv_pcb_is_probing(mdns_rx_packet_t *packet)
|
||||
{
|
||||
return s_pcbs[packet->tcpip_if][packet->ip_protocol].probe_running;
|
||||
}
|
||||
|
||||
bool mdns_priv_pcb_is_after_probing(mdns_rx_packet_t *packet)
|
||||
{
|
||||
return s_pcbs[packet->tcpip_if][packet->ip_protocol].state > PCB_PROBE_3;
|
||||
}
|
||||
|
||||
void mdns_priv_pcb_set_probe_failed(mdns_rx_packet_t *packet)
|
||||
{
|
||||
s_pcbs[packet->tcpip_if][packet->ip_protocol].failed_probes++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send probe for additional services on particular PCB
|
||||
*/
|
||||
static void init_probe_new_service(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t **services, size_t len, bool probe_ip)
|
||||
{
|
||||
mdns_pcb_t *pcb = &s_pcbs[tcpip_if][ip_protocol];
|
||||
size_t services_final_len = len;
|
||||
|
||||
if (PCB_STATE_IS_PROBING(pcb)) {
|
||||
services_final_len += pcb->probe_services_len;
|
||||
}
|
||||
mdns_srv_item_t **s = NULL;
|
||||
if (services_final_len) {
|
||||
s = (mdns_srv_item_t **)mdns_mem_malloc(sizeof(mdns_srv_item_t *) * services_final_len);
|
||||
if (!s) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return;
|
||||
}
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < len; i++) {
|
||||
s[i] = services[i];
|
||||
}
|
||||
if (pcb->probe_services) {
|
||||
for (i = 0; i < pcb->probe_services_len; i++) {
|
||||
s[len + i] = pcb->probe_services[i];
|
||||
}
|
||||
mdns_mem_free(pcb->probe_services);
|
||||
}
|
||||
}
|
||||
|
||||
probe_ip = pcb->probe_ip || probe_ip;
|
||||
|
||||
pcb->probe_ip = false;
|
||||
pcb->probe_services = NULL;
|
||||
pcb->probe_services_len = 0;
|
||||
pcb->probe_running = false;
|
||||
|
||||
mdns_tx_packet_t *packet = mdns_priv_create_probe_packet(tcpip_if, ip_protocol, s, services_final_len, true,
|
||||
probe_ip);
|
||||
if (!packet) {
|
||||
mdns_mem_free(s);
|
||||
return;
|
||||
}
|
||||
|
||||
pcb->probe_ip = probe_ip;
|
||||
pcb->probe_services = s;
|
||||
pcb->probe_services_len = services_final_len;
|
||||
pcb->probe_running = true;
|
||||
mdns_priv_send_after(packet, ((pcb->failed_probes > 5) ? 1000 : 120) + (esp_random() & 0x7F));
|
||||
pcb->state = PCB_PROBE_1;
|
||||
}
|
||||
|
||||
void mdns_priv_init_pcb_probe(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, mdns_srv_item_t **services, size_t len, bool probe_ip)
|
||||
{
|
||||
mdns_pcb_t *pcb = &s_pcbs[tcpip_if][ip_protocol];
|
||||
|
||||
mdns_priv_clear_tx_queue_if(tcpip_if, ip_protocol);
|
||||
|
||||
if (mdns_utils_str_null_or_empty(mdns_priv_get_global_hostname())) {
|
||||
pcb->state = PCB_RUNNING;
|
||||
return;
|
||||
}
|
||||
|
||||
if (PCB_STATE_IS_PROBING(pcb)) {
|
||||
// Looking for already probing services to resolve duplications
|
||||
mdns_srv_item_t *new_probe_services[len];
|
||||
int new_probe_service_len = 0;
|
||||
bool found;
|
||||
for (size_t j = 0; j < len; ++j) {
|
||||
found = false;
|
||||
for (int i = 0; i < pcb->probe_services_len; ++i) {
|
||||
if (pcb->probe_services[i] == services[j]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
new_probe_services[new_probe_service_len++] = services[j];
|
||||
}
|
||||
}
|
||||
// init probing for newly added services
|
||||
init_probe_new_service(tcpip_if, ip_protocol,
|
||||
new_probe_service_len ? new_probe_services : NULL, new_probe_service_len, probe_ip);
|
||||
} else {
|
||||
// not probing, so init for all services
|
||||
init_probe_new_service(tcpip_if, ip_protocol, services, len, probe_ip);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send by for particular services
|
||||
*/
|
||||
void mdns_priv_pcb_send_bye_service(mdns_srv_item_t **services, size_t len, bool include_ip)
|
||||
{
|
||||
uint8_t i, j;
|
||||
if (mdns_utils_str_null_or_empty(mdns_priv_get_global_hostname())) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
for (j = 0; j < MDNS_IP_PROTOCOL_MAX; j++) {
|
||||
if (mdns_priv_if_ready(i, j) && s_pcbs[i][j].state == PCB_RUNNING) {
|
||||
mdns_priv_send_bye((mdns_if_t) i, (mdns_ip_protocol_t) j, services, len, include_ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mdns_priv_probe_all_pcbs(mdns_srv_item_t **services, size_t len, bool probe_ip, bool clear_old_probe)
|
||||
{
|
||||
uint8_t i, j;
|
||||
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
for (j = 0; j < MDNS_IP_PROTOCOL_MAX; j++) {
|
||||
if (mdns_priv_if_ready(i, j)) {
|
||||
mdns_pcb_t *_pcb = &s_pcbs[i][j];
|
||||
if (clear_old_probe) {
|
||||
mdns_mem_free(_pcb->probe_services);
|
||||
_pcb->probe_services = NULL;
|
||||
_pcb->probe_services_len = 0;
|
||||
_pcb->probe_running = false;
|
||||
}
|
||||
mdns_priv_init_pcb_probe((mdns_if_t) i, (mdns_ip_protocol_t) j, services, len, probe_ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
872
components/mdns/mdns_querier.c
Normal file
872
components/mdns/mdns_querier.c
Normal file
@@ -0,0 +1,872 @@
|
||||
/*
|
||||
* 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_querier.h"
|
||||
#include "mdns_mem_caps.h"
|
||||
#include "mdns_utils.h"
|
||||
#include "mdns_send.h"
|
||||
#include "esp_log.h"
|
||||
#include "mdns_pcb.h"
|
||||
#include "mdns_netif.h"
|
||||
#include "mdns_responder.h"
|
||||
#include "mdns_service.h"
|
||||
|
||||
static const char *TAG = "mdns_querier";
|
||||
static mdns_search_once_t *s_search_once;
|
||||
|
||||
static esp_err_t send_search_action(mdns_action_type_t type, mdns_search_once_t *search);
|
||||
static void search_free(mdns_search_once_t *search);
|
||||
|
||||
void mdns_priv_query_results_free(mdns_result_t *results)
|
||||
{
|
||||
mdns_result_t *r;
|
||||
mdns_ip_addr_t *a;
|
||||
|
||||
while (results) {
|
||||
r = results;
|
||||
|
||||
mdns_mem_free((char *)(r->hostname));
|
||||
mdns_mem_free((char *)(r->instance_name));
|
||||
mdns_mem_free((char *)(r->service_type));
|
||||
mdns_mem_free((char *)(r->proto));
|
||||
|
||||
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);
|
||||
|
||||
while (r->addr) {
|
||||
a = r->addr;
|
||||
r->addr = r->addr->next;
|
||||
mdns_mem_free(a);
|
||||
}
|
||||
|
||||
results = results->next;
|
||||
mdns_mem_free(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mark search as finished and remove it from search chain
|
||||
*/
|
||||
static void search_finish(mdns_search_once_t *search)
|
||||
{
|
||||
search->state = SEARCH_OFF;
|
||||
queueDetach(mdns_search_once_t, s_search_once, search);
|
||||
if (search->notifier) {
|
||||
search->notifier(search);
|
||||
}
|
||||
xSemaphoreGive(search->done_semaphore);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add new search to the search chain
|
||||
*/
|
||||
void search_add(mdns_search_once_t *search)
|
||||
{
|
||||
search->next = s_search_once;
|
||||
s_search_once = search;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send search packet to all available interfaces
|
||||
*/
|
||||
static void search_send(mdns_search_once_t *search)
|
||||
{
|
||||
mdns_search_once_t *queue = s_search_once;
|
||||
bool found = false;
|
||||
// looking for this search in active searches
|
||||
while (queue) {
|
||||
if (queue == search) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
queue = queue->next;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// no longer active -> skip sending this search
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t i, j;
|
||||
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
for (j = 0; j < MDNS_IP_PROTOCOL_MAX; j++) {
|
||||
mdns_priv_query_send(search, (mdns_if_t) i, (mdns_ip_protocol_t) j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mdns_priv_query_action(mdns_action_t *action, mdns_action_subtype_t type)
|
||||
{
|
||||
if (type == ACTION_RUN) {
|
||||
switch (action->type) {
|
||||
case ACTION_SEARCH_ADD:
|
||||
search_add(action->data.search_add.search);
|
||||
break;
|
||||
case ACTION_SEARCH_SEND:
|
||||
search_send(action->data.search_add.search);
|
||||
break;
|
||||
case ACTION_SEARCH_END:
|
||||
search_finish(action->data.search_add.search);
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type == ACTION_CLEANUP) {
|
||||
search_free(action->data.search_add.search);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from timer task to run active searches
|
||||
*/
|
||||
void mdns_priv_query_start_stop(void)
|
||||
{
|
||||
mdns_priv_service_lock();
|
||||
mdns_search_once_t *s = s_search_once;
|
||||
uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS;
|
||||
if (!s) {
|
||||
mdns_priv_service_unlock();
|
||||
return;
|
||||
}
|
||||
while (s) {
|
||||
if (s->state != SEARCH_OFF) {
|
||||
if (now > (s->started_at + s->timeout)) {
|
||||
s->state = SEARCH_OFF;
|
||||
if (send_search_action(ACTION_SEARCH_END, s) != ESP_OK) {
|
||||
s->state = SEARCH_RUNNING;
|
||||
}
|
||||
} else if (s->state == SEARCH_INIT || (now - s->sent_at) > 1000) {
|
||||
s->state = SEARCH_RUNNING;
|
||||
s->sent_at = now;
|
||||
if (send_search_action(ACTION_SEARCH_SEND, s) != ESP_OK) {
|
||||
s->sent_at -= 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
s = s->next;
|
||||
}
|
||||
mdns_priv_service_unlock();
|
||||
}
|
||||
|
||||
void mdns_priv_query_free(void)
|
||||
{
|
||||
while (s_search_once) {
|
||||
mdns_search_once_t *h = s_search_once;
|
||||
s_search_once = h->next;
|
||||
mdns_mem_free(h->instance);
|
||||
mdns_mem_free(h->service);
|
||||
mdns_mem_free(h->proto);
|
||||
vSemaphoreDelete(h->done_semaphore);
|
||||
if (h->result) {
|
||||
mdns_priv_query_results_free(h->result);
|
||||
}
|
||||
mdns_mem_free(h);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from parser to finish any searches that have reached maximum results
|
||||
*/
|
||||
void mdns_priv_query_done(void)
|
||||
{
|
||||
mdns_search_once_t *search = s_search_once;
|
||||
mdns_search_once_t *s = NULL;
|
||||
while (search) {
|
||||
s = search;
|
||||
search = search->next;
|
||||
if (s->max_results && s->num_results >= s->max_results) {
|
||||
search_finish(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from packet parser to find matching running search
|
||||
*/
|
||||
mdns_search_once_t *mdns_priv_query_find_from(mdns_search_once_t *s, mdns_name_t *name, uint16_t type, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
mdns_result_t *r = NULL;
|
||||
while (s) {
|
||||
if (s->state == SEARCH_OFF) {
|
||||
s = s->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == MDNS_TYPE_A || type == MDNS_TYPE_AAAA) {
|
||||
if ((s->type == MDNS_TYPE_ANY && s->service != NULL)
|
||||
|| (s->type != MDNS_TYPE_ANY && s->type != type && s->type != MDNS_TYPE_PTR && s->type != MDNS_TYPE_SRV)) {
|
||||
s = s->next;
|
||||
continue;
|
||||
}
|
||||
if (s->type != MDNS_TYPE_PTR && s->type != MDNS_TYPE_SRV) {
|
||||
if (!strcasecmp(name->host, s->instance)) {
|
||||
return s;
|
||||
}
|
||||
s = s->next;
|
||||
continue;
|
||||
}
|
||||
r = s->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 s;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
s = s->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == MDNS_TYPE_SRV || type == MDNS_TYPE_TXT) {
|
||||
if ((s->type == MDNS_TYPE_ANY && s->service == NULL)
|
||||
|| (s->type != MDNS_TYPE_ANY && s->type != type && s->type != MDNS_TYPE_PTR)) {
|
||||
s = s->next;
|
||||
continue;
|
||||
}
|
||||
if (strcasecmp(name->service, s->service)
|
||||
|| strcasecmp(name->proto, s->proto)) {
|
||||
s = s->next;
|
||||
continue;
|
||||
}
|
||||
if (s->type != MDNS_TYPE_PTR) {
|
||||
if (s->instance && strcasecmp(name->host, s->instance) == 0) {
|
||||
return s;
|
||||
}
|
||||
s = s->next;
|
||||
continue;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
if (type == MDNS_TYPE_PTR && type == s->type && !strcasecmp(name->service, s->service) && !strcasecmp(name->proto, s->proto)) {
|
||||
return s;
|
||||
}
|
||||
|
||||
s = s->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mdns_search_once_t *mdns_priv_query_find(mdns_name_t *name, uint16_t type, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
return mdns_priv_query_find_from(s_search_once, name, type, tcpip_if, ip_protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create search packet for particular interface
|
||||
*/
|
||||
static mdns_tx_packet_t *create_search_packet(mdns_search_once_t *search, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
mdns_result_t *r = NULL;
|
||||
mdns_tx_packet_t *packet = mdns_priv_alloc_packet(tcpip_if, ip_protocol);
|
||||
if (!packet) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mdns_out_question_t *q = (mdns_out_question_t *)mdns_mem_malloc(sizeof(mdns_out_question_t));
|
||||
if (!q) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
mdns_priv_free_tx_packet(packet);
|
||||
return NULL;
|
||||
}
|
||||
q->next = NULL;
|
||||
q->unicast = search->unicast;
|
||||
q->type = search->type;
|
||||
q->host = search->instance;
|
||||
q->service = search->service;
|
||||
q->proto = search->proto;
|
||||
q->domain = MDNS_UTILS_DEFAULT_DOMAIN;
|
||||
q->own_dynamic_memory = false;
|
||||
queueToEnd(mdns_out_question_t, packet->questions, q);
|
||||
|
||||
if (search->type == MDNS_TYPE_PTR) {
|
||||
r = search->result;
|
||||
while (r) {
|
||||
//full record on the same interface is available
|
||||
if (r->esp_netif != mdns_priv_get_esp_netif(tcpip_if) || r->ip_protocol != ip_protocol || r->instance_name == NULL || r->hostname == NULL || r->addr == NULL) {
|
||||
r = r->next;
|
||||
continue;
|
||||
}
|
||||
mdns_out_answer_t *a = (mdns_out_answer_t *)mdns_mem_malloc(sizeof(mdns_out_answer_t));
|
||||
if (!a) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
mdns_priv_free_tx_packet(packet);
|
||||
return NULL;
|
||||
}
|
||||
a->type = MDNS_TYPE_PTR;
|
||||
a->service = NULL;
|
||||
a->custom_instance = r->instance_name;
|
||||
a->custom_service = search->service;
|
||||
a->custom_proto = search->proto;
|
||||
a->bye = false;
|
||||
a->flush = false;
|
||||
a->next = NULL;
|
||||
queueToEnd(mdns_out_answer_t, packet->answers, a);
|
||||
r = r->next;
|
||||
}
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Send search packet to particular interface
|
||||
*/
|
||||
void mdns_priv_query_send(mdns_search_once_t *search, mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
{
|
||||
mdns_tx_packet_t *packet = NULL;
|
||||
if (mdsn_priv_pcb_is_inited(tcpip_if, ip_protocol)) {
|
||||
packet = create_search_packet(search, tcpip_if, ip_protocol);
|
||||
if (!packet) {
|
||||
return;
|
||||
}
|
||||
mdns_priv_dispatch_tx_packet(packet);
|
||||
mdns_priv_free_tx_packet(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free search structure (except the results)
|
||||
*/
|
||||
static void search_free(mdns_search_once_t *search)
|
||||
{
|
||||
mdns_mem_free(search->instance);
|
||||
mdns_mem_free(search->service);
|
||||
mdns_mem_free(search->proto);
|
||||
vSemaphoreDelete(search->done_semaphore);
|
||||
mdns_mem_free(search);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate new search structure
|
||||
*/
|
||||
static mdns_search_once_t *search_init(const char *name, const char *service, const char *proto, uint16_t type, bool unicast,
|
||||
uint32_t timeout, uint8_t max_results, mdns_query_notify_t notifier)
|
||||
{
|
||||
mdns_search_once_t *search = (mdns_search_once_t *)mdns_mem_malloc(sizeof(mdns_search_once_t));
|
||||
if (!search) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
memset(search, 0, sizeof(mdns_search_once_t));
|
||||
|
||||
search->done_semaphore = xSemaphoreCreateBinary();
|
||||
if (!search->done_semaphore) {
|
||||
mdns_mem_free(search);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!mdns_utils_str_null_or_empty(name)) {
|
||||
search->instance = mdns_mem_strndup(name, MDNS_NAME_BUF_LEN - 1);
|
||||
if (!search->instance) {
|
||||
search_free(search);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mdns_utils_str_null_or_empty(service)) {
|
||||
search->service = mdns_mem_strndup(service, MDNS_NAME_BUF_LEN - 1);
|
||||
if (!search->service) {
|
||||
search_free(search);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mdns_utils_str_null_or_empty(proto)) {
|
||||
search->proto = mdns_mem_strndup(proto, MDNS_NAME_BUF_LEN - 1);
|
||||
if (!search->proto) {
|
||||
search_free(search);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
search->type = type;
|
||||
search->unicast = unicast;
|
||||
search->timeout = timeout;
|
||||
search->num_results = 0;
|
||||
search->max_results = max_results;
|
||||
search->result = NULL;
|
||||
search->state = SEARCH_INIT;
|
||||
search->sent_at = 0;
|
||||
search->started_at = xTaskGetTickCount() * portTICK_PERIOD_MS;
|
||||
search->notifier = notifier;
|
||||
search->next = NULL;
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Queue search action
|
||||
*/
|
||||
static esp_err_t send_search_action(mdns_action_type_t type, mdns_search_once_t *search)
|
||||
{
|
||||
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.search_add.search = search;
|
||||
if (!mdns_priv_queue_action(action)) {
|
||||
mdns_mem_free(action);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from parser to add TXT data to search result
|
||||
*/
|
||||
void mdns_priv_query_result_add_txt(mdns_search_once_t *search, 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_result_t *r = search->result;
|
||||
while (r) {
|
||||
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol) {
|
||||
if (r->txt) {
|
||||
goto free_txt;
|
||||
}
|
||||
r->txt = txt;
|
||||
r->txt_value_len = txt_value_len;
|
||||
r->txt_count = txt_count;
|
||||
mdns_priv_query_update_result_ttl(r, ttl);
|
||||
return;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
if (!search->max_results || search->num_results < search->max_results) {
|
||||
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->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 = search->result;
|
||||
search->result = r;
|
||||
search->num_results++;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Chain new IP to search result
|
||||
*/
|
||||
static void result_add_ip(mdns_result_t *r, esp_ip_addr_t *ip)
|
||||
{
|
||||
mdns_ip_addr_t *a = r->addr;
|
||||
while (a) {
|
||||
if (a->addr.type == ip->type) {
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
if (a->addr.type == ESP_IPADDR_TYPE_V4 && a->addr.u_addr.ip4.addr == ip->u_addr.ip4.addr) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (a->addr.type == ESP_IPADDR_TYPE_V6 && !memcmp(a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
a = mdns_priv_result_addr_create_ip(ip);
|
||||
if (!a) {
|
||||
return;
|
||||
}
|
||||
a->next = r->addr;
|
||||
r->addr = a;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from parser to add A/AAAA data to search result
|
||||
*/
|
||||
void mdns_priv_query_result_add_ip(mdns_search_once_t *search, const char *hostname, esp_ip_addr_t *ip,
|
||||
mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl)
|
||||
{
|
||||
mdns_result_t *r = NULL;
|
||||
mdns_ip_addr_t *a = NULL;
|
||||
|
||||
if ((search->type == MDNS_TYPE_A && ip->type == ESP_IPADDR_TYPE_V4)
|
||||
|| (search->type == MDNS_TYPE_AAAA && ip->type == ESP_IPADDR_TYPE_V6)
|
||||
|| search->type == MDNS_TYPE_ANY) {
|
||||
r = search->result;
|
||||
while (r) {
|
||||
if (r->esp_netif == mdns_priv_get_esp_netif(tcpip_if) && r->ip_protocol == ip_protocol) {
|
||||
result_add_ip(r, ip);
|
||||
mdns_priv_query_update_result_ttl(r, ttl);
|
||||
return;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
if (!search->max_results || search->num_results < search->max_results) {
|
||||
r = (mdns_result_t *)mdns_mem_malloc(sizeof(mdns_result_t));
|
||||
if (!r) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return;
|
||||
}
|
||||
|
||||
memset(r, 0, sizeof(mdns_result_t));
|
||||
|
||||
a = mdns_priv_result_addr_create_ip(ip);
|
||||
if (!a) {
|
||||
mdns_mem_free(r);
|
||||
return;
|
||||
}
|
||||
a->next = r->addr;
|
||||
r->hostname = mdns_mem_strdup(hostname);
|
||||
r->addr = a;
|
||||
r->esp_netif = mdns_priv_get_esp_netif(tcpip_if);
|
||||
r->ip_protocol = ip_protocol;
|
||||
r->next = search->result;
|
||||
r->ttl = ttl;
|
||||
search->result = r;
|
||||
search->num_results++;
|
||||
}
|
||||
} else if (search->type == MDNS_TYPE_PTR || search->type == MDNS_TYPE_SRV) {
|
||||
r = search->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(hostname, r->hostname)) {
|
||||
result_add_ip(r, ip);
|
||||
mdns_priv_query_update_result_ttl(r, ttl);
|
||||
break;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from parser to add SRV data to search result
|
||||
*/
|
||||
void mdns_priv_query_result_add_srv(mdns_search_once_t *search, const char *hostname, uint16_t port,
|
||||
mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, uint32_t ttl)
|
||||
{
|
||||
mdns_result_t *r = search->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(hostname, r->hostname)) {
|
||||
mdns_priv_query_update_result_ttl(r, ttl);
|
||||
return;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
if (!search->max_results || search->num_results < search->max_results) {
|
||||
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);
|
||||
if (!r->hostname) {
|
||||
mdns_mem_free(r);
|
||||
return;
|
||||
}
|
||||
if (search->instance) {
|
||||
r->instance_name = mdns_mem_strdup(search->instance);
|
||||
}
|
||||
r->service_type = mdns_mem_strdup(search->service);
|
||||
r->proto = mdns_mem_strdup(search->proto);
|
||||
r->port = port;
|
||||
r->esp_netif = mdns_priv_get_esp_netif(tcpip_if);
|
||||
r->ip_protocol = ip_protocol;
|
||||
r->ttl = ttl;
|
||||
r->next = search->result;
|
||||
search->result = r;
|
||||
search->num_results++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called from parser to add PTR data to search result
|
||||
*/
|
||||
mdns_result_t *mdns_priv_query_result_add_ptr(mdns_search_once_t *search, const char *instance,
|
||||
const char *service_type, const char *proto, mdns_if_t tcpip_if,
|
||||
mdns_ip_protocol_t ip_protocol, uint32_t ttl)
|
||||
{
|
||||
mdns_result_t *r = search->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_priv_query_update_result_ttl(r, ttl);
|
||||
return r;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
if (!search->max_results || search->num_results < search->max_results) {
|
||||
r = (mdns_result_t *)mdns_mem_malloc(sizeof(mdns_result_t));
|
||||
if (!r) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(r, 0, sizeof(mdns_result_t));
|
||||
r->instance_name = mdns_mem_strdup(instance);
|
||||
r->service_type = mdns_mem_strdup(service_type);
|
||||
r->proto = mdns_mem_strdup(proto);
|
||||
if (!r->instance_name) {
|
||||
mdns_mem_free(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->esp_netif = mdns_priv_get_esp_netif(tcpip_if);
|
||||
r->ip_protocol = ip_protocol;
|
||||
r->ttl = ttl;
|
||||
r->next = search->result;
|
||||
search->result = r;
|
||||
search->num_results++;
|
||||
return r;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mdns_ip_addr_t *mdns_priv_result_addr_create_ip(esp_ip_addr_t *ip)
|
||||
{
|
||||
mdns_ip_addr_t *a = (mdns_ip_addr_t *)mdns_mem_malloc(sizeof(mdns_ip_addr_t));
|
||||
if (!a) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
memset(a, 0, sizeof(mdns_ip_addr_t));
|
||||
a->addr.type = ip->type;
|
||||
if (ip->type == ESP_IPADDR_TYPE_V6) {
|
||||
memcpy(a->addr.u_addr.ip6.addr, ip->u_addr.ip6.addr, 16);
|
||||
} else {
|
||||
a->addr.u_addr.ip4.addr = ip->u_addr.ip4.addr;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MDNS_PUBLIC_API
|
||||
* */
|
||||
void mdns_query_results_free(mdns_result_t *results)
|
||||
{
|
||||
mdns_priv_service_lock();
|
||||
mdns_priv_query_results_free(results);
|
||||
mdns_priv_service_unlock();
|
||||
}
|
||||
|
||||
esp_err_t mdns_query_async_delete(mdns_search_once_t *search)
|
||||
{
|
||||
if (!search) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (search->state != SEARCH_OFF) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
mdns_priv_service_lock();
|
||||
search_free(search);
|
||||
mdns_priv_service_unlock();
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool mdns_query_async_get_results(mdns_search_once_t *search, uint32_t timeout, mdns_result_t **results, uint8_t *num_results)
|
||||
{
|
||||
if (xSemaphoreTake(search->done_semaphore, pdMS_TO_TICKS(timeout)) == pdTRUE) {
|
||||
if (results) {
|
||||
*results = search->result;
|
||||
}
|
||||
if (num_results) {
|
||||
*num_results = search->num_results;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
mdns_search_once_t *mdns_query_async_new(const char *name, const char *service, const char *proto, uint16_t type,
|
||||
uint32_t timeout, size_t max_results, mdns_query_notify_t notifier)
|
||||
{
|
||||
mdns_search_once_t *search = NULL;
|
||||
|
||||
if (!mdns_priv_is_server_init() || !timeout || mdns_utils_str_null_or_empty(service) != mdns_utils_str_null_or_empty(proto)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
search = search_init(name, service, proto, type, type != MDNS_TYPE_PTR, timeout, max_results, notifier);
|
||||
if (!search) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (send_search_action(ACTION_SEARCH_ADD, search)) {
|
||||
search_free(search);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
esp_err_t mdns_query_generic(const char *name, const char *service, const char *proto, uint16_t type, mdns_query_transmission_type_t transmission_type, uint32_t timeout, size_t max_results, mdns_result_t **results)
|
||||
{
|
||||
mdns_search_once_t *search = NULL;
|
||||
|
||||
*results = NULL;
|
||||
|
||||
if (!mdns_priv_is_server_init()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!timeout || mdns_utils_str_null_or_empty(service) != mdns_utils_str_null_or_empty(proto)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
search = search_init(name, service, proto, type, transmission_type == MDNS_QUERY_UNICAST, timeout, max_results,
|
||||
NULL);
|
||||
if (!search) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (send_search_action(ACTION_SEARCH_ADD, search)) {
|
||||
search_free(search);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
xSemaphoreTake(search->done_semaphore, portMAX_DELAY);
|
||||
|
||||
*results = search->result;
|
||||
search_free(search);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mdns_query(const char *name, const char *service_type, const char *proto, uint16_t type, uint32_t timeout, size_t max_results, mdns_result_t **results)
|
||||
{
|
||||
return mdns_query_generic(name, service_type, proto, type, type != MDNS_TYPE_PTR, timeout, max_results, results);
|
||||
}
|
||||
|
||||
esp_err_t mdns_query_ptr(const char *service, const char *proto, uint32_t timeout, size_t max_results, mdns_result_t **results)
|
||||
{
|
||||
if (mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return mdns_query(NULL, service, proto, MDNS_TYPE_PTR, timeout, max_results, results);
|
||||
}
|
||||
|
||||
esp_err_t mdns_query_srv(const char *instance, const char *service, const char *proto, uint32_t timeout, mdns_result_t **result)
|
||||
{
|
||||
if (mdns_utils_str_null_or_empty(instance) || mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return mdns_query(instance, service, proto, MDNS_TYPE_SRV, timeout, 1, result);
|
||||
}
|
||||
|
||||
esp_err_t mdns_query_txt(const char *instance, const char *service, const char *proto, uint32_t timeout, mdns_result_t **result)
|
||||
{
|
||||
if (mdns_utils_str_null_or_empty(instance) || mdns_utils_str_null_or_empty(service) || mdns_utils_str_null_or_empty(proto)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return mdns_query(instance, service, proto, MDNS_TYPE_TXT, timeout, 1, result);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_LWIP_IPV4
|
||||
esp_err_t mdns_query_a(const char *name, uint32_t timeout, esp_ip4_addr_t *addr)
|
||||
{
|
||||
mdns_result_t *result = NULL;
|
||||
esp_err_t err;
|
||||
|
||||
if (mdns_utils_str_null_or_empty(name)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (strstr(name, ".local")) {
|
||||
ESP_LOGW(TAG, "Please note that hostname must not contain domain name, as mDNS uses '.local' domain");
|
||||
}
|
||||
|
||||
err = mdns_query(name, NULL, NULL, MDNS_TYPE_A, timeout, 1, &result);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
mdns_ip_addr_t *a = result->addr;
|
||||
while (a) {
|
||||
if (a->addr.type == ESP_IPADDR_TYPE_V4) {
|
||||
addr->addr = a->addr.u_addr.ip4.addr;
|
||||
mdns_query_results_free(result);
|
||||
return ESP_OK;
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
|
||||
mdns_query_results_free(result);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
#endif /* CONFIG_LWIP_IPV4 */
|
||||
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
esp_err_t mdns_query_aaaa(const char *name, uint32_t timeout, esp_ip6_addr_t *addr)
|
||||
{
|
||||
mdns_result_t *result = NULL;
|
||||
esp_err_t err;
|
||||
|
||||
if (mdns_utils_str_null_or_empty(name)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (strstr(name, ".local")) {
|
||||
ESP_LOGW(TAG, "Please note that hostname must not contain domain name, as mDNS uses '.local' domain");
|
||||
}
|
||||
|
||||
err = mdns_query(name, NULL, NULL, MDNS_TYPE_AAAA, timeout, 1, &result);
|
||||
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
mdns_ip_addr_t *a = result->addr;
|
||||
while (a) {
|
||||
if (a->addr.type == ESP_IPADDR_TYPE_V6) {
|
||||
memcpy(addr->addr, a->addr.u_addr.ip6.addr, 16);
|
||||
mdns_query_results_free(result);
|
||||
return ESP_OK;
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
|
||||
mdns_query_results_free(result);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
#endif /* CONFIG_LWIP_IPV6 */
|
||||
1281
components/mdns/mdns_receive.c
Normal file
1281
components/mdns/mdns_receive.c
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user