mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-06-25 09:21:32 +02:00
Compare commits
76 Commits
websocket-
...
docs/docs_
Author | SHA1 | Date | |
---|---|---|---|
a3e8421de8 | |||
3bf0511938 | |||
319fce018e | |||
44bae24c78 | |||
d047ff569c | |||
02be2b76f8 | |||
1c850ddacf | |||
9ae88aab48 | |||
c6db3ea84c | |||
b23eedac3a | |||
1407dfc2e7 | |||
34c6f52f70 | |||
c674b8855d | |||
256389613f | |||
2865255ec0 | |||
7b2470ddd6 | |||
dda2722563 | |||
36db37ea2b | |||
4f013e3522 | |||
231392d0c6 | |||
9fb01ca534 | |||
d464ed7d30 | |||
b94a211e4b | |||
73f2800b59 | |||
e00e056162 | |||
1b134486db | |||
7ba1085e9f | |||
3bdb2067e0 | |||
1d6888445d | |||
d68624ec4f | |||
d4825f5c53 | |||
de1480a072 | |||
b9b4a75000 | |||
cc259aa035 | |||
91134f10ff | |||
fb8a2f0198 | |||
b87bef52e5 | |||
28813148b2 | |||
ae38f27997 | |||
d0c9070715 | |||
a8339e4618 | |||
12cfcb5aed | |||
2cfffb056e | |||
0247926219 | |||
05fff94b91 | |||
8958d5e37c | |||
f71192b876 | |||
f9b47900f2 | |||
0e215b118f | |||
dc8b916561 | |||
a01096f621 | |||
3de4727385 | |||
59053b8c98 | |||
2956477353 | |||
1d9b6f7e90 | |||
6b684ceb0f | |||
5304a6ec0e | |||
c7f20ee98e | |||
7d47ec3b6b | |||
e4c8a5932c | |||
b56b1c577b | |||
4e212c31fe | |||
9a7bd90ad9 | |||
4314c78ca0 | |||
457f8335bb | |||
42565ff5d7 | |||
6e2bb518fd | |||
2180ab17d8 | |||
ac5d43882b | |||
3f0262dfe5 | |||
74787c9aa8 | |||
045009bb82 | |||
79c11e9ecd | |||
c73c797595 | |||
1547aa8c03 | |||
1ca139109a |
92
.github/workflows/gcov_analyzer.yml
vendored
Normal file
92
.github/workflows/gcov_analyzer.yml
vendored
Normal 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'
|
35
.github/workflows/host-test.yml
vendored
35
.github/workflows/host-test.yml
vendored
@ -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
|
||||
|
77
.github/workflows/publish-docs.yml
vendored
Normal file
77
.github/workflows/publish-docs.yml
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
name: Docs and Publish New
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
env:
|
||||
DOCS_DEPLOY_URL_BASE: "${{ secrets.DOCS_PREVIEW_SERVER_URL }}"
|
||||
#DOCS_DEPLOY_SERVER: "ci.espressif.cn:42348"
|
||||
DOCS_DEPLOY_SERVER: "ci.espressif.cn:42348"
|
||||
DOCS_DEPLOY_SERVER_USER: "${{ secrets.DOCS_SERVER_USER }}"
|
||||
DOCS_DEPLOY_PRIVATEKEY : "${{ secrets.DOCS_DEPLOY_KEY }}"
|
||||
DOCS_DEPLOY_KEY: "${{ secrets.DOCS_DEPLOY_KEY }}"
|
||||
DOCS_DEPLOY_PATH : "${{ secrets.DOCS_PATH }}"
|
||||
|
||||
jobs:
|
||||
docs_build:
|
||||
name: Docs-Build-And-Upload
|
||||
runs-on: ubuntu-latest
|
||||
# Skip running on forks since it won't have access to secrets
|
||||
#if: github.repository == 'espressif/esp-protocols'
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Generate and delploying docs
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install doxygen clang python3-pip
|
||||
python -m pip install breathe recommonmark esp-docs
|
||||
cd $GITHUB_WORKSPACE/components/esp_websocket_client/docs
|
||||
./generate_docs
|
||||
source $GITHUB_WORKSPACE/docs/utils.sh
|
||||
add_doc_server_ssh_keys $DOCS_DEPLOY_PRIVATEKEY $DOCS_DEPLOY_SERVER $DOCS_DEPLOY_SERVER_USER
|
||||
export GIT_VER=$(git describe --always)
|
||||
ls -l $GITHUB_WORKSPACE/components/esp_websocket_client/docs
|
||||
export DOCS_BUILD_DIR=$GITHUB_WORKSPACE/components/esp_websocket_client/docs
|
||||
deploy-docs
|
||||
|
||||
#build_docs:
|
||||
# stage: build
|
||||
# image: $ESP_DOCS_ENV_IMAGE
|
||||
# tags:
|
||||
# - build_docs
|
||||
# artifacts:
|
||||
# when: always
|
||||
# paths:
|
||||
# - docs/_build/*/*/*.txt
|
||||
# - docs/_build/*/*/html/*
|
||||
# expire_in: 4 days
|
||||
# # No cleaning when the artifacts
|
||||
# after_script: []
|
||||
# script:
|
||||
# - cd docs
|
||||
# - pip install -r requirements.txt
|
||||
# - build-docs -l en -t esp32
|
||||
#
|
||||
#.deploy_docs_template:
|
||||
# stage: deploy
|
||||
# image: $ESP_DOCS_ENV_IMAGE
|
||||
# tags:
|
||||
# - deploy_docs
|
||||
# needs:
|
||||
# - build_docs
|
||||
# only:
|
||||
# changes:
|
||||
# - "docs/**/*"
|
||||
# script:
|
||||
# - source ${CI_PROJECT_DIR}/docs/utils.sh
|
||||
# - add_doc_server_ssh_keys ${{ secrets.DOCS_DEPLOY_PRIVATEKEY }} ${{ secrets.DOCS_DEPLOY_SERVER }} ${{ secrets.DOCS_DEPLOY_SERVER_USER }}
|
||||
# - export GIT_VER=$(git describe --always)
|
||||
# - pip install -r ${CI_PROJECT_DIR}/docs/requirements.txt
|
||||
# - deploy-docs
|
||||
#
|
229
.github/workflows/target-test.yml
vendored
229
.github/workflows/target-test.yml
vendored
@ -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,32 +153,53 @@ 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
|
||||
|
||||
build_esp_mqtt_cxx:
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0"]
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: example, path: "components/esp_mqtt_cxx/examples" }]
|
||||
runs-on: ubuntu-20.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
|
||||
shell: bash
|
||||
working-directory: ${{matrix.test.path}}
|
||||
run: |
|
||||
${IDF_PATH}/install.sh --enable-pytest
|
||||
. ${IDF_PATH}/export.sh
|
||||
python $IDF_PATH/tools/ci/ci_build_apps.py . --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app
|
||||
|
||||
run-target-websocket:
|
||||
name: Run Websocket Example Test on target
|
||||
needs: build_websocket
|
||||
@ -204,14 +211,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 +229,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 +255,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 +290,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 +309,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 }}
|
||||
|
@ -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
7
ci/clean_build_artifacts.sh
Executable 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
|
@ -5,3 +5,4 @@ SimpleWebSocketServer
|
||||
dpkt
|
||||
pytest
|
||||
idf_build_apps
|
||||
netifaces
|
||||
|
@ -1,5 +1,7 @@
|
||||
# ASIO port
|
||||
|
||||
[](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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -1,5 +1,7 @@
|
||||
# ESP MODEM
|
||||
|
||||
[](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.
|
||||
|
||||
|
@ -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;
|
||||
|
10
components/esp_modem/examples/modem_psm/CMakeLists.txt
Normal file
10
components/esp_modem/examples/modem_psm/CMakeLists.txt
Normal 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)
|
32
components/esp_modem/examples/modem_psm/README.md
Normal file
32
components/esp_modem/examples/modem_psm/README.md
Normal 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.
|
@ -0,0 +1,3 @@
|
||||
|
||||
idf_component_register(SRCS "modem_psm.c"
|
||||
INCLUDE_DIRS ".")
|
109
components/esp_modem/examples/modem_psm/main/Kconfig.projbuild
Normal file
109
components/esp_modem/examples/modem_psm/main/Kconfig.projbuild
Normal 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
|
202
components/esp_modem/examples/modem_psm/main/modem_psm.c
Normal file
202
components/esp_modem/examples/modem_psm/main/modem_psm.c
Normal 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();
|
||||
}
|
@ -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
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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]+')
|
@ -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
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: "0.1.25"
|
||||
version: "0.1.27"
|
||||
description: esp modem
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem
|
||||
dependencies:
|
||||
|
@ -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 */
|
||||
|
@ -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) */
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
@ -87,7 +87,7 @@ void Netif::start()
|
||||
{
|
||||
ppp_dte->set_read_cb([this](uint8_t *data, size_t len) -> bool {
|
||||
receive(data, len);
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr);
|
||||
signal.set(PPP_STARTED);
|
||||
|
@ -39,7 +39,7 @@ void Netif::start()
|
||||
{
|
||||
ppp_dte->set_read_cb([this](uint8_t *data, size_t len) -> bool {
|
||||
receive(data, len);
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
netif->transmit = esp_modem_dte_transmit;
|
||||
netif->ctx = (void *)this;
|
||||
|
@ -125,9 +125,7 @@ void UartTerminal::task()
|
||||
case UART_DATA:
|
||||
uart_get_buffered_data_len(uart.port, &len);
|
||||
if (len && on_read) {
|
||||
if (on_read(nullptr, len)) {
|
||||
on_read = nullptr;
|
||||
}
|
||||
on_read(nullptr, len);
|
||||
}
|
||||
break;
|
||||
case UART_FIFO_OVF:
|
||||
|
@ -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()
|
||||
|
10
components/esp_modem/test/host_test/env.sh
Executable file
10
components/esp_modem/test/host_test/env.sh
Executable 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
|
9
components/esp_modem/test/host_test/main/Kconfig
Normal file
9
components/esp_modem/test/host_test/main/Kconfig
Normal 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
|
@ -0,0 +1 @@
|
||||
CONFIG_GCOV_ENABLED=y
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
83
components/esp_modem/test/target/pytest_pppd.py
Normal file
83
components/esp_modem/test/target/pytest_pppd.py
Normal 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()
|
@ -1,3 +1,4 @@
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
||||
CONFIG_PPP_SUPPORT=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096
|
||||
|
8
components/esp_mqtt_cxx/CMakeLists.txt
Normal file
8
components/esp_mqtt_cxx/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
idf_component_register(SRCS "esp_mqtt_cxx.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES mqtt
|
||||
)
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
73
components/esp_mqtt_cxx/docs/Doxyfile
Normal file
73
components/esp_mqtt_cxx/docs/Doxyfile
Normal file
@ -0,0 +1,73 @@
|
||||
# This is Doxygen configuration file
|
||||
#
|
||||
# Doxygen provides over 260 configuration statements
|
||||
# To make this file easier to follow,
|
||||
# it contains only statements that are non-default
|
||||
#
|
||||
# NOTE:
|
||||
# It is recommended not to change defaults unless specifically required
|
||||
# Test any changes how they affect generated documentation
|
||||
# Make sure that correct warnings are generated to flag issues with documented code
|
||||
#
|
||||
# For the complete list of configuration statements see:
|
||||
# http://doxygen.nl/manual/config.html
|
||||
|
||||
|
||||
PROJECT_NAME = "MQTT C++ client"
|
||||
|
||||
## The 'INPUT' statement below is used as input by script 'gen-df-input.py'
|
||||
## to automatically generate API reference list files heder_file.inc
|
||||
## These files are placed in '_inc' directory
|
||||
## and used to include in API reference documentation
|
||||
|
||||
INPUT = \
|
||||
$(PROJECT_PATH)/include/esp_mqtt.hpp \
|
||||
$(PROJECT_PATH)/include/esp_mqtt_client_config.hpp
|
||||
|
||||
## Get warnings for functions that have no documentation for their parameters or return value
|
||||
##
|
||||
WARN_NO_PARAMDOC = YES
|
||||
|
||||
## Enable preprocessing and remove __attribute__(...) expressions from the INPUT files
|
||||
##
|
||||
ENABLE_PREPROCESSING = YES
|
||||
MACRO_EXPANSION = YES
|
||||
EXPAND_ONLY_PREDEF = YES
|
||||
PREDEFINED = \
|
||||
$(ENV_DOXYGEN_DEFINES) \
|
||||
__DOXYGEN__=1 \
|
||||
__attribute__(x)= \
|
||||
_Static_assert()= \
|
||||
IDF_DEPRECATED(X)= \
|
||||
IRAM_ATTR= \
|
||||
configSUPPORT_DYNAMIC_ALLOCATION=1 \
|
||||
configSUPPORT_STATIC_ALLOCATION=1 \
|
||||
configQUEUE_REGISTRY_SIZE=1 \
|
||||
configUSE_RECURSIVE_MUTEXES=1 \
|
||||
configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS=1 \
|
||||
configNUM_THREAD_LOCAL_STORAGE_POINTERS=1 \
|
||||
configUSE_APPLICATION_TASK_TAG=1 \
|
||||
configTASKLIST_INCLUDE_COREID=1 \
|
||||
"ESP_EVENT_DECLARE_BASE(x)=extern esp_event_base_t x"
|
||||
|
||||
## Do not complain about not having dot
|
||||
##
|
||||
HAVE_DOT = NO
|
||||
|
||||
## Generate XML that is required for Breathe
|
||||
##
|
||||
GENERATE_XML = YES
|
||||
XML_OUTPUT = xml
|
||||
|
||||
GENERATE_HTML = NO
|
||||
HAVE_DOT = NO
|
||||
GENERATE_LATEX = NO
|
||||
GENERATE_MAN = YES
|
||||
|
||||
## Skip distracting progress messages
|
||||
##
|
||||
QUIET = YES
|
||||
## Enable Section Tags for conditional documentation
|
||||
##
|
||||
ENABLED_SECTIONS += \
|
||||
DOC_EXCLUDE_HEADER_SECTION ## To conditionally remove doc sections from IDF source files without affecting documentation in upstream files.
|
23
components/esp_mqtt_cxx/docs/conf_common.py
Normal file
23
components/esp_mqtt_cxx/docs/conf_common.py
Normal file
@ -0,0 +1,23 @@
|
||||
# flake8: noqa
|
||||
from esp_docs.conf_docs import *
|
||||
|
||||
extensions += [
|
||||
'sphinx_copybutton',
|
||||
# Needed as a trigger for running doxygen
|
||||
'esp_docs.esp_extensions.dummy_build_system',
|
||||
'esp_docs.esp_extensions.run_doxygen',
|
||||
]
|
||||
|
||||
# link roles config
|
||||
github_repo = 'espressif/esp-protocols'
|
||||
|
||||
# context used by sphinx_idf_theme
|
||||
html_context['github_user'] = 'espressif'
|
||||
html_context['github_repo'] = 'esp-docs'
|
||||
|
||||
# Extra options required by sphinx_idf_theme
|
||||
project_slug = 'esp-idf' # >=5.0
|
||||
versions_url = 'https://github.com/espressif/esp-protocols/docs/docs_versions.js'
|
||||
|
||||
idf_targets = ['esp32']
|
||||
languages = ['en']
|
25
components/esp_mqtt_cxx/docs/doxygen-known-warnings.txt
Normal file
25
components/esp_mqtt_cxx/docs/doxygen-known-warnings.txt
Normal file
@ -0,0 +1,25 @@
|
||||
esp_mqtt_client_config.hpp:line: warning: Compound idf::mqtt::BrokerConfiguration is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Compound idf::mqtt::ClientCredentials is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Compound idf::mqtt::Configuration is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Compound idf::mqtt::Connection is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Compound idf::mqtt::Event is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Compound idf::mqtt::LastWill is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Compound idf::mqtt::Session is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Compound idf::mqtt::Task is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member address (variable) of struct idf::mqtt::BrokerConfiguration is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member security (variable) of struct idf::mqtt::BrokerConfiguration is not documented.
|
||||
esp_mqtt.hpp:line: warning: Member operator()(esp_mqtt_client *client_handler) (function) of struct idf::mqtt::Client::MqttClientDeleter is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member username (variable) of struct idf::mqtt::ClientCredentials is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member authentication (variable) of struct idf::mqtt::ClientCredentials is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member client_id (variable) of struct idf::mqtt::ClientCredentials is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member event (variable) of struct idf::mqtt::Configuration is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member task (variable) of struct idf::mqtt::Configuration is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member session (variable) of struct idf::mqtt::Configuration is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member connection (variable) of struct idf::mqtt::Configuration is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member data (variable) of struct idf::mqtt::DER is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member len (variable) of struct idf::mqtt::DER is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member ds_data (variable) of struct idf::mqtt::DigitalSignatureData is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member data (variable) of struct idf::mqtt::Password is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member data (variable) of struct idf::mqtt::PEM is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member hint_key (variable) of struct idf::mqtt::PSK is not documented.
|
||||
esp_mqtt_client_config.hpp:line: warning: Member last_will (variable) of struct idf::mqtt::Session is not documented.
|
24
components/esp_mqtt_cxx/docs/en/conf.py
Normal file
24
components/esp_mqtt_cxx/docs/en/conf.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# English Language RTD & Sphinx config file
|
||||
#
|
||||
# Uses ../conf_common.py for most non-language-specific settings.
|
||||
|
||||
# Importing conf_common adds all the non-language-specific
|
||||
# parts to this conf module
|
||||
|
||||
try:
|
||||
from conf_common import * # noqa: F403,F401
|
||||
except ImportError:
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../'))
|
||||
from conf_common import * # noqa: F403,F401
|
||||
|
||||
# General information about the project.
|
||||
project = u'ESP-Protocols'
|
||||
copyright = u'2016 - 2023, Espressif Systems (Shanghai) Co., Ltd'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
language = 'en'
|
132
components/esp_mqtt_cxx/docs/en/index.rst
Normal file
132
components/esp_mqtt_cxx/docs/en/index.rst
Normal file
@ -0,0 +1,132 @@
|
||||
ESP MQTT C++ client
|
||||
====================
|
||||
|
||||
Overview
|
||||
--------
|
||||
The ESP MQTT client is a wrapper over the `esp_mqtt` client with the goal of providing a higher level API.
|
||||
|
||||
Features
|
||||
--------
|
||||
* Supports MQTT version 3.11
|
||||
* Adds a Filter validation class for topic filters
|
||||
* Split the event handlers to member functions
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The current design uses exception as an error handling mechanism, therefore exceptions need to be enabled in menuconfig.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
User code needs to inherit fromm :cpp:class:`idf::mqtt::Client` and provide overloads for the event handlers.
|
||||
|
||||
.. note:: The handler is available to allow user code to interact directly with it in case of need. This member will likely be made private in the future once the class API stabilizes.
|
||||
|
||||
|
||||
.. doxygenclass:: idf::mqtt::Client
|
||||
:members:
|
||||
:protected-members:
|
||||
|
||||
Event Handling
|
||||
--------------
|
||||
|
||||
Events are dispatched throug calls to member functions each one dedicated to a type of event.
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
|
||||
* :example:`tcp <../examples/tcp>`
|
||||
* :example:`ssl <../examples/ssl>`
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
Header File
|
||||
^^^^^^^^^^^
|
||||
|
||||
* :project_file:`include/esp_mqtt.hpp`
|
||||
|
||||
Structures
|
||||
^^^^^^^^^^
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::MQTTException
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::Message
|
||||
:members:
|
||||
|
||||
Classes
|
||||
^^^^^^^
|
||||
|
||||
|
||||
.. doxygenclass:: idf::mqtt::Filter
|
||||
:members:
|
||||
|
||||
Header File
|
||||
^^^^^^^^^^^
|
||||
|
||||
* :project_file:`include/esp_mqtt_client_config.hpp`
|
||||
|
||||
Structures
|
||||
^^^^^^^^^^
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::Host
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::URI
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::BrokerAddress
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::PEM
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::DER
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::Insecure
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::GlobalCAStore
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::PSK
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::Password
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::ClientCertificate
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::SecureElement
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::DigitalSignatureData
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::BrokerConfiguration
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::ClientCredentials
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::Event
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::LastWill
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::Session
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::Task
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::Connection
|
||||
:members:
|
||||
|
||||
.. doxygenstruct:: idf::mqtt::Configuration
|
||||
:members:
|
26
components/esp_mqtt_cxx/docs/generate_docs
Executable file
26
components/esp_mqtt_cxx/docs/generate_docs
Executable file
@ -0,0 +1,26 @@
|
||||
build-docs --target esp32 --language en
|
||||
|
||||
cp -rf _build/en/esp32/html .
|
||||
rm -rf _build __pycache__
|
||||
|
||||
# Modifes some version and target fields of index.html
|
||||
echo "<script type="text/javascript">
|
||||
window.onload =(function() {
|
||||
var myAnchor = document.getElementById('version-select');
|
||||
var mySpan = document.createElement('input');
|
||||
mySpan.setAttribute('type', 'text');
|
||||
mySpan.setAttribute('maxLength', '10');
|
||||
mySpan.value = 'latest';
|
||||
mySpan.setAttribute('disabled', true);
|
||||
myAnchor.parentNode.replaceChild(mySpan, myAnchor);
|
||||
|
||||
var myAnchor = document.getElementById('target-select');
|
||||
var mySpan = document.createElement('input');
|
||||
mySpan.setAttribute('type', 'text');
|
||||
mySpan.setAttribute('maxLength', '10');
|
||||
mySpan.value = 'all targets';
|
||||
mySpan.setAttribute('disabled', true);
|
||||
myAnchor.parentNode.replaceChild(mySpan, myAnchor);
|
||||
|
||||
})();
|
||||
</script>" >> html/index.html
|
292
components/esp_mqtt_cxx/esp_mqtt_cxx.cpp
Normal file
292
components/esp_mqtt_cxx/esp_mqtt_cxx.cpp
Normal file
@ -0,0 +1,292 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "mqtt_client.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "esp_mqtt.hpp"
|
||||
#include "esp_mqtt_client_config.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper for static assert.
|
||||
template<class T>
|
||||
constexpr bool always_false = false;
|
||||
|
||||
template<class... Ts> struct overloaded : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
using namespace idf::mqtt;
|
||||
|
||||
/*
|
||||
* This function is responsible for fill in the configurations for the broker related data
|
||||
* of mqtt_client_config_t
|
||||
*/
|
||||
void config_broker(esp_mqtt_client_config_t &mqtt_client_cfg, BrokerConfiguration const &broker)
|
||||
{
|
||||
std::visit(overloaded{
|
||||
[&mqtt_client_cfg](Host const & host)
|
||||
{
|
||||
mqtt_client_cfg.broker.address.hostname = host.address.c_str();
|
||||
mqtt_client_cfg.broker.address.path = host.path.c_str();
|
||||
mqtt_client_cfg.broker.address.transport = host.transport;
|
||||
},
|
||||
[&mqtt_client_cfg](URI const & uri)
|
||||
{
|
||||
mqtt_client_cfg.broker.address.uri = uri.address.c_str();
|
||||
},
|
||||
[]([[maybe_unused ]]auto & unknown)
|
||||
{
|
||||
static_assert(always_false<decltype(unknown)>, "Missing type handler for variant handler");
|
||||
}
|
||||
},
|
||||
broker.address.address);
|
||||
|
||||
std::visit(overloaded{
|
||||
[]([[maybe_unused]]Insecure const & insecure) {},
|
||||
[&mqtt_client_cfg](GlobalCAStore const & use_global_store)
|
||||
{
|
||||
mqtt_client_cfg.broker.verification.use_global_ca_store = true;
|
||||
},
|
||||
[&mqtt_client_cfg](CryptographicInformation const & certificates)
|
||||
{
|
||||
std::visit(overloaded{
|
||||
[&mqtt_client_cfg](PEM const & pem)
|
||||
{
|
||||
mqtt_client_cfg.broker.verification.certificate = pem.data;
|
||||
}, [&mqtt_client_cfg](DER const & der)
|
||||
{
|
||||
mqtt_client_cfg.broker.verification.certificate = der.data;
|
||||
mqtt_client_cfg.broker.verification.certificate_len = der.len;
|
||||
}}, certificates);
|
||||
},
|
||||
[]([[maybe_unused]] PSK const & psk) {},
|
||||
[]([[maybe_unused]] auto & unknown)
|
||||
{
|
||||
static_assert(always_false<decltype(unknown)>, "Missing type handler for variant handler");
|
||||
}
|
||||
},
|
||||
broker.security);
|
||||
mqtt_client_cfg.broker.address.port = broker.address.port;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is responsible for fill in the configurations for the client credentials related data
|
||||
* of mqtt_client_config_t
|
||||
*/
|
||||
void config_client_credentials(esp_mqtt_client_config_t &mqtt_client_cfg, ClientCredentials const &credentials)
|
||||
{
|
||||
mqtt_client_cfg.credentials.client_id = credentials.client_id.has_value() ? credentials.client_id.value().c_str() : nullptr ;
|
||||
mqtt_client_cfg.credentials.username = credentials.username.has_value() ? credentials.username.value().c_str() : nullptr ;
|
||||
std::visit(overloaded{
|
||||
[&mqtt_client_cfg](Password const & password)
|
||||
{
|
||||
mqtt_client_cfg.credentials.authentication.password = password.data.c_str();
|
||||
},
|
||||
[](ClientCertificate const & certificate) {},
|
||||
[](SecureElement const & enable_secure_element) {},
|
||||
[]([[maybe_unused ]]auto & unknown)
|
||||
{
|
||||
static_assert(always_false<decltype(unknown)>, "Missing type handler for variant handler");
|
||||
}
|
||||
}, credentials.authentication);
|
||||
}
|
||||
|
||||
esp_mqtt_client_config_t make_config(BrokerConfiguration const &broker, ClientCredentials const &credentials, Configuration const &config)
|
||||
{
|
||||
esp_mqtt_client_config_t mqtt_client_cfg{};
|
||||
config_broker(mqtt_client_cfg, broker);
|
||||
config_client_credentials(mqtt_client_cfg, credentials);
|
||||
return mqtt_client_cfg;
|
||||
}
|
||||
}
|
||||
|
||||
namespace idf::mqtt {
|
||||
|
||||
Client::Client(BrokerConfiguration const &broker, ClientCredentials const &credentials, Configuration const &config): Client(make_config(broker, credentials, config)) {}
|
||||
|
||||
Client::Client(esp_mqtt_client_config_t const &config) : handler(esp_mqtt_client_init(&config))
|
||||
{
|
||||
if (handler == nullptr) {
|
||||
throw MQTTException(ESP_FAIL);
|
||||
};
|
||||
CHECK_THROW_SPECIFIC(esp_mqtt_client_register_event(handler.get(), MQTT_EVENT_ANY, mqtt_event_handler, this), mqtt::MQTTException);
|
||||
CHECK_THROW_SPECIFIC(esp_mqtt_client_start(handler.get()), mqtt::MQTTException);
|
||||
}
|
||||
|
||||
|
||||
void Client::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) noexcept
|
||||
{
|
||||
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
|
||||
auto *event = static_cast<esp_mqtt_event_t *>(event_data);
|
||||
auto &client = *static_cast<Client *>(handler_args);
|
||||
switch (event->event_id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
|
||||
client.on_connected(event);
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||
client.on_disconnected(event);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
|
||||
client.on_subscribed(event);
|
||||
break;
|
||||
case MQTT_EVENT_UNSUBSCRIBED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
|
||||
client.on_unsubscribed(event);
|
||||
break;
|
||||
case MQTT_EVENT_PUBLISHED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
|
||||
client.on_published(event);
|
||||
break;
|
||||
case MQTT_EVENT_DATA:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
|
||||
client.on_data(event);
|
||||
break;
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
|
||||
client.on_error(event);
|
||||
break;
|
||||
case MQTT_EVENT_BEFORE_CONNECT:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_BEFORE_CONNECT");
|
||||
client.on_before_connect(event);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Client::on_error(esp_mqtt_event_handle_t const event)
|
||||
{
|
||||
auto log_error_if_nonzero = [](const char *message, int error_code) {
|
||||
if (error_code != 0) {
|
||||
ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
|
||||
}
|
||||
};
|
||||
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
|
||||
log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
|
||||
log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
|
||||
log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
|
||||
ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
|
||||
}
|
||||
}
|
||||
void Client::on_disconnected(esp_mqtt_event_handle_t const event)
|
||||
{
|
||||
}
|
||||
void Client::on_subscribed(esp_mqtt_event_handle_t const event)
|
||||
{
|
||||
printf("Subscribed to %.*s\r\n", event->topic_len, event->topic);
|
||||
}
|
||||
void Client::on_unsubscribed(esp_mqtt_event_handle_t const event)
|
||||
{
|
||||
}
|
||||
void Client::on_published(esp_mqtt_event_handle_t const event)
|
||||
{
|
||||
}
|
||||
void Client::on_before_connect(esp_mqtt_event_handle_t const event)
|
||||
{
|
||||
}
|
||||
void Client::on_connected(esp_mqtt_event_handle_t const event)
|
||||
{
|
||||
}
|
||||
void Client::on_data(esp_mqtt_event_handle_t const event)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<MessageID> Client::subscribe(std::string const &topic, QoS qos)
|
||||
{
|
||||
auto res = esp_mqtt_client_subscribe(handler.get(), topic.c_str(),
|
||||
static_cast<int>(qos));
|
||||
if (res < 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return MessageID{res};
|
||||
}
|
||||
|
||||
bool is_valid(std::string::const_iterator first, std::string::const_iterator last)
|
||||
{
|
||||
if (first == last) {
|
||||
return false;
|
||||
}
|
||||
auto number = std::find(first, last, '#');
|
||||
if (number != last) {
|
||||
if (std::next(number) != last) {
|
||||
return false;
|
||||
}
|
||||
if (*std::prev(number) != '/' && number != first) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto plus = std::find(first, last, '+');
|
||||
if (plus != last) {
|
||||
if (*(std::prev(plus)) != '/' && plus != first) {
|
||||
return false;
|
||||
}
|
||||
if (std::next(plus) != last && *(std::next(plus)) != '/') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Filter::Filter(std::string user_filter) : filter(std::move(user_filter))
|
||||
{
|
||||
if (!is_valid(filter.begin(), filter.end())) {
|
||||
throw std::domain_error("Forbidden Filter string");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Filter::match(std::string::const_iterator topic_begin, std::string::const_iterator topic_end) const noexcept
|
||||
{
|
||||
auto filter_begin = filter.begin();
|
||||
auto filter_end = filter.end();
|
||||
for (auto mismatch = std::mismatch(filter_begin, filter_end, topic_begin);
|
||||
mismatch.first != filter.end() and mismatch.second != topic_end;
|
||||
mismatch = std::mismatch(filter_begin, filter_end, topic_begin)) {
|
||||
if (*mismatch.first != '#' and * mismatch.first != '+') {
|
||||
return false;
|
||||
}
|
||||
if (*mismatch.first == '#') {
|
||||
return true;
|
||||
}
|
||||
if (*mismatch.first == '+') {
|
||||
filter_begin = advance(mismatch.first, filter_end);
|
||||
topic_begin = advance(mismatch.second, topic_end);
|
||||
if (filter_begin == filter_end and topic_begin != topic_end) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const std::string &Filter::get()
|
||||
{
|
||||
return filter;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Filter::match(char *const begin, int size) const noexcept
|
||||
{
|
||||
auto it = static_cast<std::string::const_iterator>(begin);
|
||||
return match(it, it + size);
|
||||
}
|
||||
std::string::const_iterator Filter::advance(std::string::const_iterator begin, std::string::const_iterator end) const
|
||||
{
|
||||
constexpr auto separator = '/';
|
||||
return std::find(begin, end, separator);
|
||||
}
|
||||
|
||||
}
|
14
components/esp_mqtt_cxx/examples/ssl/CMakeLists.txt
Normal file
14
components/esp_mqtt_cxx/examples/ssl/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
# The following four lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# (Not part of the boilerplate) This example uses an extra component for common
|
||||
# functions such as Wi-Fi and Ethernet connection.
|
||||
# The path to esp_mqtt_cxx is also added.
|
||||
set(EXTRA_COMPONENT_DIRS
|
||||
"../../../../common_components/protocol_examples_common" "../../")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(mqtt_ssl_cxx)
|
||||
|
||||
target_add_binary_data(mqtt_ssl_cxx.elf "main/mqtt_eclipseprojects_io.pem" TEXT)
|
2
components/esp_mqtt_cxx/examples/ssl/README.md
Normal file
2
components/esp_mqtt_cxx/examples/ssl/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- |
|
3
components/esp_mqtt_cxx/examples/ssl/main/CMakeLists.txt
Normal file
3
components/esp_mqtt_cxx/examples/ssl/main/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "mqtt_ssl_example.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
@ -0,0 +1,9 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config BROKER_URI
|
||||
string "Broker URL"
|
||||
default "mqtts://mqtt.eclipseprojects.io:8883"
|
||||
help
|
||||
URL of the broker to connect to
|
||||
|
||||
endmenu
|
@ -0,0 +1,27 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
|
||||
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
|
||||
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
|
||||
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
|
||||
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
|
||||
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
|
||||
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
|
||||
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
|
||||
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
|
||||
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
|
||||
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
|
||||
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
|
||||
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
|
||||
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
|
||||
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
|
||||
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
|
||||
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
|
||||
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
|
||||
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
|
||||
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
|
||||
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
|
||||
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
|
||||
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
|
||||
-----END CERTIFICATE-----
|
@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
|
||||
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
|
||||
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
||||
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
|
||||
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
|
||||
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
|
||||
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
|
||||
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
|
||||
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
|
||||
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
|
||||
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
|
||||
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
|
||||
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
|
||||
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
|
||||
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
|
||||
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
|
||||
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
|
||||
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
|
||||
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
|
||||
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
|
||||
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
|
||||
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
|
||||
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
|
||||
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
|
||||
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
|
||||
nLRbwHOoq7hHwg==
|
||||
-----END CERTIFICATE-----
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* C++ MQTT (over TCP) Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include "esp_mqtt_client_config.hpp"
|
||||
#include "nvs_flash.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_mqtt.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr auto *TAG = "MQTT_EXAMPLE";
|
||||
|
||||
extern const char mqtt_eclipse_org_pem_start[] asm("_binary_mqtt_eclipseprojects_io_pem_start");
|
||||
extern const char mqtt_eclipse_org_pem_end[] asm("_binary_mqtt_eclipseprojects_io_pem_end");
|
||||
|
||||
class MyClient final : public idf::mqtt::Client {
|
||||
public:
|
||||
using idf::mqtt::Client::Client;
|
||||
private:
|
||||
void on_connected(esp_mqtt_event_handle_t const event) override
|
||||
{
|
||||
using idf::mqtt::QoS;
|
||||
subscribe(messages.get());
|
||||
subscribe(sent_load.get(), QoS::AtMostOnce);
|
||||
}
|
||||
void on_data(esp_mqtt_event_handle_t const event) override
|
||||
{
|
||||
if (messages.match(event->topic, event->topic_len)) {
|
||||
ESP_LOGI(TAG, "Received in the messages topic");
|
||||
}
|
||||
}
|
||||
idf::mqtt::Filter messages{std::string{"$SYS/broker/messages/received"}};
|
||||
idf::mqtt::Filter sent_load{std::string{"$SYS/broker/load/+/sent"}};
|
||||
};
|
||||
}
|
||||
|
||||
namespace mqtt = idf::mqtt;
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "[APP] Startup..");
|
||||
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
|
||||
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
|
||||
|
||||
esp_log_level_set("*", ESP_LOG_INFO);
|
||||
esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE);
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
mqtt::BrokerConfiguration broker{
|
||||
.address = {mqtt::URI{std::string{CONFIG_BROKER_URI}}},
|
||||
.security = mqtt::CryptographicInformation{mqtt::PEM{mqtt_eclipse_org_pem_start}}
|
||||
};
|
||||
idf::mqtt::ClientCredentials credentials{};
|
||||
idf::mqtt::Configuration config{};
|
||||
|
||||
MyClient client{broker, credentials, config};
|
||||
while (true) {
|
||||
constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS;
|
||||
vTaskDelay( xDelay );
|
||||
}
|
||||
}
|
8
components/esp_mqtt_cxx/examples/ssl/pytest_mqtt_ssl.py
Normal file
8
components/esp_mqtt_cxx/examples/ssl/pytest_mqtt_ssl.py
Normal file
@ -0,0 +1,8 @@
|
||||
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
def test_dummy() -> None:
|
||||
pass
|
21
components/esp_mqtt_cxx/examples/ssl/sdkconfig.defaults
Normal file
21
components/esp_mqtt_cxx/examples/ssl/sdkconfig.defaults
Normal file
@ -0,0 +1,21 @@
|
||||
# Enable C++ exceptions and set emergency pool size for exception objects
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_IP101=y
|
||||
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=y
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_IP101=y
|
||||
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=y
|
12
components/esp_mqtt_cxx/examples/tcp/CMakeLists.txt
Normal file
12
components/esp_mqtt_cxx/examples/tcp/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
# The following four lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
# The path to esp_mqtt_cxx is also added.
|
||||
set(EXTRA_COMPONENT_DIRS
|
||||
"../../../../common_components/protocol_examples_common" "../../")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(mqtt_tcp_cxx)
|
2
components/esp_mqtt_cxx/examples/tcp/README.md
Normal file
2
components/esp_mqtt_cxx/examples/tcp/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- |
|
3
components/esp_mqtt_cxx/examples/tcp/main/CMakeLists.txt
Normal file
3
components/esp_mqtt_cxx/examples/tcp/main/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "mqtt_tcp_example.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
@ -0,0 +1,9 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config BROKER_URL
|
||||
string "Broker URL"
|
||||
default "mqtt://mqtt.eclipseprojects.io"
|
||||
help
|
||||
URL of the broker to connect to
|
||||
|
||||
endmenu
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
/* C++ MQTT (over TCP) Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include "nvs_flash.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_mqtt.hpp"
|
||||
#include "esp_mqtt_client_config.hpp"
|
||||
|
||||
namespace mqtt = idf::mqtt;
|
||||
|
||||
namespace {
|
||||
constexpr auto *TAG = "MQTT_EXAMPLE";
|
||||
|
||||
class MyClient final : public mqtt::Client {
|
||||
public:
|
||||
using mqtt::Client::Client;
|
||||
|
||||
private:
|
||||
void on_connected(esp_mqtt_event_handle_t const event) override
|
||||
{
|
||||
using mqtt::QoS;
|
||||
subscribe(messages.get());
|
||||
subscribe(sent_load.get(), QoS::AtMostOnce);
|
||||
}
|
||||
void on_data(esp_mqtt_event_handle_t const event) override
|
||||
{
|
||||
if (messages.match(event->topic, event->topic_len)) {
|
||||
ESP_LOGI(TAG, "Received in the messages topic");
|
||||
}
|
||||
}
|
||||
mqtt::Filter messages{"$SYS/broker/messages/received"};
|
||||
mqtt::Filter sent_load{"$SYS/broker/load/+/sent"};
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "[APP] Startup..");
|
||||
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
|
||||
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
|
||||
|
||||
esp_log_level_set("*", ESP_LOG_INFO);
|
||||
esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("TRANSPORT_TCP", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("TRANSPORT_SSL", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE);
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
mqtt::BrokerConfiguration broker{
|
||||
.address = {mqtt::URI{std::string{CONFIG_BROKER_URL}}},
|
||||
.security = mqtt::Insecure{}
|
||||
};
|
||||
mqtt::ClientCredentials credentials{};
|
||||
mqtt::Configuration config{};
|
||||
|
||||
MyClient client{broker, credentials, config};
|
||||
|
||||
while (true) {
|
||||
constexpr TickType_t xDelay = 500 / portTICK_PERIOD_MS;
|
||||
vTaskDelay(xDelay);
|
||||
}
|
||||
}
|
8
components/esp_mqtt_cxx/examples/tcp/pytest_mqtt_tcp.py
Normal file
8
components/esp_mqtt_cxx/examples/tcp/pytest_mqtt_tcp.py
Normal file
@ -0,0 +1,8 @@
|
||||
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
def test_dummy() -> None:
|
||||
pass
|
3
components/esp_mqtt_cxx/examples/tcp/sdkconfig.defaults
Normal file
3
components/esp_mqtt_cxx/examples/tcp/sdkconfig.defaults
Normal file
@ -0,0 +1,3 @@
|
||||
# Enable C++ exceptions and set emergency pool size for exception objects
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024
|
8
components/esp_mqtt_cxx/idf_component.yml
Normal file
8
components/esp_mqtt_cxx/idf_component.yml
Normal file
@ -0,0 +1,8 @@
|
||||
version: "0.1.0"
|
||||
description: esp mqtt cxx
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_mqtt_cxx
|
||||
dependencies:
|
||||
espressif/esp-idf-cxx: "^1.0.0-beta"
|
||||
# Required IDF version
|
||||
idf:
|
||||
version: ">=5.0"
|
296
components/esp_mqtt_cxx/include/esp_mqtt.hpp
Normal file
296
components/esp_mqtt_cxx/include/esp_mqtt.hpp
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#ifndef __cpp_exceptions
|
||||
#error MQTT class can only be used when __cpp_exceptions is enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
|
||||
#endif
|
||||
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "esp_exception.hpp"
|
||||
#include "esp_mqtt_client_config.hpp"
|
||||
#include "mqtt_client.h"
|
||||
|
||||
namespace idf::mqtt {
|
||||
|
||||
constexpr auto *TAG = "mqtt_client_cpp";
|
||||
|
||||
struct MQTTException : ESPException {
|
||||
using ESPException::ESPException;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief QoS for publish and subscribe
|
||||
*
|
||||
* Sets the QoS as:
|
||||
* AtMostOnce : Best effort delivery of messages. Message loss can occur.
|
||||
* AtLeastOnce : Guaranteed delivery of messages. Duplicates can occur.
|
||||
* ExactlyOnce : Guaranteed delivery of messages exactly once.
|
||||
*
|
||||
* @note
|
||||
* When subscribing to a topic the QoS means the maximum QoS that should be sent to
|
||||
* client on this topic
|
||||
*/
|
||||
enum class QoS { AtMostOnce = 0, AtLeastOnce = 1, ExactlyOnce = 2 };
|
||||
|
||||
/**
|
||||
* @brief Sets if a message must be retained.
|
||||
*
|
||||
* Retained messages are delivered to future subscribers that match the topic name.
|
||||
*
|
||||
*/
|
||||
enum class Retain : bool { NotRetained = false, Retained = true };
|
||||
|
||||
|
||||
/**
|
||||
* @brief Message class template to publish.
|
||||
*
|
||||
*/
|
||||
template <typename T> struct Message {
|
||||
T data; /*!< Data for publish. Should be a contiguous type*/
|
||||
QoS qos = QoS::AtLeastOnce; /*!< QoS for the message*/
|
||||
Retain retain = Retain::NotRetained; /*!< Retention mark for the message.*/
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Message type that holds std::string for data
|
||||
*
|
||||
*/
|
||||
using StringMessage = Message<std::string>;
|
||||
|
||||
[[nodiscard]] bool filter_is_valid(std::string::const_iterator first, std::string::const_iterator last);
|
||||
|
||||
/**
|
||||
* @brief Filter for mqtt topic subscription.
|
||||
*
|
||||
* Topic filter.
|
||||
*
|
||||
*/
|
||||
class Filter {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Constructs the topic filter from the user filter
|
||||
* @throws std::domain_error if the filter is invalid.
|
||||
*
|
||||
* @param user_filter Filter to be used.
|
||||
*/
|
||||
explicit Filter(std::string user_filter);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get the filter string used.
|
||||
*
|
||||
* @return Reference to the topic filter.
|
||||
*/
|
||||
const std::string &get();
|
||||
|
||||
/**
|
||||
* @brief Checks the filter string against a topic name.
|
||||
*
|
||||
* @param topic_begin Iterator to the beginning of the sequence.
|
||||
* @param topic_end Iterator to the end of the sequence.
|
||||
*
|
||||
* @return true if the topic name match the filter
|
||||
*/
|
||||
[[nodiscard]] bool match(std::string::const_iterator topic_begin, std::string::const_iterator topic_end) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Checks the filter string against a topic name.
|
||||
*
|
||||
* @param topic topic name
|
||||
*
|
||||
* @return true if the topic name match the filter
|
||||
*/
|
||||
[[nodiscard]] bool match(const std::string &topic) const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Checks the filter string against a topic name.
|
||||
*
|
||||
* @param begin Char array with topic name.
|
||||
* @param size Size of given topic name.
|
||||
*
|
||||
* @return true if the topic name match the filter
|
||||
*/
|
||||
[[nodiscard]] bool match(char *begin, int size) const noexcept;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* @brief Advance the topic to the next level.
|
||||
*
|
||||
* An mqtt topic ends with a /. This function is used to iterate in topic levels.
|
||||
*
|
||||
* @return Iterator to the start of the topic.
|
||||
*/
|
||||
[[nodiscard]] std::string::const_iterator advance(std::string::const_iterator begin, std::string::const_iterator end) const;
|
||||
std::string filter;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Message identifier to track delivery.
|
||||
*
|
||||
*/
|
||||
enum class MessageID : int {};
|
||||
|
||||
/**
|
||||
* @brief Base class for MQTT client
|
||||
*
|
||||
* Should be inherited to provide event handlers.
|
||||
*/
|
||||
class Client {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor of the client
|
||||
*
|
||||
* @param broker Configuration for broker connection
|
||||
* @param credentials client credentials to be presented to the broker
|
||||
* @param config Mqtt client configuration
|
||||
*/
|
||||
Client(const BrokerConfiguration &broker, const ClientCredentials &credentials, const Configuration &config);
|
||||
|
||||
/**
|
||||
* @brief Constructs Client using the same configuration used for
|
||||
* `esp_mqtt_client`
|
||||
* @param config config struct to `esp_mqtt_client`
|
||||
*/
|
||||
Client(const esp_mqtt_client_config_t &config);
|
||||
|
||||
/**
|
||||
* @brief Subscribe to topic
|
||||
*
|
||||
* @param topic_filter MQTT topic filter
|
||||
* @param qos QoS subscription, defaulted as QoS::AtLeastOnce
|
||||
*
|
||||
* @return Optional MessageID. In case of failure std::nullopt is returned.
|
||||
*/
|
||||
std::optional<MessageID> subscribe(const std::string &topic_filter, QoS qos = QoS::AtLeastOnce);
|
||||
|
||||
/**
|
||||
* @brief publish message to topic
|
||||
*
|
||||
* @tparam Container Type for data container. Must be a contiguous memory.
|
||||
* @param topic Topic name
|
||||
* @param message Message struct containing data, qos and retain
|
||||
* configuration.
|
||||
*
|
||||
* @return Optional MessageID. In case of failure std::nullopt is returned.
|
||||
*/
|
||||
template <class Container>
|
||||
std::optional<MessageID> publish(const std::string &topic, const Message<Container> &message)
|
||||
{
|
||||
return publish(topic, std::begin(message.data), std::end(message.data), message.qos, message.retain);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief publish message to topic
|
||||
*
|
||||
* @tparam InputIt Input data iterator type.
|
||||
* @param topic Topic name
|
||||
* @param first, last Iterator pair of data to publish
|
||||
* @param qos Set qos message
|
||||
* @param retain Set if message should be retained
|
||||
*
|
||||
* @return Optional MessageID. In case of failure std::nullopt is returned.
|
||||
*/
|
||||
template <class InputIt>
|
||||
std::optional<MessageID> publish(const std::string &topic, InputIt first, InputIt last, QoS qos = QoS::AtLeastOnce, Retain retain = Retain::NotRetained)
|
||||
{
|
||||
auto size = std::distance(first, last);
|
||||
auto res = esp_mqtt_client_publish(handler.get(), topic.c_str(), &(*first), size, static_cast<int>(qos),
|
||||
static_cast<int>(retain));
|
||||
if (res < 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return MessageID{res};
|
||||
}
|
||||
|
||||
virtual ~Client() = default;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Helper type to be used as custom deleter for std::unique_ptr.
|
||||
*/
|
||||
struct MqttClientDeleter {
|
||||
void operator()(esp_mqtt_client *client_handler)
|
||||
{
|
||||
esp_mqtt_client_destroy(client_handler);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Type of the handler for the underlying mqtt_client handler.
|
||||
* It uses std::unique_ptr for lifetime management
|
||||
*/
|
||||
using ClientHandler = std::unique_ptr<esp_mqtt_client, MqttClientDeleter>;
|
||||
/**
|
||||
* @brief esp_mqtt_client handler
|
||||
*
|
||||
*/
|
||||
ClientHandler handler;
|
||||
|
||||
/**
|
||||
* @brief Called if there is an error event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
virtual void on_error(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an disconnection event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
virtual void on_disconnected(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an subscribed event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
virtual void on_subscribed(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an unsubscribed event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
virtual void on_unsubscribed(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an published event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
virtual void on_published(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an before connect event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*/
|
||||
virtual void on_before_connect(const esp_mqtt_event_handle_t event);
|
||||
/**
|
||||
* @brief Called if there is an connected event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*
|
||||
*/
|
||||
virtual void on_connected(const esp_mqtt_event_handle_t event) = 0;
|
||||
/**
|
||||
* @brief Called if there is an data event
|
||||
*
|
||||
* @param event mqtt event data
|
||||
*
|
||||
*/
|
||||
virtual void on_data(const esp_mqtt_event_handle_t event) = 0;
|
||||
private:
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id,
|
||||
void *event_data) noexcept;
|
||||
void init(const esp_mqtt_client_config_t &config);
|
||||
};
|
||||
} // namespace idf::mqtt
|
213
components/esp_mqtt_cxx/include/esp_mqtt_client_config.hpp
Normal file
213
components/esp_mqtt_cxx/include/esp_mqtt_client_config.hpp
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "mqtt_client.h"
|
||||
|
||||
namespace idf::mqtt {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Broker addresss
|
||||
*
|
||||
* Use this to set the broker without parsing the URI string.
|
||||
*
|
||||
*/
|
||||
struct Host {
|
||||
std::string address; /*!< Host name*/
|
||||
std::string path; /*!< Route path of the broker in host*/
|
||||
esp_mqtt_transport_t transport; /*!< Transport scheme to use. */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Broker addresss URI
|
||||
*
|
||||
* Use this to set the broker address using the URI.
|
||||
*
|
||||
*/
|
||||
struct URI {
|
||||
std::string address; /*!< Broker adddress URI*/
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Broker addresss.
|
||||
*
|
||||
*/
|
||||
struct BrokerAddress {
|
||||
std::variant<Host, URI> address; /*!< Address, defined by URI or Host struct */
|
||||
uint32_t port = 0; /*!< Port used, defaults to 0 to select common port for the scheme used */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief PEM formated data
|
||||
*
|
||||
* Store certificates, keys and cryptographic data.
|
||||
*
|
||||
*/
|
||||
struct PEM {
|
||||
const char *data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief DER formated data
|
||||
*
|
||||
* Store certificates, keys and cryptographic data.
|
||||
*
|
||||
*/
|
||||
struct DER {
|
||||
const char *data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Holds cryptography related information
|
||||
*
|
||||
* Hold PEM or DER formated cryptographic data.
|
||||
*
|
||||
*/
|
||||
using CryptographicInformation = std::variant<PEM, DER>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Do not verify broker certificate.
|
||||
*
|
||||
* To be used when doing MQTT over TLS connection but not verify broker's certificates.
|
||||
*
|
||||
*/
|
||||
struct Insecure {};
|
||||
|
||||
/**
|
||||
* @brief Use global CA store
|
||||
*
|
||||
* To be used when client should use the Global CA Store to get trusted certificates for the broker.
|
||||
*
|
||||
*/
|
||||
struct GlobalCAStore {};
|
||||
|
||||
/**
|
||||
* @brief Use a pre shared key for broker authentication.
|
||||
*
|
||||
* To be used when client should use a PSK to authenticate the broker.
|
||||
*
|
||||
*/
|
||||
struct PSK {
|
||||
const struct psk_key_hint *hint_key;/* Pointer to PSK struct defined in esp_tls.h to enable PSK authentication */
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Authentication method for Broker
|
||||
*
|
||||
* Selects the method for authentication based on the type it holds.
|
||||
*
|
||||
*/
|
||||
using BrokerAuthentication = std::variant<Insecure, GlobalCAStore, CryptographicInformation, PSK>;
|
||||
|
||||
/**
|
||||
* @brief Password related data.
|
||||
*
|
||||
*/
|
||||
struct Password {
|
||||
std::string data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Data to authenticate client with certificates.
|
||||
*
|
||||
*/
|
||||
struct ClientCertificate {
|
||||
CryptographicInformation certificate; /*!< Certificate in PEM or DER format.*/
|
||||
CryptographicInformation key; /*!< Key data in PEM or DER format.*/
|
||||
std::optional<Password> key_password = std::nullopt; /*!< Optional password for key */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Used to select usage of Secure Element
|
||||
*
|
||||
* Enables the usage of the secure element present in ESP32-WROOM-32SE.
|
||||
*
|
||||
*/
|
||||
struct SecureElement {};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Used to select usage of Digital Signature Peripheral.
|
||||
*
|
||||
* Enables the usage of the Digital Signature hardware accelerator.
|
||||
*
|
||||
*/
|
||||
struct DigitalSignatureData {
|
||||
void *ds_data; /* carrier of handle for digital signature parameters */
|
||||
};
|
||||
|
||||
using AuthenticationFactor = std::variant<Password, ClientCertificate, SecureElement>;
|
||||
|
||||
struct BrokerConfiguration {
|
||||
BrokerAddress address;
|
||||
BrokerAuthentication security;
|
||||
};
|
||||
|
||||
struct ClientCredentials {
|
||||
std::optional<std::string> username; // MQTT username
|
||||
AuthenticationFactor authentication;
|
||||
std::vector<std::string> alpn_protos; /*!< List of supported application protocols to be used for ALPN */
|
||||
/* default is ``ESP32_%CHIPID%`` where %CHIPID% are last 3 bytes of MAC address in hex format */
|
||||
std::optional<std::string > client_id = std::nullopt;
|
||||
};
|
||||
|
||||
struct Event {
|
||||
mqtt_event_callback_t event_handle; /*!< handle for MQTT events as a callback in legacy mode */
|
||||
esp_event_loop_handle_t event_loop_handle; /*!< handle for MQTT event loop library */
|
||||
};
|
||||
|
||||
struct LastWill {
|
||||
const char *lwt_topic; /*!< LWT (Last Will and Testament) message topic (NULL by default) */
|
||||
const char *lwt_msg; /*!< LWT message (NULL by default) */
|
||||
int lwt_qos; /*!< LWT message qos */
|
||||
int lwt_retain; /*!< LWT retained message flag */
|
||||
int lwt_msg_len; /*!< LWT message length */
|
||||
};
|
||||
|
||||
struct Session {
|
||||
LastWill last_will;
|
||||
int disable_clean_session; /*!< mqtt clean session, default clean_session is true */
|
||||
int keepalive; /*!< mqtt keepalive, default is 120 seconds */
|
||||
bool disable_keepalive; /*!< Set disable_keepalive=true to turn off keep-alive mechanism, false by default (keepalive is active by default). Note: setting the config value `keepalive` to `0` doesn't disable keepalive feature, but uses a default keepalive period */
|
||||
esp_mqtt_protocol_ver_t protocol_ver; /*!< MQTT protocol version used for connection, defaults to value from menuconfig*/
|
||||
};
|
||||
|
||||
struct Task {
|
||||
int task_prio; /*!< MQTT task priority, default is 5, can be changed in ``make menuconfig`` */
|
||||
int task_stack; /*!< MQTT task stack size, default is 6144 bytes, can be changed in ``make menuconfig`` */
|
||||
};
|
||||
|
||||
struct Connection {
|
||||
esp_mqtt_transport_t transport; /*!< overrides URI transport */
|
||||
int reconnect_timeout_ms; /*!< Reconnect to the broker after this value in miliseconds if auto reconnect is not disabled (defaults to 10s) */
|
||||
int network_timeout_ms; /*!< Abort network operation if it is not completed after this value, in milliseconds (defaults to 10s) */
|
||||
int refresh_connection_after_ms; /*!< Refresh connection after this value (in milliseconds) */
|
||||
bool disable_auto_reconnect; /*!< this mqtt client will reconnect to server (when errors/disconnect). Set disable_auto_reconnect=true to disable */
|
||||
};
|
||||
|
||||
struct Configuration {
|
||||
Event event;
|
||||
Task task;
|
||||
Session session;
|
||||
Connection connection;
|
||||
void *user_context; /*!< pass user context to this option, then can receive that context in ``event->user_context`` */
|
||||
int buffer_size; /*!< size of MQTT send/receive buffer, default is 1024 (only receive buffer size if ``out_buffer_size`` defined) */
|
||||
int out_buffer_size; /*!< size of MQTT output buffer. If not defined, both output and input buffers have the same size defined as ``buffer_size`` */
|
||||
};
|
||||
|
||||
} // idf::mqtt
|
@ -1,5 +1,7 @@
|
||||
# ESP WEBSOCKET CLIENT
|
||||
|
||||
[](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
|
||||
|
@ -18,8 +18,9 @@
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_tls_crypto.h"
|
||||
|
||||
static const char *TAG = "WEBSOCKET_CLIENT";
|
||||
static const char *TAG = "websocket_client";
|
||||
|
||||
#define WEBSOCKET_TCP_DEFAULT_PORT (80)
|
||||
#define WEBSOCKET_SSL_DEFAULT_PORT (443)
|
||||
@ -55,6 +56,7 @@ static const char *TAG = "WEBSOCKET_CLIENT";
|
||||
|
||||
#define WS_OVER_TCP_SCHEME "ws"
|
||||
#define WS_OVER_TLS_SCHEME "wss"
|
||||
#define WS_HTTP_BASIC_AUTH "Basic "
|
||||
|
||||
const static int STOPPED_BIT = BIT0;
|
||||
const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame was sent by the client
|
||||
@ -63,6 +65,7 @@ const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame
|
||||
ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS);
|
||||
|
||||
typedef struct {
|
||||
const char *task_name;
|
||||
int task_stack;
|
||||
int task_prio;
|
||||
char *uri;
|
||||
@ -71,6 +74,7 @@ typedef struct {
|
||||
char *scheme;
|
||||
char *username;
|
||||
char *password;
|
||||
char *auth;
|
||||
int port;
|
||||
bool auto_reconnect;
|
||||
void *user_context;
|
||||
@ -103,6 +107,7 @@ typedef enum {
|
||||
struct esp_websocket_client {
|
||||
esp_event_loop_handle_t event_handle;
|
||||
TaskHandle_t task_handle;
|
||||
esp_websocket_error_codes_t error_handle;
|
||||
esp_transport_list_handle_t transport_list;
|
||||
esp_transport_handle_t transport;
|
||||
websocket_config_storage_t *config;
|
||||
@ -115,8 +120,11 @@ 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;
|
||||
size_t errormsg_size;
|
||||
char *errormsg_buffer;
|
||||
char *rx_buffer;
|
||||
char *tx_buffer;
|
||||
int buffer_size;
|
||||
@ -189,6 +197,16 @@ static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle
|
||||
event_data.payload_len = client->payload_len;
|
||||
event_data.payload_offset = client->payload_offset;
|
||||
|
||||
if (client->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
|
||||
event_data.error_handle.esp_tls_last_esp_err = esp_tls_get_and_clear_last_error(esp_transport_get_error_handle(client->transport),
|
||||
&client->error_handle.esp_tls_stack_err,
|
||||
&client->error_handle.esp_tls_cert_verify_flags);
|
||||
event_data.error_handle.esp_transport_sock_errno = esp_transport_get_errno(client->transport);
|
||||
}
|
||||
event_data.error_handle.error_type = client->error_handle.error_type;
|
||||
event_data.error_handle.esp_ws_handshake_status_code = client->error_handle.esp_ws_handshake_status_code;
|
||||
|
||||
|
||||
if ((err = esp_event_post_to(client->event_handle,
|
||||
WEBSOCKET_EVENTS, event,
|
||||
&event_data,
|
||||
@ -199,7 +217,7 @@ static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle
|
||||
return esp_event_loop_run(client->event_handle, 0);
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client)
|
||||
static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client, esp_websocket_error_type_t error_type)
|
||||
{
|
||||
ESP_WS_CLIENT_STATE_CHECK(TAG, client, return ESP_FAIL);
|
||||
esp_transport_close(client->transport);
|
||||
@ -208,25 +226,71 @@ static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_hand
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms);
|
||||
}
|
||||
|
||||
client->error_handle.error_type = error_type;
|
||||
client->state = WEBSOCKET_STATE_WAIT_TIMEOUT;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DISCONNECTED, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_error_connection(esp_websocket_client_handle_t client)
|
||||
static esp_err_t esp_websocket_client_error(esp_websocket_client_handle_t client, const char *format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
static esp_err_t esp_websocket_client_error(esp_websocket_client_handle_t client, const char *format, ...)
|
||||
{
|
||||
ESP_WS_CLIENT_STATE_CHECK(TAG, client, return ESP_FAIL);
|
||||
esp_transport_close(client->transport);
|
||||
va_list myargs;
|
||||
va_start(myargs, format);
|
||||
|
||||
if (client->config->auto_reconnect) {
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms);
|
||||
size_t needed_size = vsnprintf(NULL, 0, format, myargs);
|
||||
needed_size++; // null terminator
|
||||
|
||||
if (needed_size > client->errormsg_size) {
|
||||
if (client->errormsg_buffer) {
|
||||
free(client->errormsg_buffer);
|
||||
}
|
||||
client->errormsg_buffer = malloc(needed_size);
|
||||
if (client->errormsg_buffer == NULL) {
|
||||
client->errormsg_size = 0;
|
||||
ESP_LOGE(TAG, "Failed to allocate...");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
client->errormsg_size = needed_size;
|
||||
}
|
||||
client->state = WEBSOCKET_STATE_WAIT_TIMEOUT;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_ERROR, NULL, 0);
|
||||
|
||||
needed_size = vsnprintf(client->errormsg_buffer, client->errormsg_size, format, myargs);
|
||||
|
||||
va_end(myargs);
|
||||
|
||||
ESP_LOGE(TAG, "%s", client->errormsg_buffer);
|
||||
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_ERROR, client->errormsg_buffer, needed_size);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static char *http_auth_basic(const char *username, const char *password)
|
||||
{
|
||||
int out;
|
||||
char *user_info = NULL;
|
||||
char *digest = NULL;
|
||||
size_t n = 0;
|
||||
|
||||
if (asprintf(&user_info, "%s:%s", username, password) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!user_info) {
|
||||
ESP_LOGE(TAG, "No enough memory for user information");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_crypto_base64_encode(NULL, 0, &n, (const unsigned char *)user_info, strlen(user_info));
|
||||
digest = calloc(1, strlen(WS_HTTP_BASIC_AUTH) + n + 1);
|
||||
if (digest) {
|
||||
strcpy(digest, WS_HTTP_BASIC_AUTH);
|
||||
esp_crypto_base64_encode((unsigned char *)digest + 6, n, (size_t *)&out, (const unsigned char *)user_info, strlen(user_info));
|
||||
}
|
||||
free(user_info);
|
||||
return digest;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t client, const esp_websocket_client_config_t *config)
|
||||
{
|
||||
websocket_config_storage_t *cfg = client->config;
|
||||
@ -235,6 +299,8 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c
|
||||
cfg->task_prio = WEBSOCKET_TASK_PRIORITY;
|
||||
}
|
||||
|
||||
cfg->task_name = config->task_name;
|
||||
|
||||
cfg->task_stack = config->task_stack;
|
||||
if (cfg->task_stack == 0) {
|
||||
cfg->task_stack = WEBSOCKET_TASK_STACK;
|
||||
@ -261,6 +327,12 @@ static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t c
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->password, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (cfg->username && cfg->password) {
|
||||
free(cfg->auth);
|
||||
cfg->auth = http_auth_basic(cfg->username, cfg->password);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->auth, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (config->uri) {
|
||||
free(cfg->uri);
|
||||
cfg->uri = strdup(config->uri);
|
||||
@ -333,6 +405,7 @@ static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle
|
||||
free(cfg->scheme);
|
||||
free(cfg->username);
|
||||
free(cfg->password);
|
||||
free(cfg->auth);
|
||||
free(cfg->subprotocol);
|
||||
free(cfg->user_agent);
|
||||
free(cfg->headers);
|
||||
@ -342,6 +415,27 @@ 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);
|
||||
free(client->errormsg_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);
|
||||
@ -351,6 +445,7 @@ static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_
|
||||
.sub_protocol = client->config->subprotocol,
|
||||
.user_agent = client->config->user_agent,
|
||||
.headers = client->config->headers,
|
||||
.auth = client->config->auth,
|
||||
.propagate_control_frames = true
|
||||
};
|
||||
return esp_transport_ws_set_config(trans, &config);
|
||||
@ -527,11 +622,14 @@ 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) {
|
||||
buffer_size = WEBSOCKET_BUFFER_SIZE_BYTE;
|
||||
}
|
||||
client->errormsg_buffer = NULL;
|
||||
client->errormsg_size = 0;
|
||||
#ifndef CONFIG_ESP_WS_CLIENT_ENABLE_DYNAMIC_BUFFER
|
||||
client->rx_buffer = malloc(buffer_size);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->rx_buffer, {
|
||||
@ -563,22 +661,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;
|
||||
}
|
||||
|
||||
@ -646,6 +738,19 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_set_headers(esp_websocket_client_handle_t client, const char *headers)
|
||||
{
|
||||
if (client == NULL || client->state != WEBSOCKET_STATE_CONNECTED || headers == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
|
||||
esp_err_t ret = esp_transport_ws_set_headers(client->transport, headers);
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
|
||||
{
|
||||
int rlen;
|
||||
@ -657,8 +762,15 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
|
||||
do {
|
||||
rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms);
|
||||
if (rlen < 0) {
|
||||
ESP_LOGE(TAG, "Error read data");
|
||||
esp_websocket_free_buf(client, false);
|
||||
esp_tls_error_handle_t error_handle = esp_transport_get_error_handle(client->transport);
|
||||
if (error_handle) {
|
||||
esp_websocket_client_error(client, "esp_transport_read() failed with %d, transport_error=%s, tls_error_code=%i, tls_flags=%i, errno=%d",
|
||||
rlen, esp_err_to_name(error_handle->last_error), error_handle->esp_tls_error_code,
|
||||
error_handle->esp_tls_flags, errno);
|
||||
} else {
|
||||
esp_websocket_client_error(client, "esp_transport_read() failed with %d, errno=%d", rlen, errno);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
client->payload_len = esp_transport_ws_get_read_payload_len(client->transport);
|
||||
@ -727,21 +839,32 @@ static void esp_websocket_client_task(void *pv)
|
||||
client->run = false;
|
||||
break;
|
||||
}
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_BEFORE_CONNECT, NULL, 0);
|
||||
int result = esp_transport_connect(client->transport,
|
||||
client->config->host,
|
||||
client->config->port,
|
||||
client->config->network_timeout_ms);
|
||||
if (result < 0) {
|
||||
ESP_LOGE(TAG, "Error transport connect %i", result);
|
||||
esp_websocket_client_error_connection(client);
|
||||
esp_tls_error_handle_t error_handle = esp_transport_get_error_handle(client->transport);
|
||||
client->error_handle.esp_ws_handshake_status_code = esp_transport_ws_get_upgrade_request_status(client->transport);
|
||||
if (error_handle) {
|
||||
esp_websocket_client_error(client, "esp_transport_connect() failed with %d,\
|
||||
transport_error=%s, tls_error_code=%i, tls_flags=%i, esp_ws_handshake_status_code=%d, errno=%d",
|
||||
result, esp_err_to_name(error_handle->last_error), error_handle->esp_tls_error_code,
|
||||
error_handle->esp_tls_flags, client->error_handle.esp_ws_handshake_status_code, errno);
|
||||
} else {
|
||||
esp_websocket_client_error(client, "esp_transport_connect() failed with %d, esp_ws_handshake_status_code=%d, errno=%d",
|
||||
result, client->error_handle.esp_ws_handshake_status_code, errno);
|
||||
}
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "Transport connected to %s://%s:%d", client->config->scheme, client->config->host, client->config->port);
|
||||
|
||||
client->state = WEBSOCKET_STATE_CONNECTED;
|
||||
client->wait_for_pong_resp = false;
|
||||
client->error_handle.error_type = WEBSOCKET_ERROR_TYPE_NONE;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CONNECTED, NULL, 0);
|
||||
|
||||
break;
|
||||
case WEBSOCKET_STATE_CONNECTED:
|
||||
if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) { // only send and check for PING
|
||||
@ -759,13 +882,14 @@ static void esp_websocket_client_task(void *pv)
|
||||
|
||||
if ( _tick_get_ms() - client->pingpong_tick_ms > client->config->pingpong_timeout_sec * 1000 ) {
|
||||
if (client->wait_for_pong_resp) {
|
||||
ESP_LOGE(TAG, "Error, no PONG received for more than %d seconds after PING", client->config->pingpong_timeout_sec);
|
||||
esp_websocket_client_abort_connection(client);
|
||||
esp_websocket_client_error(client, "Error, no PONG received for more than %d seconds after PING", client->config->pingpong_timeout_sec);
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_PONG_TIMEOUT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (read_select == 0) {
|
||||
ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_read()...");
|
||||
break;
|
||||
@ -774,7 +898,7 @@ static void esp_websocket_client_task(void *pv)
|
||||
|
||||
if (esp_websocket_client_recv(client) == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Error receive data");
|
||||
esp_websocket_client_abort_connection(client);
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -806,8 +930,15 @@ static void esp_websocket_client_task(void *pv)
|
||||
if (WEBSOCKET_STATE_CONNECTED == client->state) {
|
||||
read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms
|
||||
if (read_select < 0) {
|
||||
ESP_LOGE(TAG, "Network error: esp_transport_poll_read() returned %d, errno=%d", read_select, errno);
|
||||
esp_websocket_client_abort_connection(client);
|
||||
esp_tls_error_handle_t error_handle = esp_transport_get_error_handle(client->transport);
|
||||
if (error_handle) {
|
||||
esp_websocket_client_error(client, "esp_transport_poll_read() returned %d, transport_error=%s, tls_error_code=%i, tls_flags=%i, errno=%d",
|
||||
read_select, esp_err_to_name(error_handle->last_error), error_handle->esp_tls_error_code,
|
||||
error_handle->esp_tls_flags, errno);
|
||||
} else {
|
||||
esp_websocket_client_error(client, "esp_transport_poll_read() returned %d, errno=%d", read_select, errno);
|
||||
}
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
}
|
||||
} else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) {
|
||||
// waiting for reconnecting...
|
||||
@ -833,6 +964,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);
|
||||
}
|
||||
|
||||
@ -850,7 +984,8 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (xTaskCreate(esp_websocket_client_task, "websocket_task", client->config->task_stack, client, client->config->task_prio, &client->task_handle) != pdTRUE) {
|
||||
if (xTaskCreate(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
|
||||
client->config->task_stack, client, client->config->task_prio, &client->task_handle) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "Error create websocket task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
@ -1000,9 +1135,16 @@ int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client,
|
||||
(timeout == portMAX_DELAY) ? -1 : timeout * portTICK_PERIOD_MS);
|
||||
if (wlen < 0 || (wlen == 0 && need_write != 0)) {
|
||||
ret = wlen;
|
||||
ESP_LOGE(TAG, "Network error: esp_transport_write() returned %d, errno=%d", ret, errno);
|
||||
esp_websocket_free_buf(client, true);
|
||||
esp_websocket_client_abort_connection(client);
|
||||
esp_tls_error_handle_t error_handle = esp_transport_get_error_handle(client->transport);
|
||||
if (error_handle) {
|
||||
esp_websocket_client_error(client, "esp_transport_write() returned %d, transport_error=%s, tls_error_code=%i, tls_flags=%i, errno=%d",
|
||||
ret, esp_err_to_name(error_handle->last_error), error_handle->esp_tls_error_code,
|
||||
error_handle->esp_tls_flags, errno);
|
||||
} else {
|
||||
esp_websocket_client_error(client, "esp_transport_write() returned %d, errno=%d", ret, errno);
|
||||
}
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
goto unlock_and_return;
|
||||
}
|
||||
current_opcode = 0;
|
||||
|
@ -2,7 +2,7 @@
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../..")
|
||||
set(EXTRA_COMPONENT_DIRS "..")
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "../../../common_components/protocol_examples_common")
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
@ -32,11 +32,18 @@
|
||||
|
||||
#define NO_DATA_TIMEOUT_SEC 5
|
||||
|
||||
static const char *TAG = "WEBSOCKET";
|
||||
static const char *TAG = "websocket";
|
||||
|
||||
static TimerHandle_t shutdown_signal_timer;
|
||||
static SemaphoreHandle_t shutdown_sema;
|
||||
|
||||
static void log_error_if_nonzero(const char *message, int error_code)
|
||||
{
|
||||
if (error_code != 0) {
|
||||
ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
|
||||
}
|
||||
}
|
||||
|
||||
static void shutdown_signaler(TimerHandle_t xTimer)
|
||||
{
|
||||
ESP_LOGI(TAG, "No data received for %d seconds, signaling shutdown", NO_DATA_TIMEOUT_SEC);
|
||||
@ -71,6 +78,12 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
|
||||
log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
|
||||
if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
|
||||
log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
|
||||
log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
|
||||
log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
|
||||
}
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DATA:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
|
||||
@ -99,6 +112,12 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i
|
||||
break;
|
||||
case WEBSOCKET_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
|
||||
log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
|
||||
if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
|
||||
log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
|
||||
log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
|
||||
log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -154,9 +173,9 @@ void app_main(void)
|
||||
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
|
||||
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
|
||||
esp_log_level_set("*", ESP_LOG_INFO);
|
||||
esp_log_level_set("WEBSOCKET_CLIENT", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("TRANSPORT_WS", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("TRANS_TCP", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("websocket_client", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("transport_ws", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("trans_tcp", ESP_LOG_DEBUG);
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
|
@ -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 json
|
||||
import random
|
||||
@ -81,7 +81,7 @@ def test_examples_protocol_websocket(dut):
|
||||
def test_close(dut):
|
||||
code = dut.expect(
|
||||
re.compile(
|
||||
b'WEBSOCKET: Received closed message with code=(\\d*)'))[0]
|
||||
b'websocket: Received closed message with code=(\\d*)'))[0]
|
||||
print('Received close frame with code {}'.format(code))
|
||||
|
||||
def test_json(dut, websocket):
|
||||
|
@ -35,9 +35,35 @@ typedef enum {
|
||||
WEBSOCKET_EVENT_DISCONNECTED, /*!< The connection has been disconnected */
|
||||
WEBSOCKET_EVENT_DATA, /*!< When receiving data from the server, possibly multiple portions of the packet */
|
||||
WEBSOCKET_EVENT_CLOSED, /*!< The connection has been closed cleanly */
|
||||
WEBSOCKET_EVENT_BEFORE_CONNECT, /*!< The event occurs before connecting */
|
||||
WEBSOCKET_EVENT_MAX
|
||||
} esp_websocket_event_id_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket connection error codes propagated via ERROR event
|
||||
*/
|
||||
typedef enum {
|
||||
WEBSOCKET_ERROR_TYPE_NONE = 0,
|
||||
WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT,
|
||||
WEBSOCKET_ERROR_TYPE_PONG_TIMEOUT,
|
||||
WEBSOCKET_ERROR_TYPE_HANDSHAKE
|
||||
} esp_websocket_error_type_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket error code structure to be passed as a contextual information into ERROR event
|
||||
*/
|
||||
typedef struct {
|
||||
/* compatible portion of the struct corresponding to struct esp_tls_last_error */
|
||||
esp_err_t esp_tls_last_esp_err; /*!< last esp_err code reported from esp-tls component */
|
||||
int esp_tls_stack_err; /*!< tls specific error code reported from underlying tls stack */
|
||||
int esp_tls_cert_verify_flags; /*!< tls flags reported from underlying tls stack during certificate verification */
|
||||
/* esp-websocket specific structure extension */
|
||||
esp_websocket_error_type_t error_type;
|
||||
int esp_ws_handshake_status_code; /*!< http status code of the websocket upgrade handshake */
|
||||
/* tcp_transport extension */
|
||||
int esp_transport_sock_errno; /*!< errno from the underlying socket */
|
||||
} esp_websocket_error_codes_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket event data
|
||||
*/
|
||||
@ -50,6 +76,7 @@ typedef struct {
|
||||
void *user_context; /*!< user_data context, from esp_websocket_client_config_t user_data */
|
||||
int payload_len; /*!< Total payload length, payloads exceeding buffer will be posted through multiple events */
|
||||
int payload_offset; /*!< Actual offset for the data associated with this event */
|
||||
esp_websocket_error_codes_t error_handle; /*!< esp-websocket error handle including esp-tls errors as well as internal websocket errors */
|
||||
} esp_websocket_event_data_t;
|
||||
|
||||
/**
|
||||
@ -68,12 +95,13 @@ typedef struct {
|
||||
const char *uri; /*!< Websocket URI, the information on the URI can be overrides the other fields below, if any */
|
||||
const char *host; /*!< Domain or IP as string */
|
||||
int port; /*!< Port to connect, default depend on esp_websocket_transport_t (80 or 443) */
|
||||
const char *username; /*!< Using for Http authentication - Not supported for now */
|
||||
const char *password; /*!< Using for Http authentication - Not supported for now */
|
||||
const char *username; /*!< Using for Http authentication, only support basic auth now */
|
||||
const char *password; /*!< Using for Http authentication */
|
||||
const char *path; /*!< HTTP Path, if not set, default is `/` */
|
||||
bool disable_auto_reconnect; /*!< Disable the automatic reconnect function when disconnected */
|
||||
void *user_context; /*!< HTTP user data context */
|
||||
int task_prio; /*!< Websocket task priority */
|
||||
const char *task_name; /*!< Websocket task name */
|
||||
int task_stack; /*!< Websocket task stack */
|
||||
int buffer_size; /*!< Websocket buffer size */
|
||||
const char *cert_pem; /*!< Pointer to certificate data in PEM or DER format for server verify (with SSL), default is NULL, not required to verify the server. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in cert_len. */
|
||||
@ -126,6 +154,17 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
|
||||
*/
|
||||
esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, const char *uri);
|
||||
|
||||
/**
|
||||
* @brief Set additional websocket headers for the client, when performing this behavior, the headers will replace the old ones
|
||||
* @pre Must stop the WebSocket client before set headers if the client has been connected
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param headers additional header strings each terminated with \r\n
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_set_headers(esp_websocket_client_handle_t client, const char *headers);
|
||||
|
||||
/**
|
||||
* @brief Open the WebSocket connection
|
||||
*
|
||||
@ -166,6 +205,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)
|
||||
*
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,7 @@
|
||||
# mDNS Service
|
||||
|
||||
[](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
|
||||
|
@ -1,6 +1,6 @@
|
||||
mDNS Service
|
||||
============
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
`zh_CN:[中文] <https://espressif.github.io/esp-protocols/mdns/zh_CN/index.html>`_
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
@ -1,6 +1,6 @@
|
||||
mDNS 服务
|
||||
=========
|
||||
:link_to_translation:`en:[English]`
|
||||
`en:[English] <https://espressif.github.io/esp-protocols/mdns/en/index.html>`_
|
||||
|
||||
概述
|
||||
----
|
||||
|
@ -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
|
||||
*/
|
||||
@ -276,9 +276,15 @@ void app_main(void)
|
||||
* This is typically performed in "GOT_IP" event handler, but we call it here directly
|
||||
* since the `EXAMPLE_INTERFACE` netif is connected already, to keep the example simple.
|
||||
*/
|
||||
ESP_ERROR_CHECK(mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_ENABLE_IP4));
|
||||
ESP_ERROR_CHECK(mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_ANNOUNCE_IP4));
|
||||
ESP_ERROR_CHECK(mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_ENABLE_IP4 | MDNS_EVENT_ENABLE_IP6));
|
||||
ESP_ERROR_CHECK(mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_ANNOUNCE_IP4 | MDNS_EVENT_ANNOUNCE_IP6));
|
||||
|
||||
#if defined(CONFIG_MDNS_RESPOND_REVERSE_QUERIES)
|
||||
ESP_ERROR_CHECK(mdns_netif_action(EXAMPLE_INTERFACE, MDNS_EVENT_IP4_REVERSE_LOOKUP | MDNS_EVENT_IP6_REVERSE_LOOKUP));
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_MDNS_ADD_CUSTOM_NETIF
|
||||
|
||||
initialise_button();
|
||||
xTaskCreate(&mdns_example_task, "mdns_example_task", 2048, NULL, 5, NULL);
|
||||
}
|
||||
|
@ -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):
|
||||
@ -117,10 +120,11 @@ def test_examples_protocol_mdns(dut):
|
||||
2. get the dut host name (and IP address)
|
||||
3. check the mdns name is accessible
|
||||
4. check DUT output if mdns advertized host is resolved
|
||||
5. check if DUT responds to dig
|
||||
6. check the DUT is searchable via reverse IP lookup
|
||||
"""
|
||||
|
||||
specific_host = dut.expect(re.compile(
|
||||
b'mdns hostname set to: \[(.*?)\]')).group(1).decode() # noqa: W605
|
||||
specific_host = dut.expect(r'mdns hostname set to: \[(.*?)\]')[1].decode()
|
||||
|
||||
mdns_server_events = {
|
||||
'stop': Event(),
|
||||
@ -129,9 +133,14 @@ def test_examples_protocol_mdns(dut):
|
||||
}
|
||||
mdns_responder = Thread(target=mdns_server,
|
||||
args=(str(specific_host), mdns_server_events))
|
||||
ip_address = dut.expect(
|
||||
re.compile(b'IPv4 address:([a-zA-Z0-9]*).*')).group(1).decode()
|
||||
print('Connected to AP with IP: {}'.format(ip_address))
|
||||
ipv4 = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]',
|
||||
timeout=30)[1].decode()
|
||||
ip_addresses = [ipv4]
|
||||
if dut.app.sdkconfig.get('LWIP_IPV6') is True:
|
||||
ipv6_r = r':'.join((r'[0-9a-fA-F]{4}', ) * 8)
|
||||
ipv6 = dut.expect(ipv6_r, timeout=30)[0].decode()
|
||||
ip_addresses.append(ipv6)
|
||||
print('Connected with IP addresses: {}'.format(','.join(ip_addresses)))
|
||||
try:
|
||||
# 3. check the mdns name is accessible.
|
||||
mdns_responder.start()
|
||||
@ -162,11 +171,25 @@ def test_examples_protocol_mdns(dut):
|
||||
])
|
||||
print('Resolving {} using "dig" succeeded with:\n{}'.format(
|
||||
specific_host, dig_output))
|
||||
if not ip_address.encode('utf-8') in dig_output:
|
||||
if not ipv4.encode('utf-8') in dig_output:
|
||||
raise ValueError(
|
||||
'Test has failed: Incorrectly resolved DUT hostname using dig'
|
||||
"Output should've contained DUT's IP address:{}".format(
|
||||
ip_address))
|
||||
"Output should've contained DUT's IP address:{}".format(ipv4))
|
||||
# 6. check the DUT reverse lookup
|
||||
if dut.app.sdkconfig.get('MDNS_RESPOND_REVERSE_QUERIES') is True:
|
||||
for ip_address in ip_addresses:
|
||||
dig_output = subprocess.check_output([
|
||||
'dig', '+short', '-p', '5353', '@224.0.0.251', '-x',
|
||||
'{}'.format(ip_address)
|
||||
])
|
||||
print('Reverse lookup for {} using "dig" succeeded with:\n{}'.
|
||||
format(ip_address, dig_output))
|
||||
if specific_host not in dig_output.decode():
|
||||
raise ValueError(
|
||||
'Test has failed: Incorrectly resolved DUT IP address using dig'
|
||||
"Output should've contained DUT's name:{}".format(
|
||||
specific_host))
|
||||
|
||||
finally:
|
||||
mdns_server_events['stop'].set()
|
||||
mdns_responder.join()
|
||||
|
@ -6,6 +6,7 @@ CONFIG_MDNS_PREDEF_NETIF_STA=n
|
||||
CONFIG_MDNS_PREDEF_NETIF_AP=n
|
||||
CONFIG_MDNS_PREDEF_NETIF_ETH=n
|
||||
CONFIG_MDNS_ADD_CUSTOM_NETIF=y
|
||||
CONFIG_MDNS_RESPOND_REVERSE_QUERIES=y
|
||||
CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: "1.0.7"
|
||||
version: "1.0.8"
|
||||
description: mDNS
|
||||
dependencies:
|
||||
idf:
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
@ -42,7 +45,7 @@ mdns_server_t *_mdns_server = NULL;
|
||||
static mdns_host_item_t *_mdns_host_list = NULL;
|
||||
static mdns_host_item_t _mdns_self_host;
|
||||
|
||||
static const char *TAG = "MDNS";
|
||||
static const char *TAG = "mdns";
|
||||
|
||||
static volatile TaskHandle_t _mdns_service_task_handle = NULL;
|
||||
static SemaphoreHandle_t _mdns_service_semaphore = NULL;
|
||||
@ -434,8 +437,11 @@ static const uint8_t *_mdns_read_fqdn(const uint8_t *packet, const uint8_t *star
|
||||
if (name->parts == 1 && buf[0] != '_'
|
||||
&& (strcasecmp(buf, MDNS_DEFAULT_DOMAIN) != 0)
|
||||
&& (strcasecmp(buf, "arpa") != 0)
|
||||
#ifndef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
|
||||
&& (strcasecmp(buf, "ip6") != 0)
|
||||
&& (strcasecmp(buf, "in-addr") != 0)) {
|
||||
&& (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") || strstr(q->host, "ip6"))) {
|
||||
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 && strstr(name, "ip6") == 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,11 @@ 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") || strstr(answer->host->hostname, "ip6"))) {
|
||||
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 +1828,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 +1866,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 +3050,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 +3064,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 +3428,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 +3445,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 +3616,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 +3780,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;
|
||||
}
|
||||
@ -3847,6 +3967,13 @@ void _mdns_disable_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
|
||||
_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].state = PCB_OFF;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
|
||||
static inline char nibble_to_hex(int var)
|
||||
{
|
||||
return var > 9 ? var - 10 + 'a' : var + '0';
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Performs interface changes based on system events or custom commands
|
||||
*/
|
||||
@ -3873,6 +4000,48 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_LWIP_IPV6
|
||||
if (action & MDNS_EVENT_IP6_REVERSE_LOOKUP) {
|
||||
esp_ip6_addr_t addr6;
|
||||
if (!esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(mdns_if), &addr6) && !_ipv6_address_is_zero(addr6)) {
|
||||
uint8_t *paddr = (uint8_t *)&addr6.addr;
|
||||
const char sub[] = "ip6";
|
||||
const size_t query_name_size = 4 * sizeof(addr6.addr) /* (2 nibbles + 2 dots)/per byte of IP address */ + sizeof(sub);
|
||||
char *reverse_query_name = malloc(query_name_size);
|
||||
if (reverse_query_name) {
|
||||
char *ptr = &reverse_query_name[query_name_size]; // point to the end
|
||||
memcpy(ptr - sizeof(sub), sub, sizeof(sub)); // copy the IP sub-domain
|
||||
ptr -= sizeof(sub) + 1; // move before the sub-domain
|
||||
while (reverse_query_name < ptr) { // continue populating reverse query from the end
|
||||
*ptr-- = '.'; // nibble by nibble, until we reach the beginning
|
||||
*ptr-- = nibble_to_hex(((*paddr) >> 4) & 0x0F);
|
||||
*ptr-- = '.';
|
||||
*ptr-- = nibble_to_hex((*paddr) & 0x0F);
|
||||
paddr++;
|
||||
}
|
||||
ESP_LOGD(TAG, "Registered reverse query: %s.arpa", reverse_query_name);
|
||||
_mdns_delegate_hostname_add(reverse_query_name, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_LWIP_IPV6 */
|
||||
#endif /* CONFIG_MDNS_RESPOND_REVERSE_QUERIES */
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3893,6 +4062,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 +4140,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
|
||||
@ -4646,7 +4817,7 @@ static void _mdns_execute_action(mdns_action_t *action)
|
||||
_mdns_server->hostname = action->data.hostname_set.hostname;
|
||||
_mdns_self_host.hostname = action->data.hostname_set.hostname;
|
||||
_mdns_restart_all_pcbs();
|
||||
xTaskNotifyGive(action->data.hostname_set.calling_task);
|
||||
xSemaphoreGive(_mdns_server->action_sema);
|
||||
break;
|
||||
case ACTION_INSTANCE_SET:
|
||||
_mdns_send_bye_all_pcbs_no_instance(false);
|
||||
@ -4941,7 +5112,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();
|
||||
@ -5189,6 +5360,12 @@ esp_err_t mdns_init(void)
|
||||
goto free_lock;
|
||||
}
|
||||
|
||||
_mdns_server->action_sema = xSemaphoreCreateBinary();
|
||||
if (!_mdns_server->action_sema) {
|
||||
err = ESP_ERR_NO_MEM;
|
||||
goto free_queue;
|
||||
}
|
||||
|
||||
#if CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP
|
||||
if ((err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, mdns_preset_if_handle_system_event, NULL)) != ESP_OK) {
|
||||
goto free_event_handlers;
|
||||
@ -5244,6 +5421,8 @@ free_all_and_disable_pcbs:
|
||||
free_event_handlers:
|
||||
unregister_predefined_handlers();
|
||||
#endif
|
||||
vSemaphoreDelete(_mdns_server->action_sema);
|
||||
free_queue:
|
||||
vQueueDelete(_mdns_server->action_queue);
|
||||
free_lock:
|
||||
vSemaphoreDelete(_mdns_server->lock);
|
||||
@ -5293,6 +5472,7 @@ void mdns_free(void)
|
||||
}
|
||||
free(h);
|
||||
}
|
||||
vSemaphoreDelete(_mdns_server->action_sema);
|
||||
vSemaphoreDelete(_mdns_server->lock);
|
||||
free(_mdns_server);
|
||||
_mdns_server = NULL;
|
||||
@ -5319,13 +5499,12 @@ esp_err_t mdns_hostname_set(const char *hostname)
|
||||
}
|
||||
action->type = ACTION_HOSTNAME_SET;
|
||||
action->data.hostname_set.hostname = new_hostname;
|
||||
action->data.hostname_set.calling_task = xTaskGetCurrentTaskHandle();
|
||||
if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
|
||||
free(new_hostname);
|
||||
free(action);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
xTaskNotifyWait(0, 0x01, NULL, portMAX_DELAY);
|
||||
xSemaphoreTake(_mdns_server->action_sema, portMAX_DELAY);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@ -5398,7 +5577,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 +6246,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) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -27,7 +27,7 @@ extern mdns_server_t *_mdns_server;
|
||||
* MDNS Server Networking
|
||||
*
|
||||
*/
|
||||
static const char *TAG = "MDNS_Networking";
|
||||
static const char *TAG = "mdns_networking";
|
||||
|
||||
static struct udp_pcb *_pcb_main = NULL;
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
extern mdns_server_t *_mdns_server;
|
||||
|
||||
static const char *TAG = "MDNS_Networking";
|
||||
static const char *TAG = "mdns_networking";
|
||||
static bool s_run_sock_recv_task = false;
|
||||
static int create_socket(esp_netif_t *netif);
|
||||
static int join_mdns_multicast_group(int sock, esp_netif_t *netif, mdns_ip_protocol_t ip_protocol);
|
||||
|
@ -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
|
||||
@ -110,7 +89,11 @@
|
||||
#define MDNS_PACKET_QUEUE_LEN 16 // Maximum packets that can be queued for parsing
|
||||
#define MDNS_ACTION_QUEUE_LEN 16 // Maximum actions pending to the server
|
||||
#define MDNS_TXT_MAX_LEN 1024 // Maximum string length of text data in TXT record
|
||||
#if defined(CONFIG_LWIP_IPV6) && defined(CONFIG_MDNS_RESPOND_REVERSE_QUERIES)
|
||||
#define MDNS_NAME_MAX_LEN (64+4) // Need to account for IPv6 reverse queries (64 char address + ".ip6" )
|
||||
#else
|
||||
#define MDNS_NAME_MAX_LEN 64 // Maximum string length of hostname, instance, service and proto
|
||||
#endif
|
||||
#define MDNS_NAME_BUF_LEN (MDNS_NAME_MAX_LEN+1) // Maximum char buffer size to hold hostname, instance, service or proto
|
||||
#define MDNS_MAX_PACKET_SIZE 1460 // Maximum size of mDNS outgoing packet
|
||||
|
||||
@ -401,6 +384,7 @@ typedef struct mdns_server_s {
|
||||
mdns_srv_item_t *services;
|
||||
SemaphoreHandle_t lock;
|
||||
QueueHandle_t action_queue;
|
||||
SemaphoreHandle_t action_sema;
|
||||
mdns_tx_packet_t *tx_queue_head;
|
||||
mdns_search_once_t *search_once;
|
||||
esp_timer_handle_t timer_handle;
|
||||
@ -411,7 +395,6 @@ typedef struct {
|
||||
union {
|
||||
struct {
|
||||
char *hostname;
|
||||
TaskHandle_t calling_task;
|
||||
} hostname_set;
|
||||
char *instance;
|
||||
struct {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS esp_event_mock.c
|
||||
INCLUDE_DIRS include)
|
@ -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);
|
||||
|
@ -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;
|
@ -1,3 +0,0 @@
|
||||
idf_component_register(SRCS esp_event_mock.c
|
||||
INCLUDE_DIRS include
|
||||
REQUIRES esp_system_protocols_linux)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user