Compare commits

..

44 Commits

Author SHA1 Message Date
b9b4a75000 mdns: bump the component version to 1.0.8 2023-02-06 15:43:01 +01:00
cc259aa035 Merge pull request #203 from david-cermak/feat/mdns_reverse_query
feat(mdns): Add support for IPv4 reverse query
2023-02-03 15:21:38 +01:00
91134f10ff mdns: Make reverse query conditional per Kconfig
and auto-register reverse logical names
2023-02-02 10:32:01 +01:00
fb8a2f0198 fix(mdns): Host test with IDFv5.1 2023-02-02 10:31:01 +01:00
b87bef52e5 feat(mdns): Add support for IPv4 reverse query
Partially addresses https://github.com/espressif/esp-protocols/issues/101
2023-02-02 10:31:00 +01:00
28813148b2 Merge pull request #211 from gabsuren/websocket/add_new_api_esp_destroy_selected
websocket: Added new API `esp_websocket_client_destroy_on_exit` (IDF-6458)
2023-02-01 18:00:30 +04:00
ae38f27997 Merge pull request #206 from david-cermak/bugfix/mdns_unit_tests
fix(mdns): Make unit test executable with pytest
2023-02-01 12:28:52 +01:00
d0c9070715 fix(mdns): Remove strict mode as it's invalid
Strict mode was introduced to support "one-shot" queries (described in
RFC6762/sec5.1) that are sent by lwip or dig. It was incorrectly assumed
that responding to such queries violates the spec, as we have to repeat
queries in responces, which is forbidden in RFC6762/sec6. It is however
required to repeat query fields according to the Section 6.7. Legacy
Unicast Responses: "it MUST repeat the query ID and the question
given in the query message."
2023-01-31 16:06:52 +01:00
a8339e4618 fix(mdns): Allow setting instance name only after hostname set
Closes https://github.com/espressif/esp-protocols/issues/190
2023-01-31 16:06:52 +01:00
12cfcb5aed fix(mdns): Make unit test executable with pytest 2023-01-31 16:06:52 +01:00
2cfffb056e Merge pull request #214 from david-cermak/bugfix/modem_cxx_broken_components
fix(esp_modem): Fix cmux client compilation issue
2023-01-31 14:42:46 +01:00
0247926219 fix(mdns): AFL port layer per IDF-latest changes 2023-01-31 11:15:06 +01:00
05fff94b91 update(esp_modem): Bump component version to 0.1.26
Closes https://github.com/espressif/esp-protocols/issues/213
Closes https://github.com/espressif/esp-protocols/issues/212
2023-01-31 11:02:52 +01:00
8958d5e37c fix(esp-modem): Example to use variable mqtt topic/data
This example is used in the CI with public broker and
users playing with the example can influence stability
of tests if we share the same topic id
2023-01-31 11:02:52 +01:00
f71192b876 fix(esp_modem): Fix cmux client compilation issue
Removed dependency on esp_event_cxx
2023-01-30 15:30:48 +01:00
f9b47900f2 websocket: Added new API esp_websocket_client_destroy_on_exit 2023-01-24 22:29:09 +04:00
0e215b118f Added the missing definition for the esp_modem_set_baud function (IDFGH-9181) (#209)
fix(esp_modem): Added the missing definition for the esp_modem_set_baud function
2023-01-19 13:57:06 +01:00
dc8b916561 Merge pull request #205 from gabsuren/docs/fix_documentation
docs: Disable code coverage report publishing to Github Pages
2023-01-13 17:54:04 +04:00
a01096f621 docs: Disable code coverage report publishing to Github Pages 2023-01-13 17:30:33 +04:00
3de4727385 Merge pull request #202 from gabsuren/CI/minor_fix_ci
CI: minor fix of host test *yml file
2023-01-12 20:02:12 +01:00
59053b8c98 CI: minor fix of host test *yml file 2023-01-09 22:09:53 +04:00
2956477353 Merge pull request #196 from gabsuren/CI/gcovr_analyser
CI: Added coverage analyzer for esp_modem host tests
2023-01-09 14:09:11 +01:00
1d9b6f7e90 Merge pull request #199 from franz-ms-muc/patch-1
Update esp_modem_command_library.cpp (IDFGH-9018)
2023-01-04 12:30:45 +01:00
6b684ceb0f CI: Added coverage analyzer for esp_modem host tests 2022-12-30 13:11:59 +04:00
5304a6ec0e Update esp_modem_command_library.cpp
AT+IFC=2,2 instead AT+IFC=2, 2
2022-12-22 11:55:37 +01:00
c7f20ee98e Merge pull request #191 from gabsuren/CI/refactor_yml_files
CI: Minor refactoring of *yml files for the target tests
2022-12-16 11:32:14 +04:00
7d47ec3b6b Merge pull request #192 from XDanielPaul/master
Added badges with version of components to the respective README files (IDFGH-8940)
2022-12-14 12:47:48 +01:00
e4c8a5932c Added badges with version of components to the respective README files 2022-12-12 18:13:04 +01:00
b56b1c577b CI: Minor refactoring of *yml files for target tests 2022-12-12 12:31:44 +04:00
4e212c31fe Merge pull request #182 from david-cermak/feat/modem_target_test
feat(esp_modem): Added target test
2022-12-09 15:26:26 +01:00
9a7bd90ad9 fix(esp_modem): Exit data mode only after state change
Previously, we set the notification also for  event
causing permature PPP mode exit
2022-12-01 18:35:06 +01:00
4314c78ca0 feat(esp_modem): Added target test 2022-12-01 18:35:01 +01:00
457f8335bb Merge pull request #169 from david-cermak/feat/modem_dce_on_read
fix(esp-modem): Extend CMUX mode to support manual transitions
2022-11-23 20:24:26 +01:00
42565ff5d7 Merge pull request #177 from gabsuren/CI/asio_fix
CI: Fix ASIO example test
2022-11-23 20:23:20 +01:00
6e2bb518fd CI: Fix ASIO example test 2022-11-23 19:07:26 +04:00
2180ab17d8 fix(esp-modem): Support AT with callback in C-API
Closes https://github.com/espressif/esp-protocols/issues/143
2022-11-21 14:58:15 +01:00
ac5d43882b feat(esp-modem): Add support for manual CMUX operations
Closes https://github.com/espressif/esp-protocols/issues/168
2022-11-21 14:58:15 +01:00
3f0262dfe5 Merge pull request #176 from gabsuren/CI/flake_use_github
CI: Updated flake8 to use github repo
2022-11-21 14:41:43 +01:00
74787c9aa8 CI: Updated flake8 to use github repo 2022-11-21 16:31:20 +04:00
045009bb82 Merge pull request #173 from mahavirj/bugfix/some_issue_with_coverity_scan
mdns: fix some coverity reported issues
2022-11-07 15:11:08 +01:00
79c11e9ecd Merge pull request #171 from gabsuren/feature/make_public_websocket_client_send_with_opcode
websocket: make `esp_websocket_client_send_with_opcode` a public API (IDFGH-6937)
2022-11-07 14:28:36 +01:00
c73c797595 mdns: fix some coverity reported issues
- Array compared against 0 NO_EFFECT
- dereference null pointer, FORWARD_NULL check
2022-11-07 18:38:06 +05:30
1547aa8c03 Merge pull request #166 from espressif-abhikroy/esp_modem/power_saving_mode
esp_modem/examples: Added example Power Saving Mode(PSM) in LTE modem.
2022-11-07 08:36:30 +01:00
1ca139109a esp_modem/examples: Add an example to synchronise PSM sleep in Sim70XX modem and esp32.
This example enables Power saving mode in Sim70XX modem and tries to synchronise the sleep cycle of the modem with esp32.
When the modem wakes up from PSM sleep it wakes up the esp32. While the modem is awake esp32 is set to light sleep.

Closes: https://github.com/espressif/esp-protocols/issues/54
2022-11-06 18:52:50 +05:30
90 changed files with 1990 additions and 899 deletions

92
.github/workflows/gcov_analyzer.yml vendored Normal file
View File

@ -0,0 +1,92 @@
name: Code Coverage Analyzer
on: [push, pull_request]
jobs:
gcovr_analyzer_esp_modem:
name: Run gcovr on esp modem host test
runs-on: ubuntu-22.04
container: espressif/idf:release-v4.3
env:
lwip: lwip-2.1.2
lwip_contrib: contrib-2.1.0
lwip_uri: http://download.savannah.nongnu.org/releases/lwip
COMP_DIR: esp-protocols/components/esp_modem
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
with:
path: esp-protocols
persist-credentials: false
- name: Build and Test
shell: bash
run: |
apt-get update
apt-get update && apt-get install -y gcc-8 g++-8 python3-pip
apt-get install -y rsync
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8
export DEBIAN_FRONTEND=noninteractive
export LWIP_PATH=`pwd`/${{ env.lwip }}
export LWIP_CONTRIB_PATH=`pwd`/${{ env.lwip_contrib }}
. ${IDF_PATH}/export.sh
${{ env.COMP_DIR }}/test/host_test/env.sh $lwip $lwip_uri $lwip_contrib
cd $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/test/host_test
cat sdkconfig.ci.coverage >> sdkconfig.defaults
idf.py build
./build/host_modem_test.elf
- name: Run gcovr
shell: bash
run: |
python -m pip install gcovr
cd $GITHUB_WORKSPACE/${{ env.COMP_DIR }}
gcov-8 `find . -name "esp_modem*gcda" -printf '%h\n' | head -n 1`/*
gcovr --gcov-ignore-parse-errors -g -k -r . --html index.html -x esp_modem_coverage.xml
mkdir docs_gcovr
cp $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/index.html docs_gcovr
touch docs_gcovr/.nojekyll
- name: Code Coverage Summary Report
uses: irongut/CodeCoverageSummary@v1.3.0
with:
filename: esp-protocols/**/esp_modem_coverage.xml
badge: true
fail_below_min: false
format: markdown
hide_branch_rate: false
hide_complexity: false
indicators: true
output: both
thresholds: '60 80'
- name: Write to Job Summary
run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY
- name: Upload artifacts
uses: actions/upload-artifact@v3
if: always()
with:
name: docs_gcovr
path: |
${{ env.COMP_DIR }}/docs_gcovr
if-no-files-found: error
# show_report_data:
# name: Publish-Results
# if: github.ref == 'refs/heads/master' || github.repository != 'espressif/esp-protocols'
# runs-on: ubuntu-22.04
# needs: gcovr_analyzer_esp_modem
# steps:
# - name: Checkout 🛎️
# uses: actions/checkout@v3
# with:
# persist-credentials: false
# - name: Download Artifacts
# uses: actions/download-artifact@v1
# with:
# name: docs_gcovr
#
# - name: Deploy generated docs
# uses: JamesIves/github-pages-deploy-action@v4
# with:
# branch: gh-pages
# folder: 'docs_gcovr'

View File

@ -3,16 +3,15 @@ name: Host test
on: [push, pull_request]
jobs:
host_test:
name: Build and Test on Host
host_test_esp_modem:
name: esp-modem Build and Test on Host
runs-on: ubuntu-20.04
container: espressif/idf:release-v4.3
env:
lwip: lwip-2.1.2
lwip_contrib: contrib-2.1.0
lwip_uri: http://download.savannah.nongnu.org/releases/lwip
COMP_DIR: esp-protocols/components/esp_modem
steps:
- name: Checkout esp-protocols
uses: actions/checkout@master
@ -26,11 +25,8 @@ jobs:
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8
export LWIP_PATH=`pwd`/${{ env.lwip }}
export LWIP_CONTRIB_PATH=`pwd`/${{ env.lwip_contrib }}
wget --no-verbose ${lwip_uri}/${lwip}.zip
unzip -oq ${lwip}.zip
wget --no-verbose ${lwip_uri}/${lwip_contrib}.zip
unzip -oq ${lwip_contrib}.zip
. ${IDF_PATH}/export.sh
$GITHUB_WORKSPACE/${{ env.COMP_DIR }}/test/host_test/env.sh $lwip $lwip_uri $lwip_contrib
cd $GITHUB_WORKSPACE/esp-protocols/components/esp_modem/examples/linux_modem
idf.py build
cd $GITHUB_WORKSPACE/esp-protocols/components/esp_modem/test/host_test
@ -42,3 +38,26 @@ jobs:
if: always()
with:
files: esp-protocols/components/esp_modem/test/host_test/junit.xml
host_test_mdns:
name: mdns Build and Test on Host
runs-on: ubuntu-20.04
container: espressif/idf:latest
steps:
- name: Checkout esp-protocols
uses: actions/checkout@master
with:
path: esp-protocols
- name: Build and Test
shell: bash
run: |
apt-get update && apt-get install -y dnsutils gcc g++
. ${IDF_PATH}/export.sh
cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/tests/host_test
idf.py build
./build/mdns_host.elf &
dig +short -p 5353 @224.0.0.251 myesp.local > ip.txt
cat ip.txt | xargs dig +short -p 5353 @224.0.0.251 -x
cat ip.txt

View File

@ -6,7 +6,7 @@ jobs:
build_esp_modem:
strategy:
matrix:
idf_ver: ["latest", "release-v4.1", "release-v4.2", "release-v4.3", "release-v4.4"]
idf_ver: ["latest", "release-v4.1", "release-v4.2", "release-v4.3", "release-v4.4", "release-v5.0"]
example: ["pppos_client", "modem_console", "ap_to_pppos", "simple_cmux_client"]
idf_target: ["esp32"]
exclude:
@ -62,7 +62,7 @@ jobs:
matrix:
idf_ver: ["latest"]
idf_target: ["esp32", "esp32s2", "esp32c3"]
config: ["eth_custom_netif", "eth_def", "eth_no_ipv6", "eth_socket"]
test: [ { app: example, path: "components/mdns/examples" }, { app: unit_test, path: "components/mdns/tests/unit_test" } ]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
@ -70,40 +70,22 @@ jobs:
uses: actions/checkout@v3
with:
submodules: recursive
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }} for ${{ matrix.config }}
env:
IDF_TARGET: ${{ matrix.idf_target }}
- name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
shell: bash
working-directory: components/mdns/examples/
working-directory: ${{ matrix.test.path }}
run: |
${IDF_PATH}/install.sh --enable-pytest
. ${IDF_PATH}/export.sh
rm -rf sdkconfig sdkconfig.defaults build build_${{ matrix.config }}
cat sdkconfig.ci.${{ matrix.config }} >> sdkconfig.defaults
idf.py set-target ${{ matrix.idf_target }}
idf.py build
mv build build_${{ matrix.config }}
- name: Merge binaries with IDF-${{ matrix.idf_ver }} for ${{ matrix.config }}
working-directory: components/mdns/examples
env:
IDF_TARGET: ${{ matrix.idf_target }}
shell: bash
run: |
. ${IDF_PATH}/export.sh
cd build_${{ matrix.config }}
esptool.py --chip ${{ matrix.idf_target }} merge_bin --fill-flash-size 4MB -o flash_image.bin @flash_args
cd ../
cat build_${{ matrix.config }}/config/sdkconfig.json
- uses: actions/upload-artifact@v2
python $IDF_PATH/tools/ci/ci_build_apps.py . --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app
for dir in `ls -d build_*`; do
$GITHUB_WORKSPACE/ci/clean_build_artifacts.sh `pwd`/$dir
zip -qur artifacts.zip $dir
done
- uses: actions/upload-artifact@v3
if: ${{ matrix.idf_target }} == "esp32"
with:
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.config }}
path: |
components/mdns/examples/build_${{ matrix.config }}/bootloader/bootloader.bin
components/mdns/examples/build_${{ matrix.config }}/partition_table/partition-table.bin
components/mdns/examples/build_${{ matrix.config }}/*.bin
components/mdns/examples/build_${{ matrix.config }}/*.elf
components/mdns/examples/build_${{ matrix.config }}/flasher_args.json
components/mdns/examples/build_${{ matrix.config }}/config/sdkconfig.h
components/mdns/examples/build_${{ matrix.config }}/config/sdkconfig.json
name: mdns_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.test.app }}
path: ${{ matrix.test.path }}/artifacts.zip
if-no-files-found: error
build_asio:
@ -114,13 +96,15 @@ jobs:
example: ["asio_chat", "async_request", "socks4", "ssl_client_server", "tcp_echo_server", "udp_echo_server"]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
env:
TEST_DIR: components/asio/examples
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
with:
submodules: recursive
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
working-directory: components/asio/examples/${{ matrix.example }}
working-directory: ${{ env.TEST_DIR }}/${{ matrix.example }}
env:
IDF_TARGET: ${{ matrix.idf_target }}
shell: bash
@ -130,7 +114,7 @@ jobs:
idf.py set-target ${{ matrix.idf_target }}
idf.py build
- name: Merge binaries with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }} for ${{ matrix.example }}
working-directory: components/asio/examples/${{ matrix.example }}/build
working-directory: ${{ env.TEST_DIR }}/${{ matrix.example }}/build
env:
IDF_TARGET: ${{ matrix.idf_target }}
shell: bash
@ -141,13 +125,13 @@ jobs:
with:
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.example }}
path: |
components/asio/examples/${{ matrix.example }}/build/bootloader/bootloader.bin
components/asio/examples/${{ matrix.example }}/build//partition_table/partition-table.bin
components/asio/examples/${{ matrix.example }}/build/*.bin
components/asio/examples/${{ matrix.example }}/build/*.elf
components/asio/examples/${{ matrix.example }}/build/flasher_args.json
components/asio/examples/${{ matrix.example }}/build/config/sdkconfig.h
components/asio/examples/${{ matrix.example }}/build/config/sdkconfig.json
${{ env.TEST_DIR }}/${{ matrix.example }}/build/bootloader/bootloader.bin
${{ env.TEST_DIR }}/${{ matrix.example }}/build//partition_table/partition-table.bin
${{ env.TEST_DIR }}/${{ matrix.example }}/build/*.bin
${{ env.TEST_DIR }}/${{ matrix.example }}/build/*.elf
${{ env.TEST_DIR }}/${{ matrix.example }}/build/flasher_args.json
${{ env.TEST_DIR }}/${{ matrix.example }}/build/config/sdkconfig.h
${{ env.TEST_DIR }}/${{ matrix.example }}/build/config/sdkconfig.json
if-no-files-found: error
@ -158,6 +142,8 @@ jobs:
idf_target: ["esp32"]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
env:
TEST_DIR: components/esp_websocket_client/examples
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
@ -167,30 +153,30 @@ jobs:
env:
IDF_TARGET: ${{ matrix.idf_target }}
shell: bash
working-directory: components/esp_websocket_client/examples/
working-directory: ${{ env.TEST_DIR }}
run: |
. ${IDF_PATH}/export.sh
cat sdkconfig.ci >> sdkconfig.defaults
idf.py build
- name: Merge binaries
working-directory: components/esp_websocket_client/examples/build
working-directory: ${{ env.TEST_DIR }}/build
env:
IDF_TARGET: ${{ matrix.idf_target }}
shell: bash
run: |
. ${IDF_PATH}/export.sh
esptool.py --chip ${{ matrix.idf_target }} merge_bin --fill-flash-size 4MB -o flash_image.bin @flash_args
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}
path: |
components/esp_websocket_client/examples/build/bootloader/bootloader.bin
components/esp_websocket_client/examples/build/partition_table/partition-table.bin
components/esp_websocket_client/examples/build/*.bin
components/esp_websocket_client/examples/build/*.elf
components/esp_websocket_client/examples/build/flasher_args.json
components/esp_websocket_client/examples/build/config/sdkconfig.h
components/esp_websocket_client/examples/build/config/sdkconfig.json
${{ env.TEST_DIR }}/build/bootloader/bootloader.bin
${{ env.TEST_DIR }}/build/partition_table/partition-table.bin
${{ env.TEST_DIR }}/build/*.bin
${{ env.TEST_DIR }}/build/*.elf
${{ env.TEST_DIR }}/build/flasher_args.json
${{ env.TEST_DIR }}/build/config/sdkconfig.h
${{ env.TEST_DIR }}/build/config/sdkconfig.json
if-no-files-found: error
run-target-websocket:
@ -204,14 +190,16 @@ jobs:
runs-on:
- self-hosted
- ESP32-ETHERNET-KIT
env:
TEST_DIR: components/esp_websocket_client/examples
# Skip running on forks since it won't have access to secrets
if: github.repository == 'espressif/esp-protocols'
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v3
with:
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}
path: components/esp_websocket_client/examples/build/
path: ${{ env.TEST_DIR }}/build/
- name: Install Python packages
env:
PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple"
@ -220,22 +208,22 @@ jobs:
- name: Download Example Test to target
run: python -m esptool --chip ${{ matrix.idf_target }} write_flash 0x0 components/esp_websocket_client/examples/build/flash_image.bin
- name: Run Example Test on target
working-directory: components/esp_websocket_client/examples
working-directory: ${{ env.TEST_DIR }}
run: |
python -m pytest --log-cli-level DEBUG --junit-xml=./examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}.xml --target=${{ matrix.idf_target }}
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
if: always()
with:
name: examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}
path: components/esp_websocket_client/examples/*.xml
path: ${{ env.TEST_DIR }}/*.xml
run-target-mdns:
strategy:
matrix:
idf_ver: ["latest"]
idf_target: ["esp32"]
config: ["eth_custom_netif", "eth_def", "eth_no_ipv6", "eth_socket"]
name: Run mDNS Example Test on target
test: [ { app: example, path: "components/mdns/examples" }, { app: unit_test, path: "components/mdns/tests/unit_test" } ]
name: Run mDNS target tests
needs: build_mdns
runs-on:
- self-hosted
@ -246,31 +234,29 @@ jobs:
- name: Clear repository
run: sudo rm -fr $GITHUB_WORKSPACE && mkdir $GITHUB_WORKSPACE
- uses: actions/checkout@v3
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v3
with:
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.config }}
path: components/mdns/examples/build_${{ matrix.config }}
name: mdns_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.test.app }}
path: ${{ matrix.test.path }}/ci/
- 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 }}
- name: Run ${{ matrix.test.app }} application on ${{ matrix.idf_target }}
working-directory: ${{ matrix.test.path }}
run: |
python -m esptool --chip ${{ matrix.idf_target }} write_flash 0x0 components/mdns/examples/build_${{ matrix.config }}/flash_image.bin
- name: Run Example Test on target ${{ matrix.config }}
working-directory: components/mdns/examples
run: |
rm -rf build
mv build_${{ matrix.config }} build
cat sdkconfig.ci.${{ matrix.config }} >> sdkconfig.defaults
python -m pytest --log-cli-level DEBUG --junit-xml=./examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.config }}.xml --target=${{ matrix.idf_target }}
unzip ci/artifacts.zip -d ci
for dir in `ls -d ci/build_*`; do
rm -rf build sdkconfig.defaults
- uses: actions/upload-artifact@v2
mv $dir build
python -m pytest --log-cli-level DEBUG --junit-xml=./results_${{ matrix.test.app }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${dir#"ci/build_"}.xml --target=${{ matrix.idf_target }}
done
- uses: actions/upload-artifact@v3
if: always()
with:
name: examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.config }}
path: components/mdns/examples/*.xml
name: results_${{ matrix.test.app }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }}.xml
path: ${{ matrix.test.path }}/*.xml
run-target-asio:
strategy:
@ -283,16 +269,18 @@ jobs:
runs-on:
- self-hosted
- ESP32-ETHERNET-KIT
env:
TEST_DIR: components/asio/examples
# Skip running on forks since it won't have access to secrets
if: github.repository == 'espressif/esp-protocols'
steps:
- name: Clear repository
run: sudo rm -fr $GITHUB_WORKSPACE && mkdir $GITHUB_WORKSPACE
- uses: actions/checkout@v3
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v3
with:
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.example }}
path: components/asio/examples/${{ matrix.example }}/build
path: ${{ env.TEST_DIR }}/${{ matrix.example }}/build
- name: Install Python packages
env:
PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple"
@ -300,13 +288,75 @@ jobs:
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 components/asio/examples/${{ matrix.example }}/build/flash_image.bin
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: components/asio/examples/${{ matrix.example }}
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 }}
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
if: always()
with:
name: examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.example }}
path: components/asio/examples/${{ matrix.example }}/*.xml
path: ${{ env.TEST_DIR }}/${{ matrix.example }}/*.xml
build_esp_modem_tests:
strategy:
matrix:
idf_ver: ["latest"]
idf_target: ["esp32c3"]
test: [ { app: pppd, path: test/target }, { app: sim800, path: examples/pppos_client } ]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
env:
TEST_DIR: components/esp_modem/${{ matrix.test.path }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
with:
submodules: recursive
- name: Build esp-modem target tests with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
env:
IDF_TARGET: ${{ matrix.idf_target }}
SDKCONFIG: sdkconfig.ci.${{ matrix.test.app }}
shell: bash
working-directory: ${{ env.TEST_DIR }}
run: |
. ${IDF_PATH}/export.sh
rm -rf sdkconfig build
[ -f ${SDKCONFIG} ] && cp ${SDKCONFIG} sdkconfig.defaults
idf.py set-target ${{ matrix.idf_target }}
idf.py build
$GITHUB_WORKSPACE/ci/clean_build_artifacts.sh ${GITHUB_WORKSPACE}/${TEST_DIR}/build
- uses: actions/upload-artifact@v3
with:
name: modem_target_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.test.app }}
path: ${{ env.TEST_DIR }}/build
if-no-files-found: error
run_esp_modem_tests:
strategy:
matrix:
idf_ver: ["latest"]
idf_target: ["esp32c3"]
test: [ { app: pppd, path: test/target }, { app: sim800, path: examples/pppos_client } ]
name: Run esp_modem Test on target
needs: build_esp_modem_tests
runs-on:
- self-hosted
- BrnoRPI-GH006
env:
TEST_DIR: components/esp_modem/${{ matrix.test.path }}
# Skip running on forks since it won't have access to secrets
if: github.repository == 'espressif/esp-protocols'
steps:
- name: Clear repository
run: sudo rm -fr $GITHUB_WORKSPACE && mkdir $GITHUB_WORKSPACE
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: modem_target_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.test.app }}
path: ${{ env.TEST_DIR }}/build
- name: Run Example Test on target
working-directory: ${{ env.TEST_DIR }}
run: |
python -m pytest --log-cli-level DEBUG --target=${{ matrix.idf_target }}

View File

@ -12,7 +12,7 @@ repos:
- id: trailing-whitespace
- id: mixed-line-ending
- id: debug-statements
- repo: https://gitlab.com/pycqa/flake8
- repo: https://github.com/pycqa/flake8
rev: 5.0.4
hooks:
- id: flake8

7
ci/clean_build_artifacts.sh Executable file
View File

@ -0,0 +1,7 @@
# Remove everything, but
# - elf/bin files in the build dir
# - partition-table and bootloader binaries
# - flasher args
# - sdkconfigs (header and json)
# (Ignoring the command failure as it refuses to delete nonempty dirs)
find $1 ! -regex ".*/build[^/]*/[^/]+.\(bin\|elf\)" -a ! -regex ".*\(bootloader\|partition-table\).bin" -a ! -name "flasher_args.json" -a ! -regex ".*/build[^/]*/config/sdkconfig.\(h\|json\)" -delete || true

View File

@ -5,3 +5,4 @@ SimpleWebSocketServer
dpkt
pytest
idf_build_apps
netifaces

View File

@ -1,5 +1,7 @@
# ASIO port
[![Component Registry](https://components.espressif.com/components/espressif/asio/badge.svg)](https://components.espressif.com/components/espressif/asio)
Asio is a cross-platform C++ library, see https://think-async.com/Asio/. It provides a consistent asynchronous model using a modern C++ approach.
## Examples

View File

@ -1,2 +1,8 @@
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_COMPILER_CXX_EXCEPTIONS=y
#
# Partition Table
#
# Leave some room for larger apps without needing to reduce other features
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y

View File

@ -47,3 +47,15 @@ if(${target} STREQUAL "linux")
# This is needed for ESP_LOGx() macros, as integer formats differ on ESP32(..) and x64
set_target_properties(${COMPONENT_LIB} PROPERTIES COMPILE_FLAGS -Wno-format)
endif()
if(CONFIG_GCOV_ENABLED)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
MARK_AS_ADVANCED(
CMAKE_CXX_FLAGS_COVERAGE
CMAKE_C_FLAGS_COVERAGE
CMAKE_EXE_LINKER_FLAGS_COVERAGE
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
endif()

View File

@ -1,5 +1,7 @@
# ESP MODEM
[![Component Registry](https://components.espressif.com/components/espressif/esp_modem/badge.svg)](https://components.espressif.com/components/espressif/esp_modem)
The `esp-modem` component is a managed component for `esp-idf` that is used for communication with GSM/LTE modems
that support AT commands and PPP protocol as a network interface.

View File

@ -210,7 +210,17 @@ extern "C" void app_main(void)
if (c->get_count_of(&SetModeArgs::mode)) {
auto mode = c->get_string_of(&SetModeArgs::mode);
modem_mode dev_mode;
if (mode == "CMD") {
if (mode == "CMUX1") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_MODE;
} else if (mode == "CMUX2") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_EXIT;
} else if (mode == "CMUX3") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_SWAP;
} else if (mode == "CMUX4") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_DATA;
} else if (mode == "CMUX5") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_COMMAND;
} else if (mode == "CMD") {
dev_mode = esp_modem::modem_mode::COMMAND_MODE;
} else if (mode == "PPP") {
dev_mode = esp_modem::modem_mode::DATA_MODE;

View File

@ -0,0 +1,10 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS "../..")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(modem_psm)

View File

@ -0,0 +1,32 @@
# Modem Power Saving Mode, sleep modes example
## Overview
This example demonstrates Power Saving Mode(PSM) in Sim70XX modems along with Deep sleep and Light Sleep modes in esp32.
This example enables Power saving mode in Sim70XX modem and tries to synchronise the sleep cycle of the modem with esp32.
When the modem wakes up from PSM sleep it wakes up the esp32. While the modem is awake esp32 goes to light sleep.
This example is tested on a SIM7080 modem.
## Supported devices
This example is supported only on ESP32, ESP32-S2 and ESP32-S3.
## Configuration
* EXAMPLE_MODEM_UART_TX_PIN: Connect this pin to the UART Tx pin of the modem.
* EXAMPLE_MODEM_UART_RX_PIN: Connect this pin to the UART Rx pin of the modem.
* EXAMPLE_MODEM_PWRKEY_PIN: Connect this pin to the PWRKEY pin of the modem.
* EXAMPLE_MODEM_STATUS_PIN: Connect this pin to the Status pin of the modem.
The status pin might not be exposed on a modem. It needs to tapped out from the LED indicator marked as STS.
* EXAMPLE_MODEM_LIGHT_SLEEP_DURATION: Duration for which the esp32 will go into light sleep, while the modem is awake.
If the esp32 goes into deep sleep while the modem is awake, esp32 will be awaken by the STATUS pin. So it's recommended to go to light sleep while the modem is awake.
* EXAMPLE_MODEM_T3412_PERIODIC_TAU: T3412 timer is the duration of one awake and sleep cycle of the modem in PSM.
* EXAMPLE_MODEM_T3324_ACTIVE_TIME: T3324 timer is the duration for which the modem stays PSM.
The timeout information is coded in bit format for T3412 and T3324 timer.

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "modem_psm.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,109 @@
menu "Example Configuration"
choice EXAMPLE_MODEM_DEVICE
prompt "Choose supported modem device (DCE)"
default EXAMPLE_MODEM_DEVICE_SIM7080
help
Select modem device connected to the ESP DTE.
config EXAMPLE_MODEM_DEVICE_SIM7070
bool "SIM7070"
help
SIM7070 is Multi-Band CAT M and NB IoT module.
config EXAMPLE_MODEM_DEVICE_SIM7080
bool "SIM7080"
help
SIM7080 is Multi-Band CAT M and NB IoT module.
endchoice
menu "UART Configuration"
config EXAMPLE_MODEM_UART_TX_PIN
int "TXD Pin Number"
default 4
range 0 31
help
Pin number of UART TX.
config EXAMPLE_MODEM_UART_RX_PIN
int "RXD Pin Number"
default 5
range 0 31
help
Pin number of UART RX.
config EXAMPLE_MODEM_UART_EVENT_TASK_STACK_SIZE
int "UART Event Task Stack Size"
range 2000 6000
default 4096
help
Stack size of UART event task.
config EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY
int "UART Event Task Priority"
range 3 22
default 5
help
Priority of UART event task.
config EXAMPLE_MODEM_UART_EVENT_QUEUE_SIZE
int "UART Event Queue Size"
range 10 40
default 30
help
Length of UART event queue.
config EXAMPLE_MODEM_UART_TX_BUFFER_SIZE
int "UART TX Buffer Size"
range 256 2048
default 512
help
Buffer size of UART TX buffer.
config EXAMPLE_MODEM_UART_RX_BUFFER_SIZE
int "UART RX Buffer Size"
range 256 2048
default 1024
help
Buffer size of UART RX buffer.
endmenu
config EXAMPLE_MODEM_PWRKEY_PIN
int "PWRKEY Pin Number"
default 18
range 0 31
help
Pin number connected to modem's power key pin.
config EXAMPLE_MODEM_STATUS_PIN
int "STATUS Pin Number"
default 19
range 0 31
help
Pin number connected to modem's status pin.
config EXAMPLE_MODEM_LIGHT_SLEEP_DURATION
int "Light Sleep Duration"
default 10
range 0 15000
help
Duration in seconds, of which the esp32 goes into light sleep while the modem is awake.
config EXAMPLE_MODEM_PPP_APN
string "Set MODEM APN"
default "internet"
help
Set APN (Access Point Name), a logical name to choose data network
config EXAMPLE_MODEM_T3412_PERIODIC_TAU
string "T3412 Requester Periodic TAU"
default "00000100"
help
T3412 timer, i.e the duration of one awake and sleep cycle of the modem in PSM.
config EXAMPLE_MODEM_T3324_ACTIVE_TIME
string "T3324 Requester Active Time"
default "00000001"
help
T3324 timer, i.e the duration for which the modem stays PSM.
endmenu

View File

@ -0,0 +1,202 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* Power Saving Mode(PSM) in Sim70XX modems along with
* Deep sleep and Light Sleep modes in esp32.
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "driver/uart.h"
#include "string.h"
#include "driver/gpio.h"
#include "esp_sleep.h"
#include "driver/rtc_io.h"
#include "esp_modem_api.h"
#include "esp_event.h"
#include "sdkconfig.h"
#define BUF_SIZE 1024
#define TXD_PIN ((gpio_num_t)CONFIG_EXAMPLE_MODEM_UART_TX_PIN)
#define RXD_PIN ((gpio_num_t)CONFIG_EXAMPLE_MODEM_UART_RX_PIN)
#define GPIO_INPUT_STATUS ((gpio_num_t)CONFIG_EXAMPLE_MODEM_STATUS_PIN)
#define GPIO_OUTPUT_PWRKEY ((gpio_num_t)CONFIG_EXAMPLE_MODEM_PWRKEY_PIN)
#define GPIO_OUTPUT_PIN_SEL (1ULL<<GPIO_OUTPUT_PWRKEY)
#define CHECK_ERR(cmd, success_action) do { \
esp_err_t ret = cmd; \
if (ret == ESP_OK) { \
success_action; \
} else { \
ESP_LOGE(TAG, "Failed with %s", ret == ESP_ERR_TIMEOUT ? "TIMEOUT":"ERROR"); \
} } while (0)
const char *TAG = "modem_psm";
RTC_DATA_ATTR static int boot_count = 0;
void dce_init(esp_modem_dce_t **dce, esp_netif_t **esp_netif)
{
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_PPP_APN);
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
*esp_netif = esp_netif_new(&netif_ppp_config);
assert(*esp_netif);
/* setup UART specific configuration based on kconfig options */
dte_config.uart_config.tx_io_num = CONFIG_EXAMPLE_MODEM_UART_TX_PIN;
dte_config.uart_config.rx_io_num = CONFIG_EXAMPLE_MODEM_UART_RX_PIN;
dte_config.uart_config.rx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE;
dte_config.uart_config.tx_buffer_size = CONFIG_EXAMPLE_MODEM_UART_TX_BUFFER_SIZE;
dte_config.uart_config.event_queue_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_QUEUE_SIZE;
dte_config.task_stack_size = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_STACK_SIZE;
dte_config.task_priority = CONFIG_EXAMPLE_MODEM_UART_EVENT_TASK_PRIORITY;
dte_config.dte_buffer_size = CONFIG_EXAMPLE_MODEM_UART_RX_BUFFER_SIZE / 2;
ESP_LOGI(TAG, "Initializing esp_modem for a generic module...");
*dce = esp_modem_new(&dte_config, &dce_config, *esp_netif);
assert(*dce);
}
void config_pwrkey_gpio(void)
{
gpio_config_t io_conf = {}; //zero-initialize the config structure.
io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt
io_conf.mode = GPIO_MODE_OUTPUT; //set as output mode
io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL; //bit mask of the pins that you want to set,e.g.GPIO18/19
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; //disable pull-down mode
io_conf.pull_up_en = GPIO_PULLUP_DISABLE; //disable pull-up mode
gpio_config(&io_conf); //configure GPIO with the given settings
}
void power_on_modem(esp_modem_dce_t *dce)
{
rtc_gpio_hold_dis(GPIO_OUTPUT_PWRKEY);
/* Power on the modem */
ESP_LOGI(TAG, "Power on the modem");
gpio_set_level(GPIO_OUTPUT_PWRKEY, 1);
vTaskDelay(pdMS_TO_TICKS(500));
gpio_set_level(GPIO_OUTPUT_PWRKEY, 0);
rtc_gpio_hold_en(GPIO_OUTPUT_PWRKEY);
vTaskDelay(pdMS_TO_TICKS(2000));
CHECK_ERR(esp_modem_sync(dce), ESP_LOGI(TAG, "OK"));
}
void power_down_modem(esp_modem_dce_t *dce)
{
char data[BUF_SIZE];
ESP_LOGI(TAG, "Power down the modem");
/* Power down the modem by AT command */
CHECK_ERR(esp_modem_at(dce, "AT+CPOWD=1", data, 500), ESP_LOGI(TAG, "OK. %s", data));
}
void run_at(esp_modem_dce_t *dce, uint8_t count)
{
for (int i = 0; i < count; i++) {
CHECK_ERR(esp_modem_sync(dce), ESP_LOGI(TAG, "OK"));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void run_at_ping(esp_modem_dce_t *dce)
{
char data[BUF_SIZE];
int rssi, ber;
CHECK_ERR(esp_modem_get_signal_quality(dce, &rssi, &ber), ESP_LOGI(TAG, "OK. rssi=%d, ber=%d", rssi, ber));
vTaskDelay(pdMS_TO_TICKS(1000));
CHECK_ERR(esp_modem_at(dce, "AT+CPSMS?", data, 500), ESP_LOGI(TAG, "OK. %s", data));
vTaskDelay(pdMS_TO_TICKS(1000));
CHECK_ERR(esp_modem_at(dce, "AT+CPSI?", data, 500), ESP_LOGI(TAG, "OK. %s", data)); // Inquiring UE system information
CHECK_ERR(esp_modem_at(dce, "AT+CNACT=0,1", data, 500), ESP_LOGI(TAG, "OK. %s", data)); // Activate the APP network
CHECK_ERR(esp_modem_at(dce, "AT+SNPDPID=0", data, 500), ESP_LOGI(TAG, "OK. %s", data)); // Select PDP index for PING
CHECK_ERR(esp_modem_at(dce, "AT+SNPING4=\"8.8.8.8\",3,16,1000", data, 500), ESP_LOGI(TAG, "OK. %s", data)); // Send IPv4 PING
CHECK_ERR(esp_modem_at(dce, "AT+CNACT=0,0", data, 500), ESP_LOGI(TAG, "OK. %s", data)); // Deactivate the APP network
}
void app_main(void)
{
char *cmd;
char data[BUF_SIZE];
esp_modem_dce_t *dce = NULL;
esp_netif_t *esp_netif = NULL;
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
ESP_LOGI(TAG, "Deep sleep Wake Up Cause: %d\n", wakeup_cause);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
dce_init(&dce, &esp_netif);
switch (wakeup_cause) {
case ESP_SLEEP_WAKEUP_UNDEFINED:
/* Power on the modem */
config_pwrkey_gpio();
power_on_modem(dce);
run_at(dce, 3);
/* Configure Power Saving Mode in the modem */
asprintf(&cmd, "AT+CPSMS=1,,,\"%s\",\"%s\"\r\n", CONFIG_EXAMPLE_MODEM_T3412_PERIODIC_TAU,
CONFIG_EXAMPLE_MODEM_T3324_ACTIVE_TIME);
CHECK_ERR(esp_modem_at(dce, cmd, data, 500), ESP_LOGI(TAG, "OK. %s", data));
free(cmd);
vTaskDelay(pdMS_TO_TICKS(1000));
/* Perform Communication tasks */
run_at_ping(dce);
vTaskDelay(pdMS_TO_TICKS(1000));
break;
case ESP_SLEEP_WAKEUP_EXT0:
run_at(dce, 3);
/* Perform Communication tasks */
run_at_ping(dce);
vTaskDelay(pdMS_TO_TICKS(1000));
break;
default:
ESP_LOGI(TAG, "Not a deep sleep reset: %d\n", wakeup_cause);
}
ESP_LOGI(TAG, "Light sleep before going to Deep sleep\n\n");
/* Enter light sleep mode as long as the modem is awake */
esp_sleep_enable_timer_wakeup(CONFIG_EXAMPLE_MODEM_LIGHT_SLEEP_DURATION * 1000000);
esp_light_sleep_start();
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TIMER); // Disabling timer before going to deep sleep.
wakeup_cause = esp_sleep_get_wakeup_cause();
ESP_LOGI(TAG, "Light sleep wakeup cause: %d\n", wakeup_cause);
/* DCE and netif clean-up */
/* In Deep-sleep mode, the CPUs, most of the RAM, and all digital peripherals are powered off.
* So dce and netif would be lost anyway if they are not explicitly destroyed.
* But since dce is also linked with UART communication with the modem it's better to exit gracefully before going to deep sleep.
*/
esp_modem_destroy(dce);
esp_netif_destroy(esp_netif);
/* Enable wakeup if status pin is high */
esp_sleep_enable_ext0_wakeup(GPIO_INPUT_STATUS, 1);
ESP_LOGI(TAG, "Entering deep sleep: %d\n", boot_count++);
esp_deep_sleep_start();
}

View File

@ -0,0 +1,4 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_LWIP_PPP_SUPPORT=y

View File

@ -184,4 +184,22 @@ menu "Example Configuration"
endmenu
config EXAMPLE_MQTT_BROKER_URI
string "MQTT Broker URL"
default "mqtt://mqtt.eclipseprojects.io"
help
URL of the mqtt broker which this example connects to.
config EXAMPLE_MQTT_TEST_TOPIC
string "MQTT topic to publish/subscribe"
default "/topic/esp-pppos"
help
MQTT topic, which we subscribe on and publish to.
config EXAMPLE_MQTT_TEST_DATA
string "MQTT data to publish/receive"
default "esp32-pppos"
help
MQTT data message, which we publish and expect to receive.
endmenu

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -30,7 +30,6 @@
#define EXAMPLE_FLOW_CONTROL ESP_MODEM_FLOW_CONTROL_HW
#endif
#define BROKER_URL "mqtt://mqtt.eclipseprojects.io"
static const char *TAG = "pppos_example";
static EventGroupHandle_t event_group = NULL;
@ -68,7 +67,7 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
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, "/topic/esp-pppos", 0);
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:
@ -76,7 +75,7 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
msg_id = esp_mqtt_client_publish(client, "/topic/esp-pppos", "esp32-pppos", 0, 0, 0);
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:
@ -271,11 +270,11 @@ void app_main(void)
/* Config MQTT */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_mqtt_client_config_t mqtt_config = {
.broker.address.uri = BROKER_URL,
.broker.address.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
};
#else
esp_mqtt_client_config_t mqtt_config = {
.uri = BROKER_URL,
.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
};
#endif
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);

View File

@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
from __future__ import print_function, unicode_literals
def test_pppos_connect(dut):
"""
steps:
1. initializes connection with SIM800
2. checks we get an IP
3. checks for the MQTT events
4. checks that the client cleanly disconnects
"""
# Check the sequence of connecting, publishing, disconnecting
dut.expect('Modem Connect to PPP Server')
# 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')
# Check that we have disconnected
dut.expect('User interrupted event')
# And can use commands again
dut.expect('IMSI=[0-9]+')

View File

@ -0,0 +1,14 @@
# Override some defaults to enable PPP
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_NOTIFY_PHASE_SUPPORT=y
CONFIG_LWIP_PPP_PAP_SUPPORT=y
CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096
CONFIG_LWIP_PPP_ENABLE_IPV6=n
CONFIG_EXAMPLE_MODEM_UART_TX_PIN=4
CONFIG_EXAMPLE_MODEM_UART_RX_PIN=5
CONFIG_EXAMPLE_MODEM_DEVICE_SIM800=y
CONFIG_EXAMPLE_MODEM_DEVICE_BG96=n
CONFIG_EXAMPLE_MODEM_PPP_APN="lpwa.vodafone.com"
CONFIG_EXAMPLE_MQTT_TEST_TOPIC="/ci/esp-modem/pppos-client"
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
CONFIG_ESP32_PANIC_PRINT_HALT=y

View File

@ -2,7 +2,7 @@
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS "../.." $ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component)
set(EXTRA_COMPONENT_DIRS "../..")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(simple_cmux_client)

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -17,10 +17,10 @@
#include "freertos/event_groups.h"
#include "esp_netif.h"
#include "esp_log.h"
#include "esp_event.h"
#include "cxx_include/esp_modem_dte.hpp"
#include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp"
#include "esp_event_cxx.hpp"
#include "simple_mqtt_client.hpp"
#include "esp_vfs_dev.h" // For optional VFS support
#include "esp_https_ota.h" // For potential OTA configuration
@ -39,16 +39,87 @@
using namespace esp_modem;
using namespace idf::event;
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;
StatusHandler()
{
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, this));
}
~StatusHandler()
{
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);
}
ip_event_t get_ip_event_type()
{
return ip_event_type;
}
private:
static void on_event(void *arg, esp_event_base_t base, int32_t event, void *data)
{
auto *handler = static_cast<StatusHandler *>(arg);
if (base == IP_EVENT) {
handler->ip_event(event, data);
} else {
handler->mqtt_event(event, data);
}
}
void ip_event(int32_t id, void *data)
{
if (id == IP_EVENT_PPP_GOT_IP) {
auto *event = (ip_event_got_ip_t *)data;
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));
signal.set(IP_Event);
} else if (id == IP_EVENT_PPP_LOST_IP) {
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;
};
extern "C" void app_main(void)
{
/* Init and register system/core components */
auto loop = std::make_shared<ESPEventLoop>();
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
/* Configure and create the DTE */
@ -145,45 +216,32 @@ extern "C" void app_main(void)
#endif
/* Try to connect to the network and publish an mqtt topic */
ESPEventHandlerSync event_handler(loop);
event_handler.listen_to(ESPEvent(IP_EVENT, ESPEventID(ESP_EVENT_ANY_ID)));
auto result = event_handler.wait_event_for(std::chrono::milliseconds(60000));
if (result.timeout) {
StatusHandler handler;
if (!handler.wait_for(StatusHandler::IP_Event, 60000)) {
ESP_LOGE(TAG, "Cannot get IP within specified timeout... exiting");
return;
} else if (result.event.id == ESPEventID(IP_EVENT_PPP_GOT_IP)) {
auto *event = (ip_event_got_ip_t *)result.ev_data;
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));
} 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);
event_handler.listen_to(MqttClient::get_event(MqttClient::Event::CONNECT));
event_handler.listen_to(MqttClient::get_event(MqttClient::Event::DATA));
auto reg = loop->register_event(MqttClient::get_event(MqttClient::Event::DATA),
[&mqtt](const ESPEvent & event, void *data) {
std::cout << " TOPIC:" << mqtt.get_topic(data) << std::endl;
std::cout << " DATA:" << mqtt.get_data(data) << std::endl;
});
handler.handle_mqtt(&mqtt);
mqtt.connect();
while (true) {
result = event_handler.wait_event_for(std::chrono::milliseconds(60000));
if (result.event == MqttClient::get_event(MqttClient::Event::CONNECT)) {
mqtt.subscribe("/topic/esp-modem");
mqtt.publish("/topic/esp-modem", "Hello modem");
continue;
} else if (result.event == MqttClient::get_event(MqttClient::Event::DATA)) {
std::cout << "Data received" << std::endl;
break; /* Continue with CMUX example after getting data from MQTT */
} else {
break;
}
if (!handler.wait_for(StatusHandler::MQTT_Connect, 60000)) {
ESP_LOGE(TAG, "Cannot connect to %s within specified timeout... exiting", BROKER_URL);
return;
}
std::cout << "Connected" << std::endl;
} else if (result.event.id == ESPEventID(IP_EVENT_PPP_LOST_IP)) {
mqtt.subscribe("/topic/esp-modem");
mqtt.publish("/topic/esp-modem", "Hello modem");
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;
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -10,11 +10,8 @@
#include <memory>
#include "mqtt_client.h"
#include "esp_event_cxx.hpp"
#include "simple_mqtt_client.hpp"
using namespace idf::event;
/**
* Reference to the MQTT event base
*/
@ -33,7 +30,6 @@ struct MqttClientHandle {
config.uri = uri.c_str();
#endif
client = esp_mqtt_client_init(&config);
esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, mqtt_event_handler, this);
}
~MqttClientHandle()
@ -41,12 +37,6 @@ struct MqttClientHandle {
esp_mqtt_client_destroy(client);
}
static void mqtt_event_handler(void *args, esp_event_base_t base, int32_t id, void *data)
{
// forwards the internal event to the global ESPEvent
esp_event_post(base, id, data, sizeof(esp_mqtt_event_t), portMAX_DELAY);
}
esp_mqtt_client_handle_t client;
};
@ -62,16 +52,16 @@ void MqttClient::connect()
esp_mqtt_client_start(h->client);
}
idf::event::ESPEvent MqttClient::get_event(MqttClient::Event ev)
int32_t MqttClient::get_event(MqttClient::Event ev)
{
switch (ev) {
case Event::CONNECT: {
return { MQTT_EVENTS, ESPEventID(MQTT_EVENT_CONNECTED) };
return MQTT_EVENT_CONNECTED;
}
case Event::DATA:
return { MQTT_EVENTS, ESPEventID(MQTT_EVENT_DATA) };
return MQTT_EVENT_DATA;
}
return { };
return -1;
}
int MqttClient::publish(const std::string &topic, const std::string &data, int qos)
@ -102,4 +92,9 @@ std::string MqttClient::get_data(void *event_data)
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;

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -11,7 +11,6 @@
#include <string>
#include <memory>
#include "esp_event_cxx.hpp"
struct MqttClientHandle;
@ -62,12 +61,20 @@ public:
*/
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 ESPEvent
* @return corresponding esp_event id
*/
static idf::event::ESPEvent get_event(Event ev);
static int32_t get_event(Event ev);
private:
std::unique_ptr<MqttClientHandle> h;

View File

@ -1,4 +1,4 @@
version: "0.1.25"
version: "0.1.26"
description: esp modem
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem
dependencies:

View File

@ -116,8 +116,8 @@ private:
Lock internal_lock{}; /*!< Locks DTE operations */
unique_buffer buffer; /*!< DTE buffer */
std::shared_ptr<CMux> cmux_term; /*!< Primary terminal for this DTE */
std::shared_ptr<Terminal> command_term; /*!< Reference to the terminal used for sending commands */
std::shared_ptr<Terminal> data_term; /*!< Secondary terminal for this DTE */
std::shared_ptr<Terminal> primary_term; /*!< Reference to the primary terminal (mostly for sending commands) */
std::shared_ptr<Terminal> secondary_term; /*!< Secondary terminal for this DTE */
modem_mode mode; /*!< DTE operation mode */
SignalGroup signal; /*!< Event group used to signal request-response operations */
command_result result; /*!< Command result of the currently exectuted command */

View File

@ -29,8 +29,13 @@ enum class modem_mode {
UNDEF,
COMMAND_MODE, /*!< Command mode -- the modem is supposed to send AT commands in this mode */
DATA_MODE, /*!< Data mode -- the modem communicates with network interface on PPP protocol */
CMUX_MODE /*!< CMUX (Multiplex mode) -- Simplified CMUX mode, which creates two virtual terminals,
CMUX_MODE, /*!< CMUX (Multiplex mode) -- Simplified CMUX mode, which creates two virtual terminals,
* assigning one solely to command interface and the other to the data mode */
CMUX_MANUAL_MODE, /*!< Enter CMUX mode manually -- just creates two virtual terminals */
CMUX_MANUAL_EXIT, /*!< Exits CMUX mode manually -- just destroys two virtual terminals */
CMUX_MANUAL_DATA, /*!< Sets the primary terminal to DATA mode in manual CMUX */
CMUX_MANUAL_COMMAND, /*!< Sets the primary terminal to COMMAND mode in manual CMUX */
CMUX_MANUAL_SWAP, /*!< Swaps virtual terminals in manual CMUX mode (primary <-> secondary) */
};
/**

View File

@ -36,6 +36,11 @@ typedef enum esp_modem_dce_mode {
ESP_MODEM_MODE_COMMAND, /**< Default mode after modem startup, used for sending AT commands */
ESP_MODEM_MODE_DATA, /**< Used for switching to PPP mode for the modem to connect to a network */
ESP_MODEM_MODE_CMUX, /**< Multiplexed terminal mode */
ESP_MODEM_MODE_CMUX_MANUAL, /**< CMUX manual mode */
ESP_MODEM_MODE_CMUX_MANUAL_EXIT, /**< Exit CMUX manual mode */
ESP_MODEM_MODE_CMUX_MANUAL_SWAP, /**< Swap terminals in CMUX manual mode */
ESP_MODEM_MODE_CMUX_MANUAL_DATA, /**< Set DATA mode in CMUX manual mode */
ESP_MODEM_MODE_CMUX_MANUAL_COMMAND, /**< Set COMMAND mode in CMUX manual mode */
} esp_modem_dce_mode_t;
/**
@ -113,6 +118,8 @@ esp_err_t esp_modem_set_error_cb(esp_modem_dce_t *dce, esp_modem_terminal_error_
*/
esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce, esp_modem_dce_mode_t mode);
esp_err_t esp_modem_command(esp_modem_dce_t *dce, const char *command, esp_err_t(*got_line_cb)(uint8_t *data, size_t len), uint32_t timeout_ms);
/**
* @}
*/

View File

@ -1,393 +1,425 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <cassert>
#include "cxx_include/esp_modem_dte.hpp"
#include "uart_terminal.hpp"
#include "esp_log.h"
#include "cxx_include/esp_modem_api.hpp"
#include "cxx_include/esp_modem_dce_factory.hpp"
#include "esp_modem_c_api_types.h"
#include "esp_modem_config.h"
#include "exception_stub.hpp"
#include "esp_private/c_api_wrapper.hpp"
#include "cstring"
#ifndef ESP_MODEM_C_API_STR_MAX
#define ESP_MODEM_C_API_STR_MAX 64
#endif
#ifndef HAVE_STRLCPY
size_t strlcpy(char *dest, const char *src, size_t len);
#endif
//
// C API definitions
using namespace esp_modem;
extern "C" esp_modem_dce_t *esp_modem_new_dev(esp_modem_dce_device_t module, const esp_modem_dte_config_t *dte_config, const esp_modem_dce_config_t *dce_config, esp_netif_t *netif)
{
auto dce_wrap = new (std::nothrow) esp_modem_dce_wrap;
if (dce_wrap == nullptr) {
return nullptr;
}
auto dte = create_uart_dte(dte_config);
if (dte == nullptr) {
delete dce_wrap;
return nullptr;
}
dce_wrap->dte = dte;
dce_factory::Factory f(convert_modem_enum(module));
dce_wrap->dce = f.build(dce_config, std::move(dte), netif);
if (dce_wrap->dce == nullptr) {
delete dce_wrap;
return nullptr;
}
dce_wrap->modem_type = convert_modem_enum(module);
dce_wrap->dte_type = esp_modem_dce_wrap::modem_wrap_dte_type::UART;
return dce_wrap;
}
extern "C" esp_modem_dce_t *esp_modem_new(const esp_modem_dte_config_t *dte_config, const esp_modem_dce_config_t *dce_config, esp_netif_t *netif)
{
return esp_modem_new_dev(ESP_MODEM_DCE_GENETIC, dte_config, dce_config, netif);
}
extern "C" void esp_modem_destroy(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap) {
delete dce_wrap->dce;
delete dce_wrap;
}
}
extern "C" esp_err_t esp_modem_set_error_cb(esp_modem_dce_t *dce_wrap, esp_modem_terminal_error_cbt err_cb)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr || dce_wrap->dte == nullptr) {
return ESP_ERR_INVALID_ARG;
}
if (err_cb) {
dce_wrap->dte->set_error_cb([err_cb](terminal_error err) {
err_cb(convert_terminal_error_enum(err));
});
} else {
dce_wrap->dte->set_error_cb(nullptr);
}
return ESP_OK;
}
extern "C" esp_err_t esp_modem_sync(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->sync());
}
extern "C" esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce_wrap, esp_modem_dce_mode_t mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
if (mode == ESP_MODEM_MODE_DATA) {
return dce_wrap->dce->set_mode(modem_mode::DATA_MODE) ? ESP_OK : ESP_FAIL;
}
if (mode == ESP_MODEM_MODE_COMMAND) {
return dce_wrap->dce->set_mode(modem_mode::COMMAND_MODE) ? ESP_OK : ESP_FAIL;
}
if (mode == ESP_MODEM_MODE_CMUX) {
return dce_wrap->dce->set_mode(modem_mode::CMUX_MODE) ? ESP_OK : ESP_FAIL;
}
return ESP_ERR_NOT_SUPPORTED;
}
extern "C" esp_err_t esp_modem_read_pin(esp_modem_dce_t *dce_wrap, bool *pin)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->read_pin(*pin));
}
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) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->sms_txt_mode(txt));
}
extern "C" esp_err_t esp_modem_send_sms(esp_modem_dce_t *dce_wrap, const char *number, const char *message)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string number_str(number);
std::string message_str(message);
return command_response_to_esp_err(dce_wrap->dce->send_sms(number_str, message_str));
}
extern "C" esp_err_t esp_modem_sms_character_set(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->sms_character_set());
}
extern "C" esp_err_t esp_modem_set_pin(esp_modem_dce_t *dce_wrap, const char *pin)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string pin_str(pin);
return command_response_to_esp_err(dce_wrap->dce->set_pin(pin_str));
}
extern "C" esp_err_t esp_modem_at(esp_modem_dce_t *dce_wrap, const char *at, char *p_out, int timeout)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string out;
std::string at_str(at);
auto ret = command_response_to_esp_err(dce_wrap->dce->at(at_str, out, timeout));
if ((p_out != NULL) && (!out.empty())) {
strlcpy(p_out, out.c_str(), ESP_MODEM_C_API_STR_MAX);
}
return ret;
}
extern "C" esp_err_t esp_modem_get_signal_quality(esp_modem_dce_t *dce_wrap, int *rssi, int *ber)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->get_signal_quality(*rssi, *ber));
}
extern "C" esp_err_t esp_modem_get_imsi(esp_modem_dce_t *dce_wrap, char *p_imsi)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string imsi;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_imsi(imsi));
if (ret == ESP_OK && !imsi.empty()) {
strlcpy(p_imsi, imsi.c_str(), ESP_MODEM_C_API_STR_MAX);
}
return ret;
}
extern "C" esp_err_t esp_modem_set_flow_control(esp_modem_dce_t *dce_wrap, int dce_flow, int dte_flow)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_flow_control(dce_flow, dte_flow));
}
extern "C" esp_err_t esp_modem_store_profile(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->store_profile());
}
extern "C" esp_err_t esp_modem_get_imei(esp_modem_dce_t *dce_wrap, char *p_imei)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string imei;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_imei(imei));
if (ret == ESP_OK && !imei.empty()) {
strlcpy(p_imei, imei.c_str(), ESP_MODEM_C_API_STR_MAX);
}
return ret;
}
extern "C" esp_err_t esp_modem_get_operator_name(esp_modem_dce_t *dce_wrap, char *p_name, int *p_act)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr || p_name == nullptr || p_act == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string name;
int act;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_operator_name(name, act));
if (ret == ESP_OK && !name.empty()) {
strlcpy(p_name, name.c_str(), ESP_MODEM_C_API_STR_MAX);
*p_act = act;
}
return ret;
}
extern "C" esp_err_t esp_modem_get_module_name(esp_modem_dce_t *dce_wrap, char *p_name)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string name;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_module_name(name));
if (ret == ESP_OK && !name.empty()) {
strlcpy(p_name, name.c_str(), ESP_MODEM_C_API_STR_MAX);
}
return ret;
}
extern "C" esp_err_t esp_modem_get_battery_status(esp_modem_dce_t *dce_wrap, int *p_volt, int *p_bcs, int *p_bcl)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr || p_bcs == nullptr || p_bcl == nullptr || p_volt == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int bcs, bcl, volt;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_battery_status(volt, bcs, bcl));
if (ret == ESP_OK) {
*p_volt = volt;
*p_bcs = bcs;
*p_bcl = bcl;
}
return ret;
}
extern "C" esp_err_t esp_modem_power_down(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->power_down());
}
extern "C" esp_err_t esp_modem_set_operator(esp_modem_dce_t *dce_wrap, int mode, int format, const char *oper)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string operator_str(oper);
return command_response_to_esp_err(dce_wrap->dce->set_operator(mode, format, operator_str));
}
extern "C" esp_err_t esp_modem_set_network_attachment_state(esp_modem_dce_t *dce_wrap, int state)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_network_attachment_state(state));
}
extern "C" esp_err_t esp_modem_get_network_attachment_state(esp_modem_dce_t *dce_wrap, int *p_state)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int state;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_network_attachment_state(state));
if (ret == ESP_OK) {
*p_state = state;
}
return ret;
}
extern "C" esp_err_t esp_modem_set_radio_state(esp_modem_dce_t *dce_wrap, int state)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_radio_state(state));
}
extern "C" esp_err_t esp_modem_get_radio_state(esp_modem_dce_t *dce_wrap, int *p_state)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int state;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_radio_state(state));
if (ret == ESP_OK) {
*p_state = state;
}
return ret;
}
extern "C" esp_err_t esp_modem_set_network_mode(esp_modem_dce_t *dce_wrap, int mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_network_mode(mode));
}
extern "C" esp_err_t esp_modem_set_preferred_mode(esp_modem_dce_t *dce_wrap, int mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_preferred_mode(mode));
}
extern "C" esp_err_t esp_modem_set_network_bands(esp_modem_dce_t *dce_wrap, const char *mode, const int *bands, int size)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string mode_str(mode);
return command_response_to_esp_err(dce_wrap->dce->set_network_bands(mode, bands, size));
}
extern "C" esp_err_t esp_modem_get_network_system_mode(esp_modem_dce_t *dce_wrap, int *p_mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int mode;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_network_system_mode(mode));
if (ret == ESP_OK) {
*p_mode = mode;
}
return ret;
}
extern "C" esp_err_t esp_modem_set_gnss_power_mode(esp_modem_dce_t *dce_wrap, int mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_gnss_power_mode(mode));
}
extern "C" esp_err_t esp_modem_get_gnss_power_mode(esp_modem_dce_t *dce_wrap, int *p_mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int mode;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_gnss_power_mode(mode));
if (ret == ESP_OK) {
*p_mode = mode;
}
return ret;
}
extern "C" esp_err_t esp_modem_reset(esp_modem_dce_t *dce_wrap)
{
return command_response_to_esp_err(dce_wrap->dce->reset());
}
extern "C" esp_err_t esp_modem_set_pdp_context(esp_modem_dce_t *dce_wrap, esp_modem_PdpContext_t *c_api_pdp)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
esp_modem::PdpContext pdp{c_api_pdp->apn};
pdp.context_id = c_api_pdp->context_id;
pdp.protocol_type = c_api_pdp->protocol_type;
return command_response_to_esp_err(dce_wrap->dce->set_pdp_context(pdp));
}
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <cassert>
#include "cxx_include/esp_modem_dte.hpp"
#include "uart_terminal.hpp"
#include "esp_log.h"
#include "cxx_include/esp_modem_api.hpp"
#include "cxx_include/esp_modem_dce_factory.hpp"
#include "esp_modem_c_api_types.h"
#include "esp_modem_config.h"
#include "exception_stub.hpp"
#include "esp_private/c_api_wrapper.hpp"
#include "cstring"
#ifndef ESP_MODEM_C_API_STR_MAX
#define ESP_MODEM_C_API_STR_MAX 64
#endif
#ifndef HAVE_STRLCPY
size_t strlcpy(char *dest, const char *src, size_t len);
#endif
//
// C API definitions
using namespace esp_modem;
extern "C" esp_modem_dce_t *esp_modem_new_dev(esp_modem_dce_device_t module, const esp_modem_dte_config_t *dte_config, const esp_modem_dce_config_t *dce_config, esp_netif_t *netif)
{
auto dce_wrap = new (std::nothrow) esp_modem_dce_wrap;
if (dce_wrap == nullptr) {
return nullptr;
}
auto dte = create_uart_dte(dte_config);
if (dte == nullptr) {
delete dce_wrap;
return nullptr;
}
dce_wrap->dte = dte;
dce_factory::Factory f(convert_modem_enum(module));
dce_wrap->dce = f.build(dce_config, std::move(dte), netif);
if (dce_wrap->dce == nullptr) {
delete dce_wrap;
return nullptr;
}
dce_wrap->modem_type = convert_modem_enum(module);
dce_wrap->dte_type = esp_modem_dce_wrap::modem_wrap_dte_type::UART;
return dce_wrap;
}
extern "C" esp_modem_dce_t *esp_modem_new(const esp_modem_dte_config_t *dte_config, const esp_modem_dce_config_t *dce_config, esp_netif_t *netif)
{
return esp_modem_new_dev(ESP_MODEM_DCE_GENETIC, dte_config, dce_config, netif);
}
extern "C" void esp_modem_destroy(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap) {
delete dce_wrap->dce;
delete dce_wrap;
}
}
extern "C" esp_err_t esp_modem_set_error_cb(esp_modem_dce_t *dce_wrap, esp_modem_terminal_error_cbt err_cb)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr || dce_wrap->dte == nullptr) {
return ESP_ERR_INVALID_ARG;
}
if (err_cb) {
dce_wrap->dte->set_error_cb([err_cb](terminal_error err) {
err_cb(convert_terminal_error_enum(err));
});
} else {
dce_wrap->dte->set_error_cb(nullptr);
}
return ESP_OK;
}
extern "C" esp_err_t esp_modem_sync(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->sync());
}
extern "C" esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce_wrap, esp_modem_dce_mode_t mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
switch (mode) {
case ESP_MODEM_MODE_DATA:
return dce_wrap->dce->set_mode(modem_mode::DATA_MODE) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_COMMAND:
return dce_wrap->dce->set_mode(modem_mode::COMMAND_MODE) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_CMUX:
return dce_wrap->dce->set_mode(modem_mode::CMUX_MODE) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_CMUX_MANUAL:
return dce_wrap->dce->set_mode(modem_mode::CMUX_MANUAL_MODE) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_CMUX_MANUAL_EXIT:
return dce_wrap->dce->set_mode(modem_mode::CMUX_MANUAL_EXIT) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_CMUX_MANUAL_SWAP:
return dce_wrap->dce->set_mode(modem_mode::CMUX_MANUAL_SWAP) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_CMUX_MANUAL_DATA:
return dce_wrap->dce->set_mode(modem_mode::CMUX_MANUAL_DATA) ? ESP_OK : ESP_FAIL;
case ESP_MODEM_MODE_CMUX_MANUAL_COMMAND:
return dce_wrap->dce->set_mode(modem_mode::CMUX_MANUAL_COMMAND) ? ESP_OK : ESP_FAIL;
}
return ESP_ERR_NOT_SUPPORTED;
}
extern "C" esp_err_t esp_modem_read_pin(esp_modem_dce_t *dce_wrap, bool *pin)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->read_pin(*pin));
}
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) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->sms_txt_mode(txt));
}
extern "C" esp_err_t esp_modem_send_sms(esp_modem_dce_t *dce_wrap, const char *number, const char *message)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string number_str(number);
std::string message_str(message);
return command_response_to_esp_err(dce_wrap->dce->send_sms(number_str, message_str));
}
extern "C" esp_err_t esp_modem_sms_character_set(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->sms_character_set());
}
extern "C" esp_err_t esp_modem_set_pin(esp_modem_dce_t *dce_wrap, const char *pin)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string pin_str(pin);
return command_response_to_esp_err(dce_wrap->dce->set_pin(pin_str));
}
extern "C" esp_err_t esp_modem_at(esp_modem_dce_t *dce_wrap, const char *at, char *p_out, int timeout)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string out;
std::string at_str(at);
auto ret = command_response_to_esp_err(dce_wrap->dce->at(at_str, out, timeout));
if ((p_out != NULL) && (!out.empty())) {
strlcpy(p_out, out.c_str(), ESP_MODEM_C_API_STR_MAX);
}
return ret;
}
extern "C" esp_err_t esp_modem_get_signal_quality(esp_modem_dce_t *dce_wrap, int *rssi, int *ber)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->get_signal_quality(*rssi, *ber));
}
extern "C" esp_err_t esp_modem_get_imsi(esp_modem_dce_t *dce_wrap, char *p_imsi)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string imsi;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_imsi(imsi));
if (ret == ESP_OK && !imsi.empty()) {
strlcpy(p_imsi, imsi.c_str(), ESP_MODEM_C_API_STR_MAX);
}
return ret;
}
extern "C" esp_err_t esp_modem_set_flow_control(esp_modem_dce_t *dce_wrap, int dce_flow, int dte_flow)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_flow_control(dce_flow, dte_flow));
}
extern "C" esp_err_t esp_modem_store_profile(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->store_profile());
}
extern "C" esp_err_t esp_modem_get_imei(esp_modem_dce_t *dce_wrap, char *p_imei)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string imei;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_imei(imei));
if (ret == ESP_OK && !imei.empty()) {
strlcpy(p_imei, imei.c_str(), ESP_MODEM_C_API_STR_MAX);
}
return ret;
}
extern "C" esp_err_t esp_modem_get_operator_name(esp_modem_dce_t *dce_wrap, char *p_name, int *p_act)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr || p_name == nullptr || p_act == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string name;
int act;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_operator_name(name, act));
if (ret == ESP_OK && !name.empty()) {
strlcpy(p_name, name.c_str(), ESP_MODEM_C_API_STR_MAX);
*p_act = act;
}
return ret;
}
extern "C" esp_err_t esp_modem_get_module_name(esp_modem_dce_t *dce_wrap, char *p_name)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string name;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_module_name(name));
if (ret == ESP_OK && !name.empty()) {
strlcpy(p_name, name.c_str(), ESP_MODEM_C_API_STR_MAX);
}
return ret;
}
extern "C" esp_err_t esp_modem_get_battery_status(esp_modem_dce_t *dce_wrap, int *p_volt, int *p_bcs, int *p_bcl)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr || p_bcs == nullptr || p_bcl == nullptr || p_volt == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int bcs, bcl, volt;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_battery_status(volt, bcs, bcl));
if (ret == ESP_OK) {
*p_volt = volt;
*p_bcs = bcs;
*p_bcl = bcl;
}
return ret;
}
extern "C" esp_err_t esp_modem_power_down(esp_modem_dce_t *dce_wrap)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->power_down());
}
extern "C" esp_err_t esp_modem_set_operator(esp_modem_dce_t *dce_wrap, int mode, int format, const char *oper)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string operator_str(oper);
return command_response_to_esp_err(dce_wrap->dce->set_operator(mode, format, operator_str));
}
extern "C" esp_err_t esp_modem_set_network_attachment_state(esp_modem_dce_t *dce_wrap, int state)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_network_attachment_state(state));
}
extern "C" esp_err_t esp_modem_get_network_attachment_state(esp_modem_dce_t *dce_wrap, int *p_state)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int state;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_network_attachment_state(state));
if (ret == ESP_OK) {
*p_state = state;
}
return ret;
}
extern "C" esp_err_t esp_modem_set_radio_state(esp_modem_dce_t *dce_wrap, int state)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_radio_state(state));
}
extern "C" esp_err_t esp_modem_get_radio_state(esp_modem_dce_t *dce_wrap, int *p_state)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int state;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_radio_state(state));
if (ret == ESP_OK) {
*p_state = state;
}
return ret;
}
extern "C" esp_err_t esp_modem_set_network_mode(esp_modem_dce_t *dce_wrap, int mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_network_mode(mode));
}
extern "C" esp_err_t esp_modem_set_preferred_mode(esp_modem_dce_t *dce_wrap, int mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_preferred_mode(mode));
}
extern "C" esp_err_t esp_modem_set_network_bands(esp_modem_dce_t *dce_wrap, const char *mode, const int *bands, int size)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string mode_str(mode);
return command_response_to_esp_err(dce_wrap->dce->set_network_bands(mode, bands, size));
}
extern "C" esp_err_t esp_modem_get_network_system_mode(esp_modem_dce_t *dce_wrap, int *p_mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int mode;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_network_system_mode(mode));
if (ret == ESP_OK) {
*p_mode = mode;
}
return ret;
}
extern "C" esp_err_t esp_modem_set_gnss_power_mode(esp_modem_dce_t *dce_wrap, int mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_gnss_power_mode(mode));
}
extern "C" esp_err_t esp_modem_get_gnss_power_mode(esp_modem_dce_t *dce_wrap, int *p_mode)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
int mode;
auto ret = command_response_to_esp_err(dce_wrap->dce->get_gnss_power_mode(mode));
if (ret == ESP_OK) {
*p_mode = mode;
}
return ret;
}
extern "C" esp_err_t esp_modem_reset(esp_modem_dce_t *dce_wrap)
{
return command_response_to_esp_err(dce_wrap->dce->reset());
}
extern "C" esp_err_t esp_modem_set_pdp_context(esp_modem_dce_t *dce_wrap, esp_modem_PdpContext_t *c_api_pdp)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
esp_modem::PdpContext pdp{c_api_pdp->apn};
pdp.context_id = c_api_pdp->context_id;
pdp.protocol_type = c_api_pdp->protocol_type;
return command_response_to_esp_err(dce_wrap->dce->set_pdp_context(pdp));
}
extern "C" esp_err_t esp_modem_command(esp_modem_dce_t *dce_wrap, const char *command, esp_err_t(*got_line_fn)(uint8_t *data, size_t len), uint32_t timeout_ms)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr || command == nullptr || got_line_fn == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string cmd(command);
return command_response_to_esp_err(dce_wrap->dce->command(cmd, [got_line_fn](uint8_t *data, size_t len) {
switch (got_line_fn(data, len)) {
case ESP_OK:
return command_result::OK;
case ESP_FAIL:
return command_result::FAIL;
default:
return command_result::TIMEOUT;
}
}, timeout_ms));
}
extern "C" esp_err_t esp_modem_set_baud(esp_modem_dce_t *dce_wrap, int baud)
{
return command_response_to_esp_err(dce_wrap->dce->set_baud(baud));
}

View File

@ -218,7 +218,7 @@ command_result get_battery_status_sim7xxx(CommandableIf *t, int &voltage, int &b
command_result set_flow_control(CommandableIf *t, int dce_flow, int dte_flow)
{
ESP_LOGV(TAG, "%s", __func__ );
return generic_command_common(t, "AT+IFC=" + std::to_string(dce_flow) + ", " + std::to_string(dte_flow) + "\r");
return generic_command_common(t, "AT+IFC=" + std::to_string(dce_flow) + "," + std::to_string(dte_flow) + "\r");
}
command_result get_operator_name(CommandableIf *t, std::string &operator_name, int &act)

View File

@ -14,6 +14,59 @@
namespace esp_modem {
namespace transitions {
static bool exit_data(DTE &dte, ModuleIf &device, Netif &netif)
{
netif.stop();
auto signal = std::make_shared<SignalGroup>();
std::weak_ptr<SignalGroup> weak_signal = signal;
dte.set_read_cb([weak_signal](uint8_t *data, size_t len) -> bool {
if (memchr(data, '\n', len))
{
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", data, len, ESP_LOG_DEBUG);
const auto pass = std::list<std::string_view>({"NO CARRIER", "DISCONNECTED"});
std::string_view response((char *) data, len);
for (auto &it : pass)
if (response.find(it) != std::string::npos) {
if (auto signal = weak_signal.lock()) {
signal->set(1);
}
return true;
}
}
return false;
});
netif.wait_until_ppp_exits();
if (!signal->wait(1, 2000)) {
if (!device.set_mode(modem_mode::COMMAND_MODE)) {
return false;
}
}
dte.set_read_cb(nullptr);
if (!dte.set_mode(modem_mode::COMMAND_MODE)) {
return false;
}
return true;
}
static bool enter_data(DTE &dte, ModuleIf &device, Netif &netif)
{
if (!device.setup_data_mode()) {
return false;
}
if (!device.set_mode(modem_mode::DATA_MODE)) {
return false;
}
if (!dte.set_mode(modem_mode::DATA_MODE)) {
return false;
}
netif.start();
return true;
}
} // namespace transitions
/**
* Set mode while the entire DTE is locked
*/
@ -36,8 +89,8 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
switch (m) {
case modem_mode::UNDEF:
break;
case modem_mode::COMMAND_MODE: {
if (mode == modem_mode::COMMAND_MODE) {
case modem_mode::COMMAND_MODE:
if (mode == modem_mode::COMMAND_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) {
return false;
}
if (mode == modem_mode::CMUX_MODE) {
@ -49,59 +102,23 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
mode = m;
return true;
}
netif.stop();
auto signal = std::make_shared<SignalGroup>();
std::weak_ptr<SignalGroup> weak_signal = signal;
dte->set_read_cb([weak_signal](uint8_t *data, size_t len) -> bool {
if (memchr(data, '\n', len))
{
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", data, len, ESP_LOG_DEBUG);
const auto pass = std::list<std::string_view>({"NO CARRIER", "DISCONNECTED"});
std::string_view response((char *) data, len);
for (auto &it : pass)
if (response.find(it) != std::string::npos) {
if (auto signal = weak_signal.lock()) {
signal->set(1);
}
return true;
}
}
return false;
});
netif.wait_until_ppp_exits();
if (!signal->wait(1, 2000)) {
if (!device->set_mode(modem_mode::COMMAND_MODE)) {
mode = modem_mode::UNDEF;
return false;
}
}
dte->set_read_cb(nullptr);
if (!dte->set_mode(modem_mode::COMMAND_MODE)) {
if (!transitions::exit_data(*dte, *device, netif)) {
mode = modem_mode::UNDEF;
return false;
}
mode = m;
return true;
}
break;
case modem_mode::DATA_MODE:
if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE) {
if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) {
return false;
}
if (!device->setup_data_mode()) {
if (!transitions::enter_data(*dte, *device, netif)) {
return false;
}
if (!device->set_mode(modem_mode::DATA_MODE)) {
return false;
}
if (!dte->set_mode(modem_mode::DATA_MODE)) {
return false;
}
netif.start();
mode = m;
return true;
case modem_mode::CMUX_MODE:
if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE) {
if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE || mode >= modem_mode::CMUX_MANUAL_MODE) {
return false;
}
device->set_mode(modem_mode::CMUX_MODE); // switch the device into CMUX mode
@ -111,17 +128,46 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
return false;
}
mode = modem_mode::CMUX_MODE;
if (!device->setup_data_mode()) {
return transitions::enter_data(*dte, *device, netif);
case modem_mode::CMUX_MANUAL_MODE:
if (mode != modem_mode::COMMAND_MODE && mode != modem_mode::UNDEF) {
return false;
}
if (!device->set_mode(modem_mode::DATA_MODE)) {
device->set_mode(modem_mode::CMUX_MODE);
usleep(100'000);
if (!dte->set_mode(m)) {
return false;
}
if (!dte->set_mode(modem_mode::DATA_MODE)) {
return false;
}
netif.start();
mode = modem_mode::CMUX_MANUAL_MODE;
return true;
case modem_mode::CMUX_MANUAL_EXIT:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
return false;
}
if (!dte->set_mode(m)) {
return false;
}
mode = modem_mode::COMMAND_MODE;
return true;
case modem_mode::CMUX_MANUAL_SWAP:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
return false;
}
if (!dte->set_mode(m)) {
return false;
}
return true;
case modem_mode::CMUX_MANUAL_DATA:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
return false;
}
return transitions::enter_data(*dte, *device, netif);
case modem_mode::CMUX_MANUAL_COMMAND:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
return false;
}
return transitions::exit_data(*dte, *device, netif);
}
return false;
}

View File

@ -16,12 +16,12 @@ static const size_t dte_default_buffer_size = 1000;
DTE::DTE(const esp_modem_dte_config *config, std::unique_ptr<Terminal> terminal):
buffer(config->dte_buffer_size),
cmux_term(nullptr), command_term(std::move(terminal)), data_term(command_term),
cmux_term(nullptr), primary_term(std::move(terminal)), secondary_term(primary_term),
mode(modem_mode::UNDEF) {}
DTE::DTE(std::unique_ptr<Terminal> terminal):
buffer(dte_default_buffer_size),
cmux_term(nullptr), command_term(std::move(terminal)), data_term(command_term),
cmux_term(nullptr), primary_term(std::move(terminal)), secondary_term(primary_term),
mode(modem_mode::UNDEF) {}
command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator)
@ -29,10 +29,10 @@ command_result DTE::command(const std::string &command, got_line_cb got_line, ui
Scoped<Lock> l(internal_lock);
result = command_result::TIMEOUT;
signal.clear(GOT_LINE);
command_term->set_read_cb([this, got_line, separator](uint8_t *data, size_t len) {
primary_term->set_read_cb([this, got_line, separator](uint8_t *data, size_t len) {
if (!data) {
data = buffer.get();
len = command_term->read(data + buffer.consumed, buffer.size - buffer.consumed);
len = primary_term->read(data + buffer.consumed, buffer.size - buffer.consumed);
} else {
buffer.consumed = 0; // if the underlying terminal contains data, we cannot fragment
}
@ -46,13 +46,13 @@ command_result DTE::command(const std::string &command, got_line_cb got_line, ui
buffer.consumed += len;
return false;
});
command_term->write((uint8_t *)command.c_str(), command.length());
primary_term->write((uint8_t *)command.c_str(), command.length());
auto got_lf = signal.wait(GOT_LINE, time_ms);
if (got_lf && result == command_result::TIMEOUT) {
ESP_MODEM_THROW_IF_ERROR(ESP_ERR_INVALID_STATE);
}
buffer.consumed = 0;
command_term->set_read_cb(nullptr);
primary_term->set_read_cb(nullptr);
return result;
}
@ -68,26 +68,26 @@ bool DTE::exit_cmux()
}
auto ejected = cmux_term->detach();
// return the ejected terminal and buffer back to this DTE
command_term = std::move(ejected.first);
primary_term = std::move(ejected.first);
buffer = std::move(ejected.second);
data_term = command_term;
secondary_term = primary_term;
return true;
}
bool DTE::setup_cmux()
{
cmux_term = std::make_shared<CMux>(command_term, std::move(buffer));
cmux_term = std::make_shared<CMux>(primary_term, std::move(buffer));
if (cmux_term == nullptr) {
return false;
}
if (!cmux_term->init()) {
return false;
}
command_term = std::make_unique<CMuxInstance>(cmux_term, 0);
if (command_term == nullptr) {
primary_term = std::make_unique<CMuxInstance>(cmux_term, 0);
if (primary_term == nullptr) {
return false;
}
data_term = std::make_unique<CMuxInstance>(cmux_term, 1);
secondary_term = std::make_unique<CMuxInstance>(cmux_term, 1);
return true;
}
@ -106,14 +106,12 @@ bool DTE::set_mode(modem_mode m)
}
// transitions (COMMAND|CMUX|UNDEF) -> DATA
if (m == modem_mode::DATA_MODE) {
if (mode == modem_mode::CMUX_MODE) {
// mode stays the same, but need to swap terminals (as command has been switch)
data_term.swap(command_term);
if (mode == modem_mode::CMUX_MODE || mode == modem_mode::CMUX_MANUAL_MODE) {
// mode stays the same, but need to swap terminals (as command has been switched)
secondary_term.swap(primary_term);
} else {
mode = m;
}
// prepare the data terminal's callback to the configured std::function (used by netif)
data_term->set_read_cb(on_data);
return true;
}
// transitions (DATA|CMUX|UNDEF) -> COMMAND
@ -125,21 +123,47 @@ bool DTE::set_mode(modem_mode m)
}
mode = modem_mode::UNDEF;
return false;
} if (mode == modem_mode::CMUX_MANUAL_MODE) {
return true;
} else {
mode = m;
return true;
}
}
return true;
// manual CMUX transitions: Enter CMUX
if (m == modem_mode::CMUX_MANUAL_MODE) {
if (setup_cmux()) {
mode = m;
return true;
}
mode = modem_mode::UNDEF;
return false;
}
// manual CMUX transitions: Exit CMUX
if (m == modem_mode::CMUX_MANUAL_EXIT && mode == modem_mode::CMUX_MANUAL_MODE) {
if (exit_cmux()) {
mode = modem_mode::COMMAND_MODE;
return true;
}
mode = modem_mode::UNDEF;
return false;
}
// manual CMUX transitions: Swap terminals
if (m == modem_mode::CMUX_MANUAL_SWAP && mode == modem_mode::CMUX_MANUAL_MODE) {
secondary_term.swap(primary_term);
return true;
}
mode = modem_mode::UNDEF;
return false;
}
void DTE::set_read_cb(std::function<bool(uint8_t *, size_t)> f)
{
on_data = std::move(f);
data_term->set_read_cb([this](uint8_t *data, size_t len) {
secondary_term->set_read_cb([this](uint8_t *data, size_t len) {
if (!data) { // if no data available from terminal callback -> need to explicitly read some
data = buffer.get();
len = data_term->read(buffer.get(), buffer.size);
len = secondary_term->read(buffer.get(), buffer.size);
}
if (on_data) {
return on_data(data, len);
@ -150,22 +174,22 @@ void DTE::set_read_cb(std::function<bool(uint8_t *, size_t)> f)
void DTE::set_error_cb(std::function<void(terminal_error err)> f)
{
data_term->set_error_cb(f);
command_term->set_error_cb(f);
secondary_term->set_error_cb(f);
primary_term->set_error_cb(f);
}
int DTE::read(uint8_t **d, size_t len)
{
auto data_to_read = std::min(len, buffer.size);
auto data = buffer.get();
auto actual_len = data_term->read(data, data_to_read);
auto actual_len = secondary_term->read(data, data_to_read);
*d = data;
return actual_len;
}
int DTE::write(uint8_t *data, size_t len)
{
return data_term->write(data, len);
return secondary_term->write(data, len);
}
/**

View File

@ -21,7 +21,7 @@ void Netif::on_ppp_changed(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
auto *ppp = static_cast<Netif *>(arg);
if (event_id < NETIF_PP_PHASE_OFFSET) {
if (event_id > NETIF_PPP_ERRORNONE && event_id < NETIF_PP_PHASE_OFFSET) {
ESP_LOGI("esp_modem_netif", "PPP state changed event %" PRId32, event_id);
// only notify the modem on state/error events, ignoring phase transitions
ppp->signal.set(PPP_EXIT);

View File

@ -13,3 +13,15 @@ idf_component_get_property(esp_modem esp_modem COMPONENT_LIB)
target_compile_definitions(${esp_modem} PRIVATE "-DCONFIG_COMPILER_CXX_EXCEPTIONS")
target_compile_definitions(${esp_modem} PRIVATE "-DCONFIG_IDF_TARGET_LINUX")
target_link_options(${esp_modem} INTERFACE -fsanitize=address -fsanitize=undefined)
if(CONFIG_GCOV_ENABLED)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fprofile-arcs -ftest-coverage")
MARK_AS_ADVANCED(
CMAKE_CXX_FLAGS_COVERAGE
CMAKE_C_FLAGS_COVERAGE
CMAKE_EXE_LINKER_FLAGS_COVERAGE
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
endif()

View File

@ -0,0 +1,10 @@
#!/bin/bash
lwip=$1
lwip_uri=$2
lwip_contrib=$3
wget --no-verbose ${lwip_uri}/${lwip}.zip
unzip -oq ${lwip}.zip
wget --no-verbose ${lwip_uri}/${lwip_contrib}.zip
unzip -oq ${lwip_contrib}.zip

View File

@ -0,0 +1,9 @@
menu "Host-test config"
config GCOV_ENABLED
bool "Coverage analyzer"
default n
help
Enables coverage analyzing for host tests.
endmenu

View File

@ -0,0 +1 @@
CONFIG_GCOV_ENABLED=y

View File

@ -8,3 +8,5 @@ set_target_properties(${COMPONENT_LIB} PROPERTIES
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS ON
)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

View File

@ -1,26 +1,38 @@
menu "Example Configuration"
menu "Test App Configuration"
config ESP_WIFI_SSID
string "WiFi SSID"
default "myssid"
config TEST_APP_PPP_SERVER_IP
string "IP address of PPP server"
default "10.0.0.1"
help
SSID (network name) for the example to connect to.
IP address of PPP server. Note: this is also the address
where the TCP server is started to test the connection
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
config TEST_APP_PPP_CLIENT_IP
string "IP address of PPP client"
default "10.0.0.2"
help
WiFi password (WPA or WPA2) for the example to use.
config ESP_WIFI_CHANNEL
int "WiFi Channel"
range 1 13
default 1
help
WiFi channel (network channel) for the example to use.
IP address that PPP server assigns to PPP client.
config ESP_MAX_STA_CONN
int "Maximal STA connections"
default 4
config TEST_APP_UART_TX_PIN
int "TXD Pin Number"
default 6
range 0 31
help
Max number of the STA connects to AP.
Pin number of UART TX.
config TEST_APP_UART_RX_PIN
int "RXD Pin Number"
default 7
range 0 31
help
Pin number of UART RX.
config TEST_APP_TCP_PORT
int "Port of test"
range 0 65535
default 2222
help
The remote port to which the client will connects to
once the PPP connection established
endmenu

View File

@ -60,6 +60,8 @@ public:
{
// configure
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
dte_config.uart_config.tx_io_num = CONFIG_TEST_APP_UART_TX_PIN;
dte_config.uart_config.rx_io_num = CONFIG_TEST_APP_UART_RX_PIN;
esp_modem_dce_config dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("");
// create DTE and minimal network DCE
@ -96,6 +98,7 @@ esp_err_t modem_init_network(esp_netif_t *netif)
esp_err_t modem_start_network()
{
NetModule::start();
return ESP_OK;
}
void modem_stop_network()

View File

@ -0,0 +1,83 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
from __future__ import print_function, unicode_literals
import subprocess
import time
from threading import Event, Thread
import netifaces
def run_server(server_stop, port, server_ip, client_ip):
print('Starting PPP server on port: {}'.format(port))
try:
arg_list = [
'sudo', 'pppd', port, '115200',
'{}:{}'.format(server_ip, client_ip), 'modem', 'local', 'noauth',
'debug', 'nocrtscts', 'nodetach', '+ipv6'
]
p = subprocess.Popen(arg_list, stdout=subprocess.PIPE, bufsize=1)
while not server_stop.is_set():
if p.poll() is not None:
if p.poll() == 16:
print('[PPPD:] Terminated: hang-up received')
break
else:
raise ValueError(
'ENV_TEST_FAILURE: PPP terminated unexpectedly with {}'
.format(p.poll()))
line = p.stdout.readline()
if line:
print('[PPPD:]{}'.format(line.rstrip()))
time.sleep(0.1)
except Exception as e:
print(e)
raise ValueError('ENV_TEST_FAILURE: Error running PPP server')
finally:
p.terminate()
print('PPP server stopped')
def test_examples_protocol_pppos_connect(dut):
"""
steps:
1. starts PPP server
2. get DUT as PPP client to connect to the server
3. run unit test FW
"""
# Look for test case symbolic names
try:
server_ip = dut.app.sdkconfig.get('TEST_APP_PPP_SERVER_IP')
client_ip = dut.app.sdkconfig.get('TEST_APP_PPP_CLIENT_IP')
except Exception:
print(
'ENV_TEST_FAILURE: Some mandatory configuration not found in sdkconfig'
)
raise
# the PPP test env uses two ttyUSB's: one for ESP32 board, another one for ppp server
# use the other port for PPP server than the DUT/ESP
port = '/dev/ttyUSB0' if dut.serial.port == '/dev/ttyUSB1' else '/dev/ttyUSB1'
# Start the PPP server
server_stop = Event()
t = Thread(target=run_server,
args=(server_stop, port, server_ip, client_ip))
t.start()
try:
ppp_server_timeout = time.time() + 30
while 'ppp0' not in netifaces.interfaces():
print(
"PPP server haven't yet setup its netif, list of active netifs:{}"
.format(netifaces.interfaces()))
time.sleep(0.5)
if time.time() > ppp_server_timeout:
raise ValueError(
'ENV_TEST_FAILURE: PPP server failed to setup ppp0 interface within timeout'
)
dut.expect('All tests passed')
finally:
server_stop.set()
t.join()

View File

@ -1,3 +1,4 @@
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_CXX_EXCEPTIONS=y
CONFIG_PPP_SUPPORT=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096

View File

@ -1,5 +1,7 @@
# ESP WEBSOCKET CLIENT
[![Component Registry](https://components.espressif.com/components/espressif/esp_websocket_client/badge.svg)](https://components.espressif.com/components/espressif/esp_websocket_client)
The `esp-websocket_client` component is a managed component for `esp-idf` that contains implementation of [WebSocket protocol client](https://datatracker.ietf.org/doc/html/rfc6455) for ESP32
## Examples

View File

@ -115,6 +115,7 @@ struct esp_websocket_client {
int auto_reconnect;
bool run;
bool wait_for_pong_resp;
bool selected_for_destroying;
EventGroupHandle_t status_bits;
SemaphoreHandle_t lock;
char *rx_buffer;
@ -342,6 +343,26 @@ static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle
return ESP_OK;
}
static void destroy_and_free_resources(esp_websocket_client_handle_t client)
{
if (client->event_handle) {
esp_event_loop_delete(client->event_handle);
}
if (client->if_name) {
free(client->if_name);
}
esp_websocket_client_destroy_config(client);
esp_transport_list_destroy(client->transport_list);
vQueueDelete(client->lock);
free(client->tx_buffer);
free(client->rx_buffer);
if (client->status_bits) {
vEventGroupDelete(client->status_bits);
}
free(client);
client = NULL;
}
static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, const char *scheme)
{
esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, scheme);
@ -527,6 +548,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
client->reconnect_tick_ms = _tick_get_ms();
client->ping_tick_ms = _tick_get_ms();
client->wait_for_pong_resp = false;
client->selected_for_destroying = false;
int buffer_size = config->buffer_size;
if (buffer_size <= 0) {
@ -563,22 +585,16 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
if (client->run) {
esp_websocket_client_stop(client);
}
if (client->event_handle) {
esp_event_loop_delete(client->event_handle);
destroy_and_free_resources(client);
return ESP_OK;
}
esp_err_t esp_websocket_client_destroy_on_exit(esp_websocket_client_handle_t client)
{
if (client == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (client->if_name) {
free(client->if_name);
}
esp_websocket_client_destroy_config(client);
esp_transport_list_destroy(client->transport_list);
vQueueDelete(client->lock);
free(client->tx_buffer);
free(client->rx_buffer);
if (client->status_bits) {
vEventGroupDelete(client->status_bits);
}
free(client);
client = NULL;
client->selected_for_destroying = true;
return ESP_OK;
}
@ -833,6 +849,9 @@ static void esp_websocket_client_task(void *pv)
esp_transport_close(client->transport);
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
client->state = WEBSOCKET_STATE_UNKNOW;
if (client->selected_for_destroying == true) {
destroy_and_free_resources(client);
}
vTaskDelete(NULL);
}

View File

@ -166,6 +166,18 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client);
*/
esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client);
/**
* @brief If this API called, WebSocket client will destroy and free all resources at the end of event loop.
*
* Notes:
* - After event loop finished, client handle would be dangling and should never be used
*
* @param[in] client The client
*
* @return esp_err_t
*/
esp_err_t esp_websocket_client_destroy_on_exit(esp_websocket_client_handle_t client);
/**
* @brief Write binary data to the WebSocket connection (data send with WS OPCODE=02, i.e. binary)
*

View File

@ -6,7 +6,7 @@ endif()
idf_build_get_property(target IDF_TARGET)
if(${target} STREQUAL "linux")
set(dependencies esp_system_protocols_linux)
set(dependencies esp_event esp_netif_linux esp_timer_linux esp_system)
set(srcs "mdns.c" ${MDNS_NETWORKING})
else()
set(dependencies lwip console esp_netif)
@ -22,6 +22,11 @@ idf_component_register(
PRIV_REQUIRES ${private_dependencies})
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
if(${target} STREQUAL "linux")
target_link_libraries(${COMPONENT_LIB} PRIVATE "-lbsd")
endif()
if(CONFIG_ETH_ENABLED)
idf_component_optional_requires(PRIVATE esp_eth)
endif()

View File

@ -64,17 +64,6 @@ menu "mDNS"
Configures timeout for adding a new mDNS service. Adding a service
fails if could not be completed within this time.
config MDNS_STRICT_MODE
bool "mDNS strict mode"
default "n"
help
Configures strict mode. Set this to 1 for the mDNS library to strictly follow the RFC6762:
Currently the only strict feature: Do not repeat original questions in response packets
(defined in RFC6762 sec. 6).
Default configuration is 0, i.e. non-strict mode, since some implementations,
such as lwIP mDNS resolver (used by standard POSIX API like getaddrinfo, gethostbyname)
could not correctly resolve advertised names.
config MDNS_TIMER_PERIOD_MS
int "mDNS timer period (ms)"
range 10 10000
@ -92,6 +81,27 @@ menu "mDNS"
This option creates a new thread to serve receiving packets (TODO).
This option uses additional N sockets, where N is number of interfaces.
config MDNS_SKIP_SUPPRESSING_OWN_QUERIES
bool "Skip suppressing our own packets"
default n
help
Enable only if the querier and the responder share the same IP address.
This usually happens in test mode, where we may run multiple instances of
responders/queriers on the same interface.
config MDNS_ENABLE_DEBUG_PRINTS
bool "Enable debug prints of mDNS packets"
default n
help
Enable for the library to log received and sent mDNS packets to stdout.
config MDNS_RESPOND_REVERSE_QUERIES
bool "Enable responding to IPv4 reverse queries"
default n
help
Enables support for IPv4 reverse lookup. If enabled, the mDNS library
response to PTR queries of "A.B.C.D.in-addr.arpa" type.
config MDNS_MULTIPLE_INSTANCE
bool "Multiple instances under the same service type"
default y

View File

@ -1,5 +1,7 @@
# mDNS Service
[![Component Registry](https://components.espressif.com/components/espressif/mdns/badge.svg)](https://components.espressif.com/components/espressif/mdns)
mDNS is a multicast UDP service that is used to provide local network service and host discovery.
## Examples

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -279,6 +279,13 @@ void app_main(void)
ESP_ERROR_CHECK(mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_ENABLE_IP4));
ESP_ERROR_CHECK(mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_ANNOUNCE_IP4));
#endif
#if defined(CONFIG_MDNS_RESPOND_REVERSE_QUERIES)
while (mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_IP4_REVERSE_LOOKUP) != ESP_OK) {
vTaskDelay(50 / portTICK_PERIOD_MS);
}
#endif
initialise_button();
xTaskCreate(&mdns_example_task, "mdns_example_task", 2048, NULL, 5, NULL);
}

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import re
import select
@ -8,8 +8,11 @@ import subprocess
import time
from threading import Event, Thread
import dpkt
import dpkt.dns
try:
import dpkt
import dpkt.dns
except ImportError:
pass
def get_dns_query_for_esp(esp_host):

View File

@ -1,4 +1,4 @@
version: "1.0.7"
version: "1.0.8"
description: mDNS
dependencies:
idf:

View File

@ -34,6 +34,8 @@ typedef enum {
MDNS_EVENT_ANNOUNCE_IP6 = 1 << 4,
MDNS_EVENT_DISABLE_IP4 = 1 << 5,
MDNS_EVENT_DISABLE_IP6 = 1 << 6,
MDNS_EVENT_IP4_REVERSE_LOOKUP = 1 << 7,
MDNS_EVENT_IP6_REVERSE_LOOKUP = 1 << 8,
} mdns_event_actions_t;
/**

View File

@ -17,10 +17,13 @@
#include "mdns_networking.h"
#include "esp_log.h"
#include "esp_random.h"
#if CONFIG_ETH_ENABLED
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
#include "esp_eth.h"
#endif
#if CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP
#include "esp_wifi.h"
#endif
#ifdef MDNS_ENABLE_DEBUG
@ -435,7 +438,10 @@ static const uint8_t *_mdns_read_fqdn(const uint8_t *packet, const uint8_t *star
&& (strcasecmp(buf, MDNS_DEFAULT_DOMAIN) != 0)
&& (strcasecmp(buf, "arpa") != 0)
&& (strcasecmp(buf, "ip6") != 0)
&& (strcasecmp(buf, "in-addr") != 0)) {
#ifndef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
&& (strcasecmp(buf, "in-addr") != 0)
#endif
) {
strlcat(name->host, ".", sizeof(name->host));
strlcat(name->host, buf, sizeof(name->host));
} else if (strcasecmp(buf, MDNS_SUB_STR) == 0) {
@ -639,6 +645,60 @@ static inline int append_one_txt_record_entry(uint8_t *packet, uint16_t *index,
return len + 1;
}
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
static inline int append_single_str(uint8_t *packet, uint16_t *index, const char *str, int len)
{
if ((*index + len + 1) >= MDNS_MAX_PACKET_SIZE) {
return 0;
}
if (!_mdns_append_u8(packet, index, len)) {
return 0;
}
memcpy(packet + *index, str, len);
*index += len;
return *index;
}
/**
* @brief appends FQDN to a packet from hostname separated by dots. This API works the same way as
* _mdns_append_fqdn(), but refrains from DNS compression (as it's mainly used for IP addresses (many short items),
* where we gain very little (or compression even gets counter-productive mainly for IPv6 addresses)
*
* @param packet MDNS packet
* @param index offset in the packet
* @param name name representing FQDN in '.' separated parts
* @param last true if appending the last part (domain, typically "arpa")
*
* @return length of added data: 0 on error or length on success
*/
static uint16_t append_fqdn_dots(uint8_t *packet, uint16_t *index, const char *name, bool last)
{
int len = strlen(name);
char *host = (char *)name;
char *end = host;
char *start = host;
do {
end = memchr(start, '.', len);
end = end ? end : host + len;
int part_len = end - start;
if (!append_single_str(packet, index, start, part_len)) {
return 0;
}
start = ++end;
} while (end < name + len);
if (!append_single_str(packet, index, "arpa", sizeof("arpa") - 1)) {
return 0;
}
//empty string so terminate
if (!_mdns_append_u8(packet, index, 0)) {
return 0;
}
return *index;
}
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
/**
* @brief appends FQDN to a packet, incrementing the index and
* compressing the output if previous occurrence of the string (or part of it) has been found
@ -680,7 +740,7 @@ search_next:
const uint8_t *content = _mdns_read_fqdn(packet, len_location, &name, buf, packet_len);
if (!content) {
//not a readable fqdn?
return 0;
goto search_next; // could be our unfinished fqdn, continue searching
}
if (name.parts == count) {
uint8_t i;
@ -1100,25 +1160,34 @@ static uint16_t _mdns_append_aaaa_record(uint8_t *packet, uint16_t *index, const
*/
static uint16_t _mdns_append_question(uint8_t *packet, uint16_t *index, mdns_out_question_t *q)
{
const char *str[4];
uint8_t str_index = 0;
uint8_t part_length;
if (q->host) {
str[str_index++] = q->host;
}
if (q->service) {
str[str_index++] = q->service;
}
if (q->proto) {
str[str_index++] = q->proto;
}
if (q->domain) {
str[str_index++] = q->domain;
}
part_length = _mdns_append_fqdn(packet, index, str, str_index, MDNS_MAX_PACKET_SIZE);
if (!part_length) {
return 0;
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
if (q->host && strstr(q->host, "in-addr")) {
part_length = append_fqdn_dots(packet, index, q->host, false);
if (!part_length) {
return 0;
}
} else
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
{
const char *str[4];
uint8_t str_index = 0;
if (q->host) {
str[str_index++] = q->host;
}
if (q->service) {
str[str_index++] = q->service;
}
if (q->proto) {
str[str_index++] = q->proto;
}
if (q->domain) {
str[str_index++] = q->domain;
}
part_length = _mdns_append_fqdn(packet, index, str, str_index, MDNS_MAX_PACKET_SIZE);
if (!part_length) {
return 0;
}
}
part_length += _mdns_append_u16(packet, index, q->type);
@ -1200,6 +1269,37 @@ static uint8_t _mdns_append_host_answer(uint8_t *packet, uint16_t *index, mdns_h
return num_records;
}
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
/**
* @brief Appends reverse lookup PTR record
*/
static uint8_t _mdns_append_reverse_ptr_record(uint8_t *packet, uint16_t *index, const char *name)
{
if (strstr(name, "in-addr") == NULL) {
return 0;
}
if (!append_fqdn_dots(packet, index, name, false)) {
return 0;
}
if (!_mdns_append_type(packet, index, MDNS_ANSWER_PTR, false, 10 /* TTL set to 10s*/ )) {
return 0;
}
uint16_t data_len_location = *index - 2; /* store the position of size (2=16bis) of this record */
const char *str[2] = { _mdns_self_host.hostname, MDNS_DEFAULT_DOMAIN };
int part_length = _mdns_append_fqdn(packet, index, str, 2, MDNS_MAX_PACKET_SIZE);
if (!part_length) {
return 0;
}
_mdns_set_u16(packet, data_len_location, part_length);
return 1; /* appending only 1 record */
}
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
/**
* @brief Append PTR answers to packet
*
@ -1238,6 +1338,10 @@ static uint8_t _mdns_append_answer(uint8_t *packet, uint16_t *index, mdns_out_an
if (answer->type == MDNS_TYPE_PTR) {
if (answer->service) {
return _mdns_append_service_ptr_answers(packet, index, answer->service, answer->flush, answer->bye);
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
} else if (answer->host && answer->host->hostname && strstr(answer->host->hostname, "in-addr")) {
return _mdns_append_reverse_ptr_record(packet, index, answer->host->hostname) > 0;
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
} else {
return _mdns_append_ptr_record(packet, index,
answer->custom_instance, answer->custom_service, answer->custom_proto,
@ -1723,14 +1827,24 @@ static void _mdns_create_answer_from_parsed_packet(mdns_parsed_packet_t *parsed_
_mdns_free_tx_packet(packet);
return;
}
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
} else if (q->type == MDNS_TYPE_PTR) {
mdns_host_item_t *host = mdns_get_host_item(q->host);
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, NULL, host, send_flush, false)) {
return;
}
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
} else if (!_mdns_alloc_answer(&packet->answers, q->type, NULL, NULL, send_flush, false)) {
_mdns_free_tx_packet(packet);
return;
}
#ifdef MDNS_REPEAT_QUERY_IN_RESPONSE
if (parsed_packet->src_port != MDNS_SERVICE_PORT && // Repeat the queries only for "One-Shot mDNS queries"
(q->type == MDNS_TYPE_ANY || q->type == MDNS_TYPE_A || q->type == MDNS_TYPE_AAAA)) {
(q->type == MDNS_TYPE_ANY || q->type == MDNS_TYPE_A || q->type == MDNS_TYPE_AAAA
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
|| q->type == MDNS_TYPE_PTR
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
)) {
mdns_out_question_t *out_question = malloc(sizeof(mdns_out_question_t));
if (out_question == NULL) {
HOOK_MALLOC_FAILED;
@ -1751,7 +1865,6 @@ static void _mdns_create_answer_from_parsed_packet(mdns_parsed_packet_t *parsed_
out_question->own_dynamic_memory = true;
queueToEnd(mdns_out_question_t, packet->questions, out_question);
}
#endif // MDNS_REPEAT_QUERY_IN_RESPONSE
if (q->unicast) {
unicast = true;
}
@ -2936,10 +3049,10 @@ static bool _mdns_delegate_hostname_remove(const char *hostname)
static bool _mdns_name_is_discovery(mdns_name_t *name, uint16_t type)
{
return (
(name->host && name->host[0] && !strcasecmp(name->host, "_services"))
&& (name->service && name->service[0] && !strcasecmp(name->service, "_dns-sd"))
&& (name->proto && name->proto[0] && !strcasecmp(name->proto, "_udp"))
&& (name->domain && name->domain[0] && !strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN))
(name->host[0] && !strcasecmp(name->host, "_services"))
&& (name->service[0] && !strcasecmp(name->service, "_dns-sd"))
&& (name->proto[0] && !strcasecmp(name->proto, "_udp"))
&& (name->domain[0] && !strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN))
&& type == MDNS_TYPE_PTR
);
}
@ -2950,7 +3063,11 @@ static bool _mdns_name_is_discovery(mdns_name_t *name, uint16_t type)
static bool _mdns_name_is_ours(mdns_name_t *name)
{
//domain have to be "local"
if (_str_null_or_empty(name->domain) || strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN)) {
if (_str_null_or_empty(name->domain) || ( strcasecmp(name->domain, MDNS_DEFAULT_DOMAIN)
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
&& strcasecmp(name->domain, "arpa")
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
) ) {
return false;
}
@ -3310,6 +3427,7 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
mdns_debug_packet(data, len);
#endif
#ifndef CONFIG_MDNS_SKIP_SUPPRESSING_OWN_QUERIES
// Check if the packet wasn't sent by us
if (packet->ip_protocol == MDNS_IP_PROTOCOL_V4) {
esp_netif_ip_info_t if_ip_info;
@ -3326,6 +3444,7 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
}
#endif
}
#endif
// Check for the minimum size of mdns packet
if (len <= MDNS_HEAD_ADDITIONAL_OFFSET) {
@ -3496,7 +3615,7 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
discovery = true;
} else if (!name->sub && _mdns_name_is_ours(name)) {
ours = true;
if (name->service && name->service[0] && name->proto && name->proto[0]) {
if (name->service[0] && name->proto[0]) {
service = _mdns_get_service_item(name->service, name->proto, NULL);
}
} else {
@ -3660,7 +3779,7 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
}
}
} else if (ours) {
if (parsed_packet->questions && !parsed_packet->probe) {
if (parsed_packet->questions && !parsed_packet->probe && service) {
_mdns_remove_parsed_question(parsed_packet, type, service);
continue;
}
@ -3873,6 +3992,22 @@ static void perform_event_action(mdns_if_t mdns_if, mdns_event_actions_t action)
if (action & MDNS_EVENT_ANNOUNCE_IP6) {
_mdns_announce_pcb(mdns_if, MDNS_IP_PROTOCOL_V6, NULL, 0, true);
}
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
if (action & MDNS_EVENT_IP4_REVERSE_LOOKUP) {
esp_netif_ip_info_t if_ip_info;
if (esp_netif_get_ip_info(_mdns_get_esp_netif(mdns_if), &if_ip_info) == ESP_OK) {
esp_ip4_addr_t *ip = &if_ip_info.ip;
char *reverse_query_name = NULL;
if (asprintf(&reverse_query_name, "%d.%d.%d.%d.in-addr",
esp_ip4_addr4_16(ip), esp_ip4_addr3_16(ip),
esp_ip4_addr2_16(ip), esp_ip4_addr1_16(ip)) > 0 && reverse_query_name) {
ESP_LOGD(TAG, "Registered reverse query: %s.arpa", reverse_query_name);
_mdns_delegate_hostname_add(reverse_query_name, NULL);
}
}
}
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
}
/**
@ -3893,6 +4028,7 @@ static inline void post_mdns_announce_pcb(mdns_predef_if_t preset_if, mdns_ip_pr
mdns_post_custom_action_tcpip_if(mdns_if_from_preset_if(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
void mdns_preset_if_handle_system_event(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
@ -3970,6 +4106,7 @@ void mdns_preset_if_handle_system_event(void *arg, esp_event_base_t event_base,
}
}
}
#endif /* CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP || CONFIG_MDNS_PREDEF_NETIF_ETH */
/*
* MDNS Search
@ -4941,7 +5078,7 @@ static void _mdns_service_task(void *pvParameters)
for (;;) {
if (_mdns_server && _mdns_server->action_queue) {
if (xQueueReceive(_mdns_server->action_queue, &a, portMAX_DELAY) == pdTRUE) {
if (a->type == ACTION_TASK_STOP) {
if (a && a->type == ACTION_TASK_STOP) {
break;
}
MDNS_SERVICE_LOCK();
@ -5398,7 +5535,7 @@ esp_err_t mdns_instance_name_set(const char *instance)
if (!_mdns_server) {
return ESP_ERR_INVALID_STATE;
}
if (_str_null_or_empty(instance) || strlen(instance) > (MDNS_NAME_BUF_LEN - 1)) {
if (_str_null_or_empty(instance) || _mdns_server->hostname == NULL || strlen(instance) > (MDNS_NAME_BUF_LEN - 1)) {
return ESP_ERR_INVALID_ARG;
}
char *new_instance = strndup(instance, MDNS_NAME_BUF_LEN - 1);
@ -6067,19 +6204,19 @@ void mdns_debug_packet(const uint8_t *data, size_t len)
_mdns_dbg_printf("Packet[%u]: ", t);
header.id = _mdns_read_u16(data, MDNS_HEAD_ID_OFFSET);
header.flags.value = _mdns_read_u16(data, MDNS_HEAD_FLAGS_OFFSET);
header.flags = _mdns_read_u16(data, MDNS_HEAD_FLAGS_OFFSET);
header.questions = _mdns_read_u16(data, MDNS_HEAD_QUESTIONS_OFFSET);
header.answers = _mdns_read_u16(data, MDNS_HEAD_ANSWERS_OFFSET);
header.servers = _mdns_read_u16(data, MDNS_HEAD_SERVERS_OFFSET);
header.additional = _mdns_read_u16(data, MDNS_HEAD_ADDITIONAL_OFFSET);
_mdns_dbg_printf("%s",
(header.flags.value == MDNS_FLAGS_QR_AUTHORITATIVE) ? "AUTHORITATIVE\n" :
(header.flags.value == MDNS_FLAGS_DISTRIBUTED) ? "DISTRIBUTED\n" :
(header.flags.value == 0) ? "\n" : " "
(header.flags == MDNS_FLAGS_QR_AUTHORITATIVE) ? "AUTHORITATIVE\n" :
(header.flags == MDNS_FLAGS_DISTRIBUTED) ? "DISTRIBUTED\n" :
(header.flags == 0) ? "\n" : " "
);
if (header.flags.value && header.flags.value != MDNS_FLAGS_QR_AUTHORITATIVE) {
_mdns_dbg_printf("0x%04X\n", header.flags.value);
if (header.flags && header.flags != MDNS_FLAGS_QR_AUTHORITATIVE) {
_mdns_dbg_printf("0x%04X\n", header.flags);
}
if (header.questions) {

View File

@ -14,34 +14,13 @@
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_system.h"
//#define MDNS_ENABLE_DEBUG
#ifdef MDNS_ENABLE_DEBUG
#ifdef CONFIG_MDNS_ENABLE_DEBUG_PRINTS
#define MDNS_ENABLE_DEBUG
#define _mdns_dbg_printf(...) printf(__VA_ARGS__)
#endif
/** mDNS strict mode: Set this to 1 for the mDNS library to strictly follow the RFC6762:
* Strict features:
* - to do not set original questions in response packets per RFC6762, sec 6
*
* The actual configuration is 0, i.e. non-strict mode, since some implementations,
* such as lwIP mdns resolver (used by standard POSIX API like getaddrinfo, gethostbyname)
* could not correctly resolve advertised names.
*/
#ifndef CONFIG_MDNS_STRICT_MODE
#define MDNS_STRICT_MODE 0
#else
#define MDNS_STRICT_MODE 1
#endif
#if !MDNS_STRICT_MODE
/* mDNS responders sometimes repeat queries in responses
* but according to RFC6762, sec 6: Responses MUST NOT contain
* any item in question field */
#define MDNS_REPEAT_QUERY_IN_RESPONSE 1
#endif
/** Number of predefined interfaces */
#ifndef CONFIG_MDNS_PREDEF_NETIF_STA
#define CONFIG_MDNS_PREDEF_NETIF_STA 0

View File

@ -1,5 +1,7 @@
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS "../..")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
set(COMPONENTS main esp_netif_linux)
project(mdns_host)

View File

@ -1,7 +1,10 @@
# Setup dummy network interfaces
Note: Set two addresses so we could use one as source and another as destination
```
sudo ip link add eth2 type dummy
sudo ip addr add 192.168.1.200/24 dev eth2
sudo ip addr add 192.168.1.201/24 dev eth2
sudo ip link set eth2 up
sudo ifconfig eth2 multicast
```
@ -12,6 +15,11 @@ sudo ifconfig eth2 multicast
dig +short -b 192.168.1.200 -p 5353 @224.0.0.251 myesp.local
```
or a reverse query:
```
dig +short -b 192.168.2.200 -p 5353 @224.0.0.251 -x 192.168.1.200
```
# Run avahi to browse services
Avahi needs the netif to have the "multicast" flag set

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS esp_event_mock.c
INCLUDE_DIRS include)

View File

@ -8,16 +8,7 @@
#include "stdbool.h"
#include "esp_err.h"
#include "esp_event_base.h"
#include "bsd_strings.h"
#define ESP_EVENT_DECLARE_BASE(x)
#define ESP_EVENT_ANY_ID (-1)
typedef void *esp_event_base_t;
typedef void *system_event_t;
const char *WIFI_EVENT;
const char *IP_EVENT;
#include "bsd/string.h"
esp_err_t esp_event_handler_register(const char *event_base, int32_t event_id, void *event_handler, void *event_handler_arg);

View File

@ -11,3 +11,9 @@ typedef enum {
WIFI_EVENT_AP_START, /**< ESP32 soft-AP start */
WIFI_EVENT_AP_STOP, /**< ESP32 soft-AP stop */
} mdns_used_event_t;
#define ESP_EVENT_DECLARE_BASE(x)
#define ESP_EVENT_ANY_ID (-1)
typedef void *esp_event_base_t;
typedef void *system_event_t;

View File

@ -1,3 +0,0 @@
idf_component_register(SRCS esp_event_mock.c
INCLUDE_DIRS include
REQUIRES esp_system_protocols_linux)

View File

@ -1,3 +1,3 @@
idf_component_register(SRCS esp_netif_linux.c
INCLUDE_DIRS include
REQUIRES esp_system_protocols_linux)
INCLUDE_DIRS include $ENV{IDF_PATH}/components/esp_netif/include
REQUIRES esp_event)

View File

@ -50,9 +50,7 @@ esp_err_t esp_netif_get_ip_info(esp_netif_t *esp_netif, esp_netif_ip_info_t *ip_
struct sockaddr_in *pAddr = (struct sockaddr_in *) tmp->ifa_addr;
inet_ntop(AF_INET, &pAddr->sin_addr, addr, sizeof(addr) );
if (strcmp(esp_netif->if_desc, tmp->ifa_name) == 0) {
printf("AF_INET: %s: %s\n", tmp->ifa_name, addr);
memcpy(&ip_info->ip.addr, &pAddr->sin_addr, 4);
break;
}
}
tmp = tmp->ifa_next;

View File

@ -1,3 +0,0 @@
idf_component_register(SRCS esp_log_impl.c strlcat.c
INCLUDE_DIRS include
REQUIRES esp_netif_linux esp_timer_linux freertos_linux esp_event_mock esp_netif log esp_common)

View File

@ -1,27 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_err.h"
#include "esp_log.h"
#include <stdlib.h>
void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression)
{
ESP_LOGE("ESP_ERROR_CHECK", "Failed with esp_err_t: 0x%x", rc);
ESP_LOGE("ESP_ERROR_CHECK", "Expression: %s", expression);
ESP_LOGE("ESP_ERROR_CHECK", "Functions: %s %s(%d)", function, file, line);
abort();
}
void esp_log_buffer_hexdump_internal(const char *tag, const void *buffer, uint16_t buff_len, esp_log_level_t log_level)
{
if ( LOG_LOCAL_LEVEL >= log_level ) {
ESP_LOG_LEVEL(log_level, tag, "Buffer:%p length:%d", buffer, buff_len);
for (int i = 0; i < buff_len; ++i) {
printf("%02x ", ((uint8_t *)buffer)[i]);
}
printf("\n");
}
}

View File

@ -1,8 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
size_t strlcat(char *dest, const char *src, size_t size);

View File

@ -1,70 +0,0 @@
/* $OpenBSD: strlcat.c,v 1.2 1999/06/17 16:28:58 millert Exp $ */
/*-
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "string.h"
/*
* Appends src to string dst of size siz (unlike strncat, siz is the
* full size of dst, not space left). At most siz-1 characters
* will be copied. Always NUL terminates (unless siz <= strlen(dst)).
* Returns strlen(src) + MIN(siz, strlen(initial dst)).
* If retval >= siz, truncation occurred.
*/
size_t
strlcat(dst, src, siz)
char *dst;
const char *src;
size_t siz;
{
char *d = dst;
const char *s = src;
size_t n = siz;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0') {
d++;
}
dlen = d - dst;
n = siz - dlen;
if (n == 0) {
return (dlen + strlen(s));
}
while (*s != '\0') {
if (n != 1) {
*d++ = *s;
n--;
}
s++;
}
*d = '\0';
return (dlen + (s - src)); /* count does not include NUL */
}

View File

@ -1,6 +1,6 @@
idf_component_register(SRCS esp_timer_linux.c timer_task.cpp
INCLUDE_DIRS include
REQUIRES esp_system_protocols_linux freertos_linux)
REQUIRES esp_event)
set_target_properties(${COMPONENT_LIB} PROPERTIES
CXX_STANDARD 17

View File

@ -16,7 +16,7 @@ typedef void (*cb_t)(void *arg);
class TimerTaskMock {
public:
TimerTaskMock(cb_t cb): cb(cb), t(run_static, this), active(false), ms(INT32_MAX) {}
TimerTaskMock(cb_t cb): cb(cb), active(false), ms(INT32_MAX) {}
~TimerTaskMock(void)
{
active = false;
@ -27,6 +27,7 @@ public:
{
ms = m;
active = true;
t = std::thread(run_static, this);
}
private:

View File

@ -1,6 +1,5 @@
idf_component_register(SRCS freertos_linux.c queue_unique_ptr.cpp
INCLUDE_DIRS include
REQUIRES esp_system_protocols_linux)
INCLUDE_DIRS include)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)

View File

@ -158,16 +158,6 @@ void xTaskCreate(TaskFunction_t pvTaskCode, const char *const pcName, const uint
}
}
uint32_t esp_get_free_heap_size(void)
{
return 0;
}
uint32_t esp_random(void)
{
return rand();
}
void xTaskNotifyGive(TaskHandle_t task)
{

View File

@ -32,3 +32,4 @@ typedef int BaseType_t;
uint32_t esp_get_free_heap_size(void);
uint32_t esp_random(void);
void vTaskSuspendAll(void);

View File

@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
//
// Created by david on 1/13/23.
//
#ifndef _QUEUE_H_
#define _QUEUE_H_
#endif //_QUEUE_H_

View File

@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
//
// Created by david on 1/13/23.
//
#ifndef _SEMAPHR_H_
#define _SEMAPHR_H_
#endif //_SEMAPHR_H_

View File

@ -0,0 +1,15 @@
menu "Test Configuration"
config TEST_HOSTNAME
string "mDNS Hostname"
default "esp32-mdns"
help
mDNS Hostname for example to use
config TEST_NETIF_NAME
string "Network interface name"
default "eth2"
help
Name/ID if the network interface on which we run the mDNS host test
endmenu

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -35,14 +35,16 @@ int main(int argc, char *argv[])
{
setvbuf(stdout, NULL, _IONBF, 0);
const esp_netif_inherent_config_t base_cg = { .if_key = "WIFI_STA_DEF", .if_desc = "eth2" };
const esp_netif_inherent_config_t base_cg = { .if_key = "WIFI_STA_DEF", .if_desc = CONFIG_TEST_NETIF_NAME };
esp_netif_config_t cfg = { .base = &base_cg };
esp_netif_t *sta = esp_netif_new(&cfg);
ESP_ERROR_CHECK(mdns_init());
ESP_ERROR_CHECK(mdns_hostname_set(CONFIG_TEST_HOSTNAME));
ESP_LOGI(TAG, "mdns hostname set to: [%s]", CONFIG_TEST_HOSTNAME);
ESP_ERROR_CHECK(mdns_register_netif(sta));
ESP_ERROR_CHECK(mdns_netif_action(sta, MDNS_EVENT_ENABLE_IP4 | MDNS_EVENT_IP4_REVERSE_LOOKUP));
mdns_init();
mdns_hostname_set("myesp");
ESP_LOGI(TAG, "mdns hostname set to: [%s]", "myesp");
#ifdef REGISTER_SERVICE
//set default mDNS instance name
mdns_instance_name_set("myesp-inst");
//structure with TXT records
@ -51,12 +53,12 @@ int main(int argc, char *argv[])
{"u", "user"},
{"p", "password"}
};
vTaskDelay(1000);
vTaskDelay(pdMS_TO_TICKS(10000));
ESP_ERROR_CHECK(mdns_service_add("myesp-service2", "_http", "_tcp", 80, serviceTxtData, 3));
vTaskDelay(2000);
query_mdns_host("david-comp");
vTaskDelay(2000);
#endif
vTaskDelay(pdMS_TO_TICKS(10000));
query_mdns_host("david-work");
vTaskDelay(pdMS_TO_TICKS(1000));
esp_netif_destroy(sta);
mdns_free();
ESP_LOGI(TAG, "Exit");

View File

@ -0,0 +1,9 @@
CONFIG_IDF_TARGET="linux"
CONFIG_MDNS_NETWORKING_SOCKET=y
CONFIG_MDNS_SKIP_SUPPRESSING_OWN_QUERIES=y
CONFIG_TEST_NETIF_NAME="eth0"
CONFIG_TEST_HOSTNAME="myesp"
CONFIG_MDNS_PREDEF_NETIF_STA=n
CONFIG_MDNS_PREDEF_NETIF_AP=n
CONFIG_MDNS_ENABLE_DEBUG_PRINTS=y
CONFIG_MDNS_RESPOND_REVERSE_QUERIES=y

View File

@ -55,7 +55,6 @@
#define INC_TASK_H
#define pdMS_TO_TICKS(a) a
#define portTICK_PERIOD_MS 10
#define xSemaphoreTake(s,d) true
#define xTaskDelete(a)
#define vTaskDelete(a) free(a)
@ -87,10 +86,6 @@ typedef int BaseType_t;
typedef uint32_t TickType_t;
extern const char *WIFI_EVENT;
extern const char *IP_EVENT;
extern const char *ETH_EVENT;
struct udp_pcb {
uint8_t dummy;
};

View File

@ -1,2 +1,7 @@
idf_component_register(SRC_DIRS "."
PRIV_REQUIRES cmock test_utils mdns)
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS ../.. "$ENV{IDF_PATH}/tools/unit-test-app/components")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mdns_test)

View File

@ -0,0 +1,5 @@
idf_component_register(SRCS "test_mdns.c"
REQUIRES test_utils
INCLUDE_DIRS "."
PRIV_REQUIRES unity mdns)

View File

@ -1,13 +1,16 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "test_utils.h"
#include "mdns.h"
#include "esp_event.h"
#include "unity.h"
#include "test_utils.h"
#include "unity_fixture.h"
#include "memory_checks.h"
#define MDNS_HOSTNAME "test-hostname"
#define MDNS_DELEGATE_HOSTNAME "delegate-hostname"
@ -16,6 +19,18 @@
#define MDNS_SERVICE_PROTO "_tcp"
#define MDNS_SERVICE_PORT 80
TEST_GROUP(mdns);
TEST_SETUP(mdns)
{
test_utils_record_free_mem();
TEST_ESP_OK(test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL));
}
TEST_TEAR_DOWN(mdns)
{
test_utils_finish_and_evaluate_leaks(32, 64);
}
static void yield_to_all_priorities(void)
{
@ -27,7 +42,7 @@ static void yield_to_all_priorities(void)
}
TEST_CASE("mdns api to fail in invalid state", "[mdns][leaks=64]")
TEST(mdns, api_fails_with_invalid_state)
{
TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_init() );
TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_hostname_set(MDNS_HOSTNAME) );
@ -35,7 +50,7 @@ TEST_CASE("mdns api to fail in invalid state", "[mdns][leaks=64]")
TEST_ASSERT_NOT_EQUAL(ESP_OK, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0) );
}
TEST_CASE("mdns init and deinit", "[mdns][leaks=64]")
TEST(mdns, init_deinit)
{
test_case_uses_tcpip();
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default());
@ -45,7 +60,7 @@ TEST_CASE("mdns init and deinit", "[mdns][leaks=64]")
esp_event_loop_delete_default();
}
TEST_CASE("mdns api return expected err-code and do not leak memory", "[mdns][leaks=64]")
TEST(mdns, api_fails_with_expected_err)
{
mdns_txt_item_t serviceTxtData[CONFIG_MDNS_MAX_SERVICES] = { {NULL, NULL},
};
@ -91,7 +106,7 @@ TEST_CASE("mdns api return expected err-code and do not leak memory", "[mdns][le
esp_event_loop_delete_default();
}
TEST_CASE("mdns query api return expected err-code and do not leak memory", "[leaks=64]")
TEST(mdns, query_api_fails_with_expected_err)
{
mdns_result_t *results = NULL;
esp_ip6_addr_t addr6;
@ -100,6 +115,14 @@ TEST_CASE("mdns query api return expected err-code and do not leak memory", "[le
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_create_default());
TEST_ASSERT_EQUAL(ESP_OK, mdns_init() );
// check it is not possible to register a service or set an instance without configuring the hostname
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, mdns_instance_name_set(MDNS_INSTANCE));
TEST_ASSERT_EQUAL(ESP_OK, mdns_hostname_set(MDNS_HOSTNAME));
// hostname is set, now adding a service and instance should succeed
TEST_ASSERT_EQUAL(ESP_OK, mdns_service_add(MDNS_INSTANCE, MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, MDNS_SERVICE_PORT, NULL, 0));
TEST_ASSERT_EQUAL(ESP_OK, mdns_instance_name_set(MDNS_INSTANCE));
TEST_ASSERT_EQUAL(ESP_OK, mdns_query_ptr(MDNS_SERVICE_NAME, MDNS_SERVICE_PROTO, 10, CONFIG_MDNS_MAX_SERVICES, &results) );
mdns_query_results_free(results);
@ -118,3 +141,16 @@ TEST_CASE("mdns query api return expected err-code and do not leak memory", "[le
mdns_free();
esp_event_loop_delete_default();
}
TEST_GROUP_RUNNER(mdns)
{
RUN_TEST_CASE(mdns, api_fails_with_invalid_state)
RUN_TEST_CASE(mdns, api_fails_with_expected_err)
RUN_TEST_CASE(mdns, query_api_fails_with_expected_err)
RUN_TEST_CASE(mdns, init_deinit)
}
void app_main(void)
{
UNITY_MAIN(mdns);
}

View File

@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from pytest_embedded import Dut
def test_lwip(dut: Dut) -> None:
dut.expect_unity_test_output()

View File

@ -0,0 +1,2 @@
CONFIG_UNITY_ENABLE_FIXTURE=y
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n