mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-25 08:17:28 +02:00
Compare commits
417 Commits
modem-v0.1
...
modem-v0.1
Author | SHA1 | Date | |
---|---|---|---|
5addf9e885 | |||
128c0a2d87 | |||
1f91d248f5 | |||
8fe2a3a661 | |||
417591b99f | |||
b6b20ad399 | |||
8863ed944d | |||
78001ec871 | |||
4a52cf23f5 | |||
00e7675913 | |||
87c269911d | |||
eb536a74a0 | |||
b720d02fca | |||
3e93ea9b76 | |||
2c764b1b7a | |||
f836ae7f65 | |||
e9a1c26e0c | |||
4b5f24f5b8 | |||
05675c7d63 | |||
30f37c0565 | |||
ddc58e8220 | |||
fa951bf320 | |||
605d1fab73 | |||
58bf2186f7 | |||
d39a4c744e | |||
ec491ec536 | |||
defdfd59b6 | |||
7f42c31252 | |||
94ae672041 | |||
e5a3a3df1d | |||
7710ea9a11 | |||
034c55e18a | |||
ec03fec3d3 | |||
5909e9e54c | |||
82e2a5dcc1 | |||
48e4d4035c | |||
ba3fa24a96 | |||
869f5b75af | |||
ac6dcb6830 | |||
085dbd8c4e | |||
f78e8cfce8 | |||
6cdf5ee074 | |||
fcb5515f1e | |||
9fdbe5f130 | |||
20e6e9e7fe | |||
3c5b13ea0d | |||
c588263b45 | |||
6258edf23b | |||
c8b0d5ea9d | |||
5252b1d801 | |||
4e11cc86fe | |||
7af91ec490 | |||
01256d3e34 | |||
5a2d4eab6d | |||
9de3f534e2 | |||
bcabc8ea16 | |||
dfb27b39cc | |||
076c095aec | |||
7dd0bc1fff | |||
7e82a7cef7 | |||
ae381b779f | |||
d0f4e68c7a | |||
941dc5c42f | |||
525c64915e | |||
f0839d909b | |||
69902ea8e1 | |||
d5001e894f | |||
145a8d2291 | |||
d0bbe880b6 | |||
4a9d55edf7 | |||
be2a924674 | |||
76fcd4128a | |||
fd8499c874 | |||
38b4fe2353 | |||
eb7eeb58d3 | |||
b26606252f | |||
5e087d82d6 | |||
91a3d95f96 | |||
6d6dd2b75e | |||
0611b0cb67 | |||
cf6ed1cc53 | |||
52306e914f | |||
d37ab6dd25 | |||
05dcd8f0ee | |||
5c55ea6e02 | |||
0c71c7bfe1 | |||
4c368c0090 | |||
c2abeff476 | |||
6a92d3253f | |||
af2275341e | |||
b0957e70fd | |||
47c7266103 | |||
40da0d29be | |||
5f6b6f9273 | |||
8a120829e2 | |||
46f28a8011 | |||
5a81eaea3f | |||
2ddaee2b6e | |||
73b1763029 | |||
27fc285000 | |||
93e6efedc7 | |||
bc4cda8ea7 | |||
8a8d58d4dc | |||
402baebfee | |||
9fa25ef3b6 | |||
121b525108 | |||
418fb60dd9 | |||
4049b3b5ed | |||
c8821199a2 | |||
1eb5df9780 | |||
b62b4b3e25 | |||
4d8aec1ad3 | |||
6d649102ab | |||
3ad559bf3f | |||
ab3fa69b8f | |||
c3a5826d60 | |||
cbcbe4ffd7 | |||
adc34309dc | |||
7a8329cb5c | |||
c30617d872 | |||
1e5eeb16ec | |||
22c7c0a195 | |||
eda5d72acf | |||
1623c0e729 | |||
b114ed69de | |||
49a00c2455 | |||
dec1d7dcf8 | |||
2ffd22382d | |||
487287157d | |||
1fe901f70f | |||
2cf9fd8891 | |||
89439e0a9b | |||
becd5d0266 | |||
0d7a30944e | |||
e2653e7fb0 | |||
df6a208f45 | |||
1fdffbbbab | |||
d5566971fb | |||
988d120902 | |||
fd47df3e30 | |||
2cb3a6e35e | |||
ccd48bc9a9 | |||
825652f3e1 | |||
fc53888115 | |||
7635c0479b | |||
d5fe42bffb | |||
e0bc60a586 | |||
9772e49b26 | |||
110f1f652d | |||
ed77d65a82 | |||
6021a88657 | |||
78f71ecdf6 | |||
07f57523c8 | |||
ad67a23097 | |||
078abd8161 | |||
f1f6c5de05 | |||
12fb6d8e15 | |||
2258d5bcef | |||
54f5c6f29c | |||
3319844745 | |||
ac70c9aba8 | |||
123ae8db62 | |||
e24cc7d1ae | |||
9d9aac1569 | |||
ef9a758662 | |||
2a23f355c7 | |||
07399011f7 | |||
2b7d43e1f8 | |||
7bf23a7fe9 | |||
881ca095f0 | |||
d9fa457b4f | |||
4eb3e89841 | |||
74aee42f80 | |||
b5e5a64e7f | |||
6713ffedcc | |||
817c4fd2e8 | |||
8f0dc6d578 | |||
7a3aa26df8 | |||
c7ff8ba0c9 | |||
80ce63d73e | |||
d20666f3a0 | |||
cd3cc0be3b | |||
1734e59057 | |||
662a4ce050 | |||
fb1de80fd7 | |||
1f35e9a728 | |||
573855031d | |||
d1c62628b8 | |||
48b819bbc1 | |||
dfcefc38fd | |||
879a6cdfa3 | |||
53e2aa3241 | |||
3cc64469c1 | |||
248b11bb0a | |||
d52dddfea5 | |||
67d310b988 | |||
901124b7ee | |||
f6ff165be9 | |||
d8d6b35553 | |||
f44c569422 | |||
7dfe14c83d | |||
286c646725 | |||
0582187b9a | |||
28d5b74a00 | |||
c70d527d80 | |||
7cdf96cffa | |||
407875d9c5 | |||
3e753f5e2d | |||
aaba3fc47a | |||
144d4ad1d4 | |||
e431b6b7fe | |||
ed71a239a8 | |||
680bad646f | |||
271665e0cb | |||
7fb6686716 | |||
4912bef740 | |||
181a22ec2b | |||
4172219225 | |||
9a0803ad7e | |||
05ddd5f0e4 | |||
7e3b35efd9 | |||
98d2c1a073 | |||
84cbb1f3cf | |||
4c6818ee97 | |||
c440114d14 | |||
c0f65a6997 | |||
778eaa6ced | |||
25f8656fbc | |||
6ea0ea93fa | |||
d9aec9fc71 | |||
985e69117e | |||
75deebbf03 | |||
fdd27dc9fa | |||
2ec3b558ea | |||
450cbf03cf | |||
34f6d8dd33 | |||
b6efc688b5 | |||
11e4aebefd | |||
90e4babc61 | |||
c546ab8dea | |||
6582b41cd1 | |||
358d26c8a1 | |||
8d08e5ed95 | |||
2ac83d0f27 | |||
98e3171db6 | |||
2f85c075be | |||
b30a7fec27 | |||
7a4fdad16d | |||
b4e57424f9 | |||
2763bcdb8d | |||
dce0b26ef8 | |||
ade4aeffa5 | |||
8cd0e8a501 | |||
9b3b41c3f1 | |||
ea2300753e | |||
81c219d4ee | |||
0c17121ad7 | |||
67173f6770 | |||
fed787f54f | |||
b4ab30b5de | |||
dd714947d6 | |||
39de491597 | |||
19acac76eb | |||
0191d6fcd7 | |||
b26c8665f8 | |||
98069f9ca2 | |||
ad29d34bb6 | |||
9f1be3668e | |||
450c9de67b | |||
c7701d41f8 | |||
b9726db48e | |||
f1ccc4052e | |||
84bd1d7e88 | |||
259d3fc609 | |||
6d99957f2d | |||
7784d002fc | |||
9ebd9852ca | |||
26f00e8ddb | |||
bce7d5231c | |||
ef924f1aa5 | |||
ad8c92db52 | |||
3aa605fe24 | |||
00a72b8920 | |||
907e7ee29e | |||
b367484361 | |||
4a8582f500 | |||
75de31cea3 | |||
4acf639afc | |||
1a1cf71a84 | |||
5924dafd94 | |||
91bb5095f5 | |||
caa4884b3a | |||
441a53c604 | |||
7fbf8e5247 | |||
7346ed9765 | |||
76298ff70e | |||
0875008a46 | |||
a661e51f7e | |||
3ced2d9709 | |||
ba88d7fdbc | |||
4f3c4299d6 | |||
2a4e684848 | |||
541b391c3e | |||
279a3cb6e0 | |||
60bb0eea99 | |||
52de8f1b2a | |||
503218b3ba | |||
38149c8d9b | |||
1984da150d | |||
9e37f537bd | |||
e55f54b69e | |||
6e4d8a19ed | |||
e98cf16859 | |||
be3d2ece55 | |||
15ed885035 | |||
b72a9ae710 | |||
0733ea8ff4 | |||
15cbc9bd50 | |||
bb7f198bea | |||
d3f7ea67fb | |||
6e5bede4c7 | |||
d191a720d1 | |||
cc0d52793b | |||
804a8d5df6 | |||
74040cfd1a | |||
16ea3f7eb8 | |||
1f5eb396c5 | |||
827bebd2bc | |||
745201b188 | |||
ca266cabd0 | |||
e54b240870 | |||
4ccc32ffb5 | |||
91a177edd4 | |||
f303cdc70b | |||
ab8c2da395 | |||
80c3cf0f02 | |||
b3c777ad43 | |||
8ce791e969 | |||
fc7ed90d74 | |||
59e82695e7 | |||
755f16222f | |||
bece6e7045 | |||
46bd32d952 | |||
525c70c0b2 | |||
c4c323666e | |||
19c0455b4d | |||
665c520faf | |||
9118e0f044 | |||
fbdbd550c0 | |||
028be5a8d7 | |||
de7cd72f70 | |||
4a608ec1cd | |||
f0351ff378 | |||
9219ff710a | |||
86aa0b8d39 | |||
1933367f63 | |||
cf697a1a1b | |||
95cf983502 | |||
8a6c320a29 | |||
d1dd6ece38 | |||
36167db336 | |||
d376480766 | |||
e90272c812 | |||
fda070ba39 | |||
6d12d06605 | |||
1455bc0305 | |||
01b4f640d9 | |||
6ab0aea841 | |||
5f2a50f09f | |||
b71c49c277 | |||
f8e3ba7813 | |||
7a5b2d5a7d | |||
42920d7fb5 | |||
2b044f2434 | |||
2b6022c85d | |||
fae2343b19 | |||
17281a515e | |||
aec6a75d40 | |||
a6be8e2e3d | |||
09453e4694 | |||
3b0488cfdc | |||
f21a2f32e0 | |||
a48b0fafe8 | |||
1fcc001ae8 | |||
a41e3383b3 | |||
d0121b964d | |||
f55d8391c9 | |||
fe26b734b5 | |||
f5a26c4d32 | |||
23f6a1d46e | |||
2553d65e64 | |||
67949f94f4 | |||
bfc88ab76c | |||
343fbfdcc9 | |||
4d644954fe | |||
da74a4a489 | |||
f718676083 | |||
13a40d2344 | |||
35d6f9a2c6 | |||
f3a0586663 | |||
04a7643d67 | |||
8b3d420055 | |||
50b083a58c | |||
325a1933c4 | |||
face03e4e5 | |||
5d27b2681a | |||
2cb74cf8d0 | |||
d879e82a42 | |||
4f1d31f9b7 | |||
bece4efa09 | |||
8417e232aa | |||
5f0832a0ad | |||
464baeeb83 | |||
5d9ad9cffd | |||
9fbd6e658a | |||
8465b14653 | |||
e7ae0301ae |
76
.github/workflows/build_and_run_example_test.yml
vendored
Normal file
76
.github/workflows/build_and_run_example_test.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
name: Build Websockets
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_target: ["esp32"]
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@v1
|
||||
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
|
||||
env:
|
||||
IDF_TARGET: ${{ matrix.idf_target }}
|
||||
shell: bash
|
||||
working-directory: components/esp_websocket_client/examples/
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
cat sdkconfig.ci >> sdkconfig.defaults
|
||||
idf.py build
|
||||
- name: Merge binaries
|
||||
working-directory: components/esp_websocket_client/examples/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
|
||||
with:
|
||||
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}
|
||||
path: components/esp_websocket_client/examples/build/
|
||||
if-no-files-found: error
|
||||
|
||||
run-target:
|
||||
name: Run Example Test on target
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_target: ["esp32"]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- ESP32-ETHERNET-KIT
|
||||
container:
|
||||
image: python:3.7-buster
|
||||
options: --privileged # Privileged mode has access to serial ports
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}
|
||||
path: components/esp_websocket_client/examples/build/
|
||||
- name: Install Python packages
|
||||
env:
|
||||
PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple"
|
||||
run: |
|
||||
pip install -r $GITHUB_WORKSPACE/components/esp_websocket_client/examples/requirements.txt
|
||||
- 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
|
||||
run: |
|
||||
cp sdkconfig.ci sdkconfig.defaults
|
||||
pytest --log-cli-level DEBUG --junit-xml=./test_app_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}.xml --target=${{ matrix.idf_target }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}
|
||||
path: examples/*.xml
|
35
.github/workflows/build_and_run_example_test_mdns.yml
vendored
Normal file
35
.github/workflows/build_and_run_example_test_mdns.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Build mDNS
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_target: ["esp32", "esp32s2", "esp32c3"]
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
path: esp-protocols
|
||||
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
|
||||
env:
|
||||
IDF_TARGET: ${{ matrix.idf_target }}
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/examples/
|
||||
cat sdkconfig.ci.eth_def >> sdkconfig.defaults
|
||||
idf.py build
|
||||
rm sdkconfig.defaults
|
||||
cat sdkconfig.ci.eth_custom_netif >> sdkconfig.defaults
|
||||
idf.py build
|
||||
rm sdkconfig.defaults
|
||||
cat sdkconfig.ci.eth_socket >> sdkconfig.defaults
|
||||
idf.py build
|
||||
cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/tests/test_apps/
|
||||
idf.py build
|
44
.github/workflows/host-test.yml
vendored
Normal file
44
.github/workflows/host-test.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: Host test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
host_test:
|
||||
name: Build and test
|
||||
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
|
||||
|
||||
|
||||
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 gcc-8 g++-8
|
||||
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
|
||||
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
|
||||
idf.py build
|
||||
./build/host_modem_test.elf -r junit -o junit.xml
|
||||
|
||||
- name: Publish Results
|
||||
uses: EnricoMi/publish-unit-test-result-action@v1
|
||||
if: always()
|
||||
with:
|
||||
files: esp-protocols/components/esp_modem/test/host_test/junit.xml
|
24
.github/workflows/publish-docs-component.yml
vendored
24
.github/workflows/publish-docs-component.yml
vendored
@ -21,20 +21,36 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install doxygen clang python3-pip
|
||||
python -m pip install breathe recommonmark
|
||||
python -m pip install breathe recommonmark esp-docs
|
||||
cd $GITHUB_WORKSPACE/components/esp_modem/docs
|
||||
./generate_docs
|
||||
mkdir -p $GITHUB_WORKSPACE/docs/esp_modem
|
||||
cp -r html/. $GITHUB_WORKSPACE/docs/esp_modem
|
||||
|
||||
cd $GITHUB_WORKSPACE/components/esp_websocket_client/docs
|
||||
./generate_docs
|
||||
mkdir -p $GITHUB_WORKSPACE/docs/esp_websocket_client
|
||||
cp -r html/. $GITHUB_WORKSPACE/docs/esp_websocket_client
|
||||
|
||||
cd $GITHUB_WORKSPACE/components/mdns/docs
|
||||
./generate_docs
|
||||
mkdir -p $GITHUB_WORKSPACE/docs/mdns/en
|
||||
mkdir -p $GITHUB_WORKSPACE/docs/mdns/zh_CN
|
||||
cp -r html_en/. $GITHUB_WORKSPACE/docs/mdns/en
|
||||
cp -r html_zh_CN/. $GITHUB_WORKSPACE/docs/mdns/zh_CN
|
||||
|
||||
cd $GITHUB_WORKSPACE/docs
|
||||
touch .nojekyll
|
||||
echo '<a href="esp_modem/index.html">esp-modem</a>' > index.html
|
||||
echo '<a href="esp_modem/index.html">esp-modem</a><br>' > index.html
|
||||
echo '<a href="esp_websocket_client/index.html">esp-websocket-client</a><br>' >> index.html
|
||||
echo '<a href="mdns/en/index.html">mDNS_en</a><br>' >> index.html
|
||||
echo '<a href="mdns/zh_CN/index.html">mDNS_zh_CN</a><br>' >> index.html
|
||||
|
||||
|
||||
- name: Upload components to component service
|
||||
uses: espressif/github-actions/upload_components@master
|
||||
with:
|
||||
directories: "components/esp_modem"
|
||||
name: "esp_modem"
|
||||
directories: "components/esp_modem;components/esp_websocket_client;components/mdns"
|
||||
namespace: "espressif"
|
||||
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}
|
||||
|
||||
|
30
.github/workflows/test_afl_fuzzer.yml
vendored
Normal file
30
.github/workflows/test_afl_fuzzer.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: AFL fuzzer compilation test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_target: ["esp32"]
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
path: esp-protocols
|
||||
- name: Install Necessary Libs
|
||||
run: |
|
||||
apt-get update -y
|
||||
apt-get install -y libbsd-dev
|
||||
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
|
||||
env:
|
||||
IDF_TARGET: ${{ matrix.idf_target }}
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/tests/test_afl_fuzz_host/
|
||||
make INSTR=off
|
11
README.md
11
README.md
@ -8,3 +8,14 @@
|
||||
|
||||
* Brief introduction [README](components/esp_modem/README.md)
|
||||
* Full html [documentation](https://espressif.github.io/esp-protocols/esp_modem/index.html)
|
||||
|
||||
### mDNS
|
||||
|
||||
* Brief introduction [README](components/mdns/README.md)
|
||||
* Full html [documentation(English)](https://espressif.github.io/esp-protocols/mdns/en/index.html)
|
||||
* Full html [documentation(Chinese)](https://espressif.github.io/esp-protocols/mdns/zh_CN/index.html)
|
||||
|
||||
### esp_websocket_client
|
||||
|
||||
* Brief introduction [README](components/esp_websocket_client/README.md)
|
||||
* Full html [documentation](https://espressif.github.io/esp-protocols/esp_websocket_client/index.html)
|
||||
|
@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "connect.c" "stdin_out.c" "addr_from_stdin.c"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES esp_netif driver
|
||||
)
|
322
common_components/protocol_examples_common/Kconfig.projbuild
Normal file
322
common_components/protocol_examples_common/Kconfig.projbuild
Normal file
@ -0,0 +1,322 @@
|
||||
menu "Example Connection Configuration"
|
||||
|
||||
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
|
||||
|
||||
config EXAMPLE_CONNECT_WIFI
|
||||
bool "connect using WiFi interface"
|
||||
default y
|
||||
help
|
||||
Protocol examples can use Wi-Fi and/or Ethernet to connect to the network.
|
||||
Choose this option to connect with WiFi
|
||||
|
||||
if EXAMPLE_CONNECT_WIFI
|
||||
config EXAMPLE_WIFI_SSID
|
||||
string "WiFi SSID"
|
||||
default "myssid"
|
||||
help
|
||||
SSID (network name) for the example to connect to.
|
||||
|
||||
config EXAMPLE_WIFI_PASSWORD
|
||||
string "WiFi Password"
|
||||
default "mypassword"
|
||||
help
|
||||
WiFi password (WPA or WPA2) for the example to use.
|
||||
Can be left blank if the network has no security set.
|
||||
|
||||
choice EXAMPLE_WIFI_SCAN_METHOD
|
||||
prompt "WiFi Scan Method"
|
||||
default EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL
|
||||
help
|
||||
WiFi scan method:
|
||||
|
||||
If "Fast" is selected, scan will end after find SSID match AP.
|
||||
|
||||
If "All Channel" is selected, scan will end after scan all the channel.
|
||||
|
||||
config EXAMPLE_WIFI_SCAN_METHOD_FAST
|
||||
bool "Fast"
|
||||
config EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL
|
||||
bool "All Channel"
|
||||
endchoice
|
||||
|
||||
menu "WiFi Scan threshold"
|
||||
config EXAMPLE_WIFI_SCAN_RSSI_THRESHOLD
|
||||
int "WiFi minimum rssi"
|
||||
range -127 0
|
||||
|
||||
default -127
|
||||
help
|
||||
The minimum rssi to accept in the scan mode.
|
||||
|
||||
choice EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD
|
||||
prompt "WiFi Scan auth mode threshold"
|
||||
default EXAMPLE_WIFI_AUTH_OPEN
|
||||
help
|
||||
The weakest authmode to accept in the scan mode.
|
||||
|
||||
config EXAMPLE_WIFI_AUTH_OPEN
|
||||
bool "OPEN"
|
||||
config EXAMPLE_WIFI_AUTH_WEP
|
||||
bool "WEP"
|
||||
config EXAMPLE_WIFI_AUTH_WPA_PSK
|
||||
bool "WPA PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WPA2_PSK
|
||||
bool "WPA2 PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WPA_WPA2_PSK
|
||||
bool "WPA WPA2 PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WPA2_ENTERPRISE
|
||||
bool "WPA2 ENTERPRISE"
|
||||
config EXAMPLE_WIFI_AUTH_WPA3_PSK
|
||||
bool "WPA3 PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK
|
||||
bool "WPA2 WPA3 PSK"
|
||||
config EXAMPLE_WIFI_AUTH_WAPI_PSK
|
||||
bool "WAPI PSK"
|
||||
endchoice
|
||||
endmenu
|
||||
|
||||
choice EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD
|
||||
prompt "WiFi Connect AP Sort Method"
|
||||
default EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL
|
||||
help
|
||||
WiFi connect AP sort method:
|
||||
|
||||
If "Signal" is selected, Sort matched APs in scan list by RSSI.
|
||||
|
||||
If "Security" is selected, Sort matched APs in scan list by security mode.
|
||||
|
||||
config EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL
|
||||
bool "Signal"
|
||||
config EXAMPLE_WIFI_CONNECT_AP_BY_SECURITY
|
||||
bool "Security"
|
||||
endchoice
|
||||
endif
|
||||
|
||||
config EXAMPLE_CONNECT_ETHERNET
|
||||
bool "connect using Ethernet interface"
|
||||
default n
|
||||
help
|
||||
Protocol examples can use Wi-Fi and/or Ethernet to connect to the network.
|
||||
Choose this option to connect with Ethernet
|
||||
|
||||
if EXAMPLE_CONNECT_ETHERNET
|
||||
config EXAMPLE_USE_SPI_ETHERNET
|
||||
bool
|
||||
|
||||
choice EXAMPLE_ETHERNET_TYPE
|
||||
prompt "Ethernet Type"
|
||||
default EXAMPLE_USE_INTERNAL_ETHERNET if IDF_TARGET_ESP32
|
||||
default EXAMPLE_USE_W5500
|
||||
help
|
||||
Select which kind of Ethernet will be used in the example.
|
||||
|
||||
config EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
depends on IDF_TARGET_ESP32
|
||||
select ETH_USE_ESP32_EMAC
|
||||
bool "Internal EMAC"
|
||||
help
|
||||
Select internal Ethernet MAC controller.
|
||||
|
||||
config EXAMPLE_USE_DM9051
|
||||
bool "DM9051 Module"
|
||||
select EXAMPLE_USE_SPI_ETHERNET
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_DM9051
|
||||
help
|
||||
Select external SPI-Ethernet module.
|
||||
|
||||
config EXAMPLE_USE_W5500
|
||||
bool "W5500 Module"
|
||||
select EXAMPLE_USE_SPI_ETHERNET
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_W5500
|
||||
help
|
||||
Select external SPI-Ethernet module (W5500).
|
||||
|
||||
config EXAMPLE_USE_OPENETH
|
||||
bool "OpenCores Ethernet MAC (EXPERIMENTAL)"
|
||||
select ETH_USE_OPENETH
|
||||
help
|
||||
When this option is enabled, the example is built with support for
|
||||
OpenCores Ethernet MAC, which allows testing the example in QEMU.
|
||||
Note that this option is used for internal testing purposes, and
|
||||
not officially supported. Examples built with this option enabled
|
||||
will not run on a real ESP32 chip.
|
||||
|
||||
endchoice # EXAMPLE_ETHERNET_TYPE
|
||||
|
||||
if EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
choice EXAMPLE_ETH_PHY_MODEL
|
||||
prompt "Ethernet PHY Device"
|
||||
default EXAMPLE_ETH_PHY_IP101
|
||||
help
|
||||
Select the Ethernet PHY device to use in the example.
|
||||
|
||||
config EXAMPLE_ETH_PHY_IP101
|
||||
bool "IP101"
|
||||
help
|
||||
IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
|
||||
Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_RTL8201
|
||||
bool "RTL8201/SR8201"
|
||||
help
|
||||
RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
|
||||
Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_LAN87XX
|
||||
bool "LAN87xx"
|
||||
help
|
||||
Below chips are supported:
|
||||
LAN8710A is a small footprint MII/RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
|
||||
LAN8740A/LAN8741A is a small footprint MII/RMII 10/100 Energy Efficient Ethernet Transceiver
|
||||
with HP Auto-MDIX and flexPWR® Technology.
|
||||
LAN8742A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
Goto https://www.microchip.com for more information about them.
|
||||
|
||||
config EXAMPLE_ETH_PHY_DP83848
|
||||
bool "DP83848"
|
||||
help
|
||||
DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
|
||||
Goto http://www.ti.com/product/DP83848J for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_KSZ80XX
|
||||
bool "KSZ80xx"
|
||||
help
|
||||
With the KSZ80xx series, Microchip offers single-chip 10BASE-T/100BASE-TX
|
||||
Ethernet Physical Layer Tranceivers (PHY).
|
||||
The following chips are supported: KSZ8001, KSZ8021, KSZ8031, KSZ8041,
|
||||
KSZ8051, KSZ8061, KSZ8081, KSZ8091
|
||||
Goto https://www.microchip.com for more information about them.
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_ETH_MDC_GPIO
|
||||
int "SMI MDC GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 23
|
||||
help
|
||||
Set the GPIO number used by SMI MDC.
|
||||
|
||||
config EXAMPLE_ETH_MDIO_GPIO
|
||||
int "SMI MDIO GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 18
|
||||
help
|
||||
Set the GPIO number used by SMI MDIO.
|
||||
endif
|
||||
|
||||
if EXAMPLE_USE_SPI_ETHERNET
|
||||
config EXAMPLE_ETH_SPI_HOST
|
||||
int "SPI Host Number"
|
||||
range 0 2
|
||||
default 1
|
||||
help
|
||||
Set the SPI host used to communicate with the SPI Ethernet Controller.
|
||||
|
||||
config EXAMPLE_ETH_SPI_SCLK_GPIO
|
||||
int "SPI SCLK GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 14
|
||||
help
|
||||
Set the GPIO number used by SPI SCLK.
|
||||
|
||||
config EXAMPLE_ETH_SPI_MOSI_GPIO
|
||||
int "SPI MOSI GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 13
|
||||
help
|
||||
Set the GPIO number used by SPI MOSI.
|
||||
|
||||
config EXAMPLE_ETH_SPI_MISO_GPIO
|
||||
int "SPI MISO GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
|
||||
default 12
|
||||
help
|
||||
Set the GPIO number used by SPI MISO.
|
||||
|
||||
config EXAMPLE_ETH_SPI_CS_GPIO
|
||||
int "SPI CS GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||
default 15
|
||||
help
|
||||
Set the GPIO number used by SPI CS.
|
||||
|
||||
config EXAMPLE_ETH_SPI_CLOCK_MHZ
|
||||
int "SPI clock speed (MHz)"
|
||||
range 5 80
|
||||
default 36
|
||||
help
|
||||
Set the clock speed (MHz) of SPI interface.
|
||||
|
||||
config EXAMPLE_ETH_SPI_INT_GPIO
|
||||
int "Interrupt GPIO number"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
|
||||
default 4
|
||||
help
|
||||
Set the GPIO number used by the SPI Ethernet module interrupt line.
|
||||
endif # EXAMPLE_USE_SPI_ETHERNET
|
||||
|
||||
config EXAMPLE_ETH_PHY_RST_GPIO
|
||||
int "PHY Reset GPIO number"
|
||||
range -1 ENV_GPIO_OUT_RANGE_MAX
|
||||
default 5
|
||||
help
|
||||
Set the GPIO number used to reset PHY chip.
|
||||
Set to -1 to disable PHY chip hardware reset.
|
||||
|
||||
config EXAMPLE_ETH_PHY_ADDR
|
||||
int "PHY Address"
|
||||
range 0 31 if EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
default 1
|
||||
help
|
||||
Set PHY address according your board schematic.
|
||||
endif # EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6
|
||||
bool "Obtain IPv6 address"
|
||||
default y
|
||||
depends on EXAMPLE_CONNECT_WIFI || EXAMPLE_CONNECT_ETHERNET
|
||||
select LWIP_IPV6
|
||||
help
|
||||
By default, examples will wait until IPv4 and IPv6 local link addresses are obtained.
|
||||
Disable this option if the network does not support IPv6.
|
||||
Choose the preferred IPv6 address type if the connection code should wait until other than
|
||||
the local link address gets assigned.
|
||||
Consider enabling IPv6 stateless address autoconfiguration (SLAAC) in the LWIP component.
|
||||
|
||||
if EXAMPLE_CONNECT_IPV6
|
||||
choice EXAMPLE_CONNECT_PREFERRED_IPV6
|
||||
prompt "Preferred IPv6 Type"
|
||||
default EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK
|
||||
help
|
||||
Select which kind of IPv6 address the connect logic waits for.
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK
|
||||
bool "Local Link Address"
|
||||
help
|
||||
Blocks until Local link address assigned.
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6_PREF_GLOBAL
|
||||
bool "Global Address"
|
||||
help
|
||||
Blocks until Global address assigned.
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL
|
||||
bool "Site Local Address"
|
||||
help
|
||||
Blocks until Site link address assigned.
|
||||
|
||||
config EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL
|
||||
bool "Unique Local Link Address"
|
||||
help
|
||||
Blocks until Unique local address assigned.
|
||||
|
||||
endchoice
|
||||
|
||||
endif
|
||||
|
||||
|
||||
endmenu
|
68
common_components/protocol_examples_common/addr_from_stdin.c
Normal file
68
common_components/protocol_examples_common/addr_from_stdin.c
Normal file
@ -0,0 +1,68 @@
|
||||
#include <string.h>
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "lwip/sockets.h"
|
||||
#include <lwip/netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#define HOST_IP_SIZE 128
|
||||
|
||||
esp_err_t get_addr_from_stdin(int port, int sock_type, int *ip_protocol, int *addr_family, struct sockaddr_storage *dest_addr)
|
||||
{
|
||||
char host_ip[HOST_IP_SIZE];
|
||||
int len;
|
||||
static bool already_init = false;
|
||||
|
||||
// this function could be called multiple times -> make sure UART init runs only once
|
||||
if (!already_init) {
|
||||
example_configure_stdin_stdout();
|
||||
already_init = true;
|
||||
}
|
||||
|
||||
// ignore empty or LF only string (could receive from DUT class)
|
||||
do {
|
||||
fgets(host_ip, HOST_IP_SIZE, stdin);
|
||||
len = strlen(host_ip);
|
||||
} while (len<=1 && host_ip[0] == '\n');
|
||||
host_ip[len - 1] = '\0';
|
||||
|
||||
struct addrinfo hints, *addr_list, *cur;
|
||||
memset( &hints, 0, sizeof( hints ) );
|
||||
|
||||
// run getaddrinfo() to decide on the IP protocol
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = sock_type;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
if( getaddrinfo( host_ip, NULL, &hints, &addr_list ) != 0 ) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
for( cur = addr_list; cur != NULL; cur = cur->ai_next ) {
|
||||
memcpy(dest_addr, cur->ai_addr, sizeof(*dest_addr));
|
||||
if (cur->ai_family == AF_INET) {
|
||||
*ip_protocol = IPPROTO_IP;
|
||||
*addr_family = AF_INET;
|
||||
// add port number and return on first IPv4 match
|
||||
((struct sockaddr_in*)dest_addr)->sin_port = htons(port);
|
||||
freeaddrinfo( addr_list );
|
||||
return ESP_OK;
|
||||
|
||||
}
|
||||
#if CONFIG_LWIP_IPV6
|
||||
else if (cur->ai_family == AF_INET6) {
|
||||
*ip_protocol = IPPROTO_IPV6;
|
||||
*addr_family = AF_INET6;
|
||||
// add port and interface number and return on first IPv6 match
|
||||
((struct sockaddr_in6*)dest_addr)->sin6_port = htons(port);
|
||||
((struct sockaddr_in6*)dest_addr)->sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
|
||||
freeaddrinfo( addr_list );
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// no match found
|
||||
freeaddrinfo( addr_list );
|
||||
return ESP_FAIL;
|
||||
}
|
515
common_components/protocol_examples_common/connect.c
Normal file
515
common_components/protocol_examples_common/connect.c
Normal file
@ -0,0 +1,515 @@
|
||||
/* Common functions for protocol examples, to establish Wi-Fi or Ethernet connection.
|
||||
|
||||
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 <string.h>
|
||||
#include "protocol_examples_common.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_wifi_default.h"
|
||||
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#include "esp_eth.h"
|
||||
#if CONFIG_ETH_USE_SPI_ETHERNET
|
||||
#include "driver/spi_master.h"
|
||||
#endif // CONFIG_ETH_USE_SPI_ETHERNET
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sys.h"
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
#define MAX_IP6_ADDRS_PER_NETIF (5)
|
||||
#define NR_OF_IP_ADDRESSES_TO_WAIT_FOR (s_active_interfaces*2)
|
||||
|
||||
#if defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK)
|
||||
#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_GLOBAL)
|
||||
#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_GLOBAL
|
||||
#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL)
|
||||
#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_SITE_LOCAL
|
||||
#elif defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL)
|
||||
#define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_UNIQUE_LOCAL
|
||||
#endif // if-elif CONFIG_EXAMPLE_CONNECT_IPV6_PREF_...
|
||||
|
||||
#else
|
||||
#define NR_OF_IP_ADDRESSES_TO_WAIT_FOR (s_active_interfaces)
|
||||
#endif
|
||||
|
||||
#define EXAMPLE_DO_CONNECT CONFIG_EXAMPLE_CONNECT_WIFI || CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
#if CONFIG_EXAMPLE_WIFI_SCAN_METHOD_FAST
|
||||
#define EXAMPLE_WIFI_SCAN_METHOD WIFI_FAST_SCAN
|
||||
#elif CONFIG_EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL
|
||||
#define EXAMPLE_WIFI_SCAN_METHOD WIFI_ALL_CHANNEL_SCAN
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL
|
||||
#define EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
|
||||
#elif CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SECURITY
|
||||
#define EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD WIFI_CONNECT_AP_BY_SECURITY
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_WIFI_AUTH_OPEN
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WEP
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA2_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA_WPA2_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA2_ENTERPRISE
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_ENTERPRISE
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA3_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
|
||||
#elif CONFIG_EXAMPLE_WIFI_AUTH_WAPI_PSK
|
||||
#define EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
|
||||
#endif
|
||||
|
||||
static int s_active_interfaces = 0;
|
||||
static SemaphoreHandle_t s_semph_get_ip_addrs;
|
||||
static esp_netif_t *s_example_esp_netif = NULL;
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
static esp_ip6_addr_t s_ipv6_addr;
|
||||
|
||||
/* types of ipv6 addresses to be displayed on ipv6 events */
|
||||
static const char *s_ipv6_addr_types[] = {
|
||||
"ESP_IP6_ADDR_IS_UNKNOWN",
|
||||
"ESP_IP6_ADDR_IS_GLOBAL",
|
||||
"ESP_IP6_ADDR_IS_LINK_LOCAL",
|
||||
"ESP_IP6_ADDR_IS_SITE_LOCAL",
|
||||
"ESP_IP6_ADDR_IS_UNIQUE_LOCAL",
|
||||
"ESP_IP6_ADDR_IS_IPV4_MAPPED_IPV6"
|
||||
};
|
||||
#endif
|
||||
|
||||
static const char *TAG = "example_connect";
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
static esp_netif_t *wifi_start(void);
|
||||
static void wifi_stop(void);
|
||||
#endif
|
||||
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
static esp_netif_t *eth_start(void);
|
||||
static void eth_stop(void);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Checks the netif description if it contains specified prefix.
|
||||
* All netifs created withing common connect component are prefixed with the module TAG,
|
||||
* so it returns true if the specified netif is owned by this module
|
||||
*/
|
||||
static bool is_our_netif(const char *prefix, esp_netif_t *netif)
|
||||
{
|
||||
return strncmp(prefix, esp_netif_get_desc(netif), strlen(prefix) - 1) == 0;
|
||||
}
|
||||
|
||||
/* set up connection, Wi-Fi and/or Ethernet */
|
||||
static void start(void)
|
||||
{
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
s_example_esp_netif = wifi_start();
|
||||
s_active_interfaces++;
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
s_example_esp_netif = eth_start();
|
||||
s_active_interfaces++;
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI && CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
/* if both intefaces at once, clear out to indicate that multiple netifs are active */
|
||||
s_example_esp_netif = NULL;
|
||||
#endif
|
||||
|
||||
#if EXAMPLE_DO_CONNECT
|
||||
/* create semaphore if at least one interface is active */
|
||||
s_semph_get_ip_addrs = xSemaphoreCreateCounting(NR_OF_IP_ADDRESSES_TO_WAIT_FOR, 0);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/* tear down connection, release resources */
|
||||
static void stop(void)
|
||||
{
|
||||
#if CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
wifi_stop();
|
||||
s_active_interfaces--;
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
eth_stop();
|
||||
s_active_interfaces--;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if EXAMPLE_DO_CONNECT
|
||||
static esp_ip4_addr_t s_ip_addr;
|
||||
|
||||
static void on_got_ip(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
if (!is_our_netif(TAG, event->esp_netif)) {
|
||||
ESP_LOGW(TAG, "Got IPv4 from another interface \"%s\": ignored", esp_netif_get_desc(event->esp_netif));
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip));
|
||||
memcpy(&s_ip_addr, &event->ip_info.ip, sizeof(s_ip_addr));
|
||||
xSemaphoreGive(s_semph_get_ip_addrs);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
|
||||
static void on_got_ipv6(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
|
||||
if (!is_our_netif(TAG, event->esp_netif)) {
|
||||
ESP_LOGW(TAG, "Got IPv6 from another netif: ignored");
|
||||
return;
|
||||
}
|
||||
esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip);
|
||||
ESP_LOGI(TAG, "Got IPv6 event: Interface \"%s\" address: " IPV6STR ", type: %s", esp_netif_get_desc(event->esp_netif),
|
||||
IPV62STR(event->ip6_info.ip), s_ipv6_addr_types[ipv6_type]);
|
||||
if (ipv6_type == EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE) {
|
||||
memcpy(&s_ipv6_addr, &event->ip6_info.ip, sizeof(s_ipv6_addr));
|
||||
xSemaphoreGive(s_semph_get_ip_addrs);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
|
||||
esp_err_t example_connect(void)
|
||||
{
|
||||
#if EXAMPLE_DO_CONNECT
|
||||
if (s_semph_get_ip_addrs != NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
#endif
|
||||
start();
|
||||
ESP_ERROR_CHECK(esp_register_shutdown_handler(&stop));
|
||||
ESP_LOGI(TAG, "Waiting for IP(s)");
|
||||
for (int i = 0; i < NR_OF_IP_ADDRESSES_TO_WAIT_FOR; ++i) {
|
||||
xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY);
|
||||
}
|
||||
// iterate over active interfaces, and print out IPs of "our" netifs
|
||||
esp_netif_t *netif = NULL;
|
||||
esp_netif_ip_info_t ip;
|
||||
for (int i = 0; i < esp_netif_get_nr_of_ifs(); ++i) {
|
||||
netif = esp_netif_next(netif);
|
||||
if (is_our_netif(TAG, netif)) {
|
||||
ESP_LOGI(TAG, "Connected to %s", esp_netif_get_desc(netif));
|
||||
ESP_ERROR_CHECK(esp_netif_get_ip_info(netif, &ip));
|
||||
|
||||
ESP_LOGI(TAG, "- IPv4 address: " IPSTR, IP2STR(&ip.ip));
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
esp_ip6_addr_t ip6[MAX_IP6_ADDRS_PER_NETIF];
|
||||
int ip6_addrs = esp_netif_get_all_ip6(netif, ip6);
|
||||
for (int j = 0; j < ip6_addrs; ++j) {
|
||||
esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&(ip6[j]));
|
||||
ESP_LOGI(TAG, "- IPv6 address: " IPV6STR ", type: %s", IPV62STR(ip6[j]), s_ipv6_addr_types[ipv6_type]);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t example_disconnect(void)
|
||||
{
|
||||
if (s_semph_get_ip_addrs == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
vSemaphoreDelete(s_semph_get_ip_addrs);
|
||||
s_semph_get_ip_addrs = NULL;
|
||||
stop();
|
||||
ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&stop));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
|
||||
static void on_wifi_disconnect(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
ESP_LOGI(TAG, "Wi-Fi disconnected, trying to reconnect...");
|
||||
esp_err_t err = esp_wifi_connect();
|
||||
if (err == ESP_ERR_WIFI_NOT_STARTED) {
|
||||
return;
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
|
||||
static void on_wifi_connect(void *esp_netif, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_netif_create_ip6_linklocal(esp_netif);
|
||||
}
|
||||
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
|
||||
static esp_netif_t *wifi_start(void)
|
||||
{
|
||||
char *desc;
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA();
|
||||
// Prefix the interface description with the module TAG
|
||||
// Warning: the interface desc is used in tests to capture actual connection details (IP, gw, mask)
|
||||
asprintf(&desc, "%s: %s", TAG, esp_netif_config.if_desc);
|
||||
esp_netif_config.if_desc = desc;
|
||||
esp_netif_config.route_prio = 128;
|
||||
esp_netif_t *netif = esp_netif_create_wifi(WIFI_IF_STA, &esp_netif_config);
|
||||
free(desc);
|
||||
esp_wifi_set_default_wifi_sta_handlers();
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &on_wifi_disconnect, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip, NULL));
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &on_wifi_connect, netif));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &on_got_ipv6, NULL));
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
||||
wifi_config_t wifi_config = {
|
||||
.sta = {
|
||||
.ssid = CONFIG_EXAMPLE_WIFI_SSID,
|
||||
.password = CONFIG_EXAMPLE_WIFI_PASSWORD,
|
||||
.scan_method = EXAMPLE_WIFI_SCAN_METHOD,
|
||||
.sort_method = EXAMPLE_WIFI_CONNECT_AP_SORT_METHOD,
|
||||
.threshold.rssi = CONFIG_EXAMPLE_WIFI_SCAN_RSSI_THRESHOLD,
|
||||
.threshold.authmode = EXAMPLE_WIFI_SCAN_AUTH_MODE_THRESHOLD,
|
||||
},
|
||||
};
|
||||
ESP_LOGI(TAG, "Connecting to %s...", wifi_config.sta.ssid);
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
esp_wifi_connect();
|
||||
return netif;
|
||||
}
|
||||
|
||||
static void wifi_stop(void)
|
||||
{
|
||||
esp_netif_t *wifi_netif = get_example_netif_from_desc("sta");
|
||||
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &on_wifi_disconnect));
|
||||
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip));
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, &on_got_ipv6));
|
||||
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &on_wifi_connect));
|
||||
#endif
|
||||
esp_err_t err = esp_wifi_stop();
|
||||
if (err == ESP_ERR_WIFI_NOT_INIT) {
|
||||
return;
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
ESP_ERROR_CHECK(esp_wifi_deinit());
|
||||
ESP_ERROR_CHECK(esp_wifi_clear_default_wifi_driver_and_handlers(wifi_netif));
|
||||
esp_netif_destroy(wifi_netif);
|
||||
s_example_esp_netif = NULL;
|
||||
}
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
|
||||
/** Event handler for Ethernet events */
|
||||
static void on_eth_event(void *esp_netif, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
switch (event_id) {
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet Link Up");
|
||||
ESP_ERROR_CHECK(esp_netif_create_ip6_linklocal(esp_netif));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
|
||||
static esp_eth_handle_t s_eth_handle = NULL;
|
||||
static esp_eth_mac_t *s_mac = NULL;
|
||||
static esp_eth_phy_t *s_phy = NULL;
|
||||
static esp_eth_netif_glue_handle_t s_eth_glue = NULL;
|
||||
|
||||
static esp_netif_t *eth_start(void)
|
||||
{
|
||||
char *desc;
|
||||
esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
|
||||
// Prefix the interface description with the module TAG
|
||||
// Warning: the interface desc is used in tests to capture actual connection details (IP, gw, mask)
|
||||
asprintf(&desc, "%s: %s", TAG, esp_netif_config.if_desc);
|
||||
esp_netif_config.if_desc = desc;
|
||||
esp_netif_config.route_prio = 64;
|
||||
esp_netif_config_t netif_config = {
|
||||
.base = &esp_netif_config,
|
||||
.stack = ESP_NETIF_NETSTACK_DEFAULT_ETH
|
||||
};
|
||||
esp_netif_t *netif = esp_netif_new(&netif_config);
|
||||
assert(netif);
|
||||
free(desc);
|
||||
|
||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||
phy_config.phy_addr = CONFIG_EXAMPLE_ETH_PHY_ADDR;
|
||||
phy_config.reset_gpio_num = CONFIG_EXAMPLE_ETH_PHY_RST_GPIO;
|
||||
#if CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
|
||||
esp32_emac_config.smi_mdc_gpio_num = CONFIG_EXAMPLE_ETH_MDC_GPIO;
|
||||
esp32_emac_config.smi_mdio_gpio_num = CONFIG_EXAMPLE_ETH_MDIO_GPIO;
|
||||
s_mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
|
||||
#if CONFIG_EXAMPLE_ETH_PHY_IP101
|
||||
s_phy = esp_eth_phy_new_ip101(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
|
||||
s_phy = esp_eth_phy_new_rtl8201(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_LAN87XX
|
||||
s_phy = esp_eth_phy_new_lan87xx(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_DP83848
|
||||
s_phy = esp_eth_phy_new_dp83848(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_KSZ80XX
|
||||
s_phy = esp_eth_phy_new_ksz80xx(&phy_config);
|
||||
#endif
|
||||
#elif CONFIG_EXAMPLE_USE_SPI_ETHERNET
|
||||
gpio_install_isr_service(0);
|
||||
spi_device_handle_t spi_handle = NULL;
|
||||
spi_bus_config_t buscfg = {
|
||||
.miso_io_num = CONFIG_EXAMPLE_ETH_SPI_MISO_GPIO,
|
||||
.mosi_io_num = CONFIG_EXAMPLE_ETH_SPI_MOSI_GPIO,
|
||||
.sclk_io_num = CONFIG_EXAMPLE_ETH_SPI_SCLK_GPIO,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
|
||||
#if CONFIG_EXAMPLE_USE_DM9051
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 1,
|
||||
.address_bits = 7,
|
||||
.mode = 0,
|
||||
.clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
|
||||
.spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
|
||||
.queue_size = 20
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
|
||||
/* dm9051 ethernet driver is based on spi driver */
|
||||
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle);
|
||||
dm9051_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
|
||||
s_mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
|
||||
s_phy = esp_eth_phy_new_dm9051(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_USE_W5500
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 16, // Actually it's the address phase in W5500 SPI frame
|
||||
.address_bits = 8, // Actually it's the control phase in W5500 SPI frame
|
||||
.mode = 0,
|
||||
.clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
|
||||
.spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
|
||||
.queue_size = 20
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
|
||||
/* w5500 ethernet driver is based on spi driver */
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
|
||||
w5500_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
|
||||
s_mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||
s_phy = esp_eth_phy_new_w5500(&phy_config);
|
||||
#endif
|
||||
#elif CONFIG_EXAMPLE_USE_OPENETH
|
||||
phy_config.autonego_timeout_ms = 100;
|
||||
s_mac = esp_eth_mac_new_openeth(&mac_config);
|
||||
s_phy = esp_eth_phy_new_dp83848(&phy_config);
|
||||
#endif
|
||||
|
||||
// Install Ethernet driver
|
||||
esp_eth_config_t config = ETH_DEFAULT_CONFIG(s_mac, s_phy);
|
||||
ESP_ERROR_CHECK(esp_eth_driver_install(&config, &s_eth_handle));
|
||||
#if !CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
/* The SPI Ethernet module might doesn't have a burned factory MAC address, we cat to set it manually.
|
||||
02:00:00 is a Locally Administered OUI range so should not be used except when testing on a LAN under your control.
|
||||
*/
|
||||
ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_S_MAC_ADDR, (uint8_t[]) {
|
||||
0x02, 0x00, 0x00, 0x12, 0x34, 0x56
|
||||
}));
|
||||
#endif
|
||||
// combine driver with netif
|
||||
s_eth_glue = esp_eth_new_netif_glue(s_eth_handle);
|
||||
esp_netif_attach(netif, s_eth_glue);
|
||||
|
||||
// Register user defined event handers
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &on_got_ip, NULL));
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_CONNECTED, &on_eth_event, netif));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &on_got_ipv6, NULL));
|
||||
#endif
|
||||
|
||||
esp_eth_start(s_eth_handle);
|
||||
return netif;
|
||||
}
|
||||
|
||||
static void eth_stop(void)
|
||||
{
|
||||
esp_netif_t *eth_netif = get_example_netif_from_desc("eth");
|
||||
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, &on_got_ip));
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_IPV6
|
||||
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, &on_got_ipv6));
|
||||
ESP_ERROR_CHECK(esp_event_handler_unregister(ETH_EVENT, ETHERNET_EVENT_CONNECTED, &on_eth_event));
|
||||
#endif
|
||||
ESP_ERROR_CHECK(esp_eth_stop(s_eth_handle));
|
||||
ESP_ERROR_CHECK(esp_eth_del_netif_glue(s_eth_glue));
|
||||
ESP_ERROR_CHECK(esp_eth_driver_uninstall(s_eth_handle));
|
||||
s_eth_handle = NULL;
|
||||
ESP_ERROR_CHECK(s_phy->del(s_phy));
|
||||
ESP_ERROR_CHECK(s_mac->del(s_mac));
|
||||
|
||||
esp_netif_destroy(eth_netif);
|
||||
s_example_esp_netif = NULL;
|
||||
}
|
||||
|
||||
esp_eth_handle_t get_example_eth_handle(void)
|
||||
{
|
||||
return s_eth_handle;
|
||||
}
|
||||
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
esp_netif_t *get_example_netif(void)
|
||||
{
|
||||
return s_example_esp_netif;
|
||||
}
|
||||
|
||||
esp_netif_t *get_example_netif_from_desc(const char *desc)
|
||||
{
|
||||
esp_netif_t *netif = NULL;
|
||||
char *expected_desc;
|
||||
asprintf(&expected_desc, "%s: %s", TAG, desc);
|
||||
while ((netif = esp_netif_next(netif)) != NULL) {
|
||||
if (strcmp(esp_netif_get_desc(netif), expected_desc) == 0) {
|
||||
free(expected_desc);
|
||||
return netif;
|
||||
}
|
||||
}
|
||||
free(expected_desc);
|
||||
return netif;
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/* Common utilities for socket address input interface:
|
||||
The API get_addr_from_stdin() is mainly used by socket client examples which read IP address from stdin (if configured).
|
||||
This option is typically used in the CI, but could be enabled in the project configuration.
|
||||
In that case this component is used to receive a string that is evaluated and processed to output
|
||||
socket structures to open a connectio
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "lwip/sys.h"
|
||||
#include <lwip/netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
/**
|
||||
* @brief Read and evaluate IP address from stdin
|
||||
*
|
||||
* This API reads stdin and parses the input address using getaddrinfo()
|
||||
* to fill in struct sockaddr_storage (for both IPv4 and IPv6) used to open
|
||||
* a socket. IP protocol is guessed from the IP address string.
|
||||
*
|
||||
* @param[in] port port number of expected connection
|
||||
* @param[in] sock_type expected protocol: SOCK_STREAM or SOCK_DGRAM
|
||||
* @param[out] ip_protocol resultant IP protocol: IPPROTO_IP or IPPROTO_IP6
|
||||
* @param[out] addr_family resultant address family: AF_INET or AF_INET6
|
||||
* @param[out] dest_addr sockaddr_storage structure (for both IPv4 and IPv6)
|
||||
* @return ESP_OK on success, ESP_FAIL otherwise
|
||||
*/
|
||||
esp_err_t get_addr_from_stdin(int port, int sock_type,
|
||||
int *ip_protocol,
|
||||
int *addr_family,
|
||||
struct sockaddr_storage *dest_addr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -0,0 +1,92 @@
|
||||
/* Common functions for protocol examples, to establish Wi-Fi or Ethernet connection.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_netif.h"
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#define EXAMPLE_INTERFACE get_example_netif()
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#define EXAMPLE_INTERFACE get_example_netif()
|
||||
#endif
|
||||
|
||||
#if !defined (CONFIG_EXAMPLE_CONNECT_ETHERNET) && !defined (CONFIG_EXAMPLE_CONNECT_WIFI)
|
||||
// This is useful for some tests which do not need a network connection
|
||||
#define EXAMPLE_INTERFACE NULL
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Configure Wi-Fi or Ethernet, connect, wait for IP
|
||||
*
|
||||
* This all-in-one helper function is used in protocols examples to
|
||||
* reduce the amount of boilerplate in the example.
|
||||
*
|
||||
* It is not intended to be used in real world applications.
|
||||
* See examples under examples/wifi/getting_started/ and examples/ethernet/
|
||||
* for more complete Wi-Fi or Ethernet initialization code.
|
||||
*
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*
|
||||
* @return ESP_OK on successful connection
|
||||
*/
|
||||
esp_err_t example_connect(void);
|
||||
|
||||
/**
|
||||
* Counterpart to example_connect, de-initializes Wi-Fi or Ethernet
|
||||
*/
|
||||
esp_err_t example_disconnect(void);
|
||||
|
||||
/**
|
||||
* @brief Configure stdin and stdout to use blocking I/O
|
||||
*
|
||||
* This helper function is used in ASIO examples. It wraps installing the
|
||||
* UART driver and configuring VFS layer to use UART driver for console I/O.
|
||||
*/
|
||||
esp_err_t example_configure_stdin_stdout(void);
|
||||
|
||||
/**
|
||||
* @brief Returns esp-netif pointer created by example_connect()
|
||||
*
|
||||
* @note If multiple interfaces active at once, this API return NULL
|
||||
* In that case the get_example_netif_from_desc() should be used
|
||||
* to get esp-netif pointer based on interface description
|
||||
*/
|
||||
esp_netif_t *get_example_netif(void);
|
||||
|
||||
/**
|
||||
* @brief Returns esp-netif pointer created by example_connect() described by
|
||||
* the supplied desc field
|
||||
*
|
||||
* @param desc Textual interface of created network interface, for example "sta"
|
||||
* indicate default WiFi station, "eth" default Ethernet interface.
|
||||
*
|
||||
*/
|
||||
esp_netif_t *get_example_netif_from_desc(const char *desc);
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
/**
|
||||
* @brief Get the example Ethernet driver handle
|
||||
*
|
||||
* @return esp_eth_handle_t
|
||||
*/
|
||||
esp_eth_handle_t get_example_eth_handle(void);
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
29
common_components/protocol_examples_common/stdin_out.c
Normal file
29
common_components/protocol_examples_common/stdin_out.c
Normal file
@ -0,0 +1,29 @@
|
||||
/* Common functions for protocol examples, to configure stdin and stdout.
|
||||
|
||||
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 "protocol_examples_common.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "driver/uart.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
esp_err_t example_configure_stdin_stdout(void)
|
||||
{
|
||||
// Initialize VFS & UART so we can use std::cout/cin
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
/* Install UART driver for interrupt-driven reads and writes */
|
||||
ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM,
|
||||
256, 0, 0, NULL, 0) );
|
||||
/* Tell VFS to use UART driver */
|
||||
esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
|
||||
/* Move the caret to the beginning of the next line on '\n' */
|
||||
esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
|
||||
return ESP_OK;
|
||||
}
|
@ -7,6 +7,7 @@ if(${target} STREQUAL "linux")
|
||||
set(dependencies esp_system_protocols_linux)
|
||||
else()
|
||||
set(platform_srcs src/esp_modem_primitives_freertos.cpp
|
||||
src/esp_modem_api_target.cpp
|
||||
src/esp_modem_uart.cpp
|
||||
src/esp_modem_term_uart.cpp
|
||||
src/esp_modem_netif.cpp)
|
||||
@ -33,16 +34,11 @@ idf_component_register(SRCS "${srcs}"
|
||||
PRIV_INCLUDE_DIRS private_include
|
||||
REQUIRES ${dependencies})
|
||||
|
||||
|
||||
# If CMake doesn't know C++17 features, set it manually via comile options
|
||||
# esp-modem component requires C++17 internally, but older CMake (< 3.8, but still supported in IDF)
|
||||
# doesn't support target_compile_features() for cxx_std_17.
|
||||
get_property(cxx_known_features GLOBAL PROPERTY CMAKE_CXX_KNOWN_FEATURES)
|
||||
if ("cxx_std_17" IN_LIST cxx_known_features)
|
||||
target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17)
|
||||
else()
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-std=gnu++17")
|
||||
endif()
|
||||
set_target_properties(${COMPONENT_LIB} PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS ON
|
||||
)
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
# This is needed for ESP_LOGx() macros, as integer formats differ on ESP32(..) and x64
|
||||
|
@ -16,6 +16,10 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "network_dce.h"
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
#include "esp_mac.h"
|
||||
#include "dhcpserver/dhcpserver.h"
|
||||
#endif
|
||||
|
||||
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
|
||||
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
|
||||
|
@ -5,5 +5,9 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(${COMPONENT_LIB} PRIVATE Threads::Threads)
|
||||
|
||||
target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17)
|
||||
set_target_properties(${COMPONENT_LIB} PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS ON
|
||||
)
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE "-DCONFIG_IDF_TARGET_LINUX")
|
||||
|
@ -4,5 +4,13 @@ cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../..")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/version.cmake)
|
||||
|
||||
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "4.4")
|
||||
if(("${IDF_TARGET}" STREQUAL "esp32s2") OR ("${IDF_TARGET}" STREQUAL "esp32s3"))
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(modem-console)
|
||||
|
@ -1,7 +1,5 @@
|
||||
# Modem console example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||
|
||||
This example is mainly targets experimenting with a modem device, sending custom commands and switching to PPP mode using esp-console, command line API.
|
||||
@ -16,6 +14,16 @@ that sets up the DCE based on a custom module implemented in the `my_module_dce.
|
||||
`get_module_name()` method supplying a user defined name, but keeps all other commands the same as defined in the `GenericModule`
|
||||
class.
|
||||
|
||||
### USB DTE support
|
||||
|
||||
For USB enabled targets (ESP32-S2 and ESP32-S3), it is possible to connect to the modem device via USB.
|
||||
1. In menuconfig, navigate to `Example Configuration->Type of serial connection to the modem` and choose `USB`.
|
||||
2. Connect the modem USB signals to pin 19 (DATA-) and 20 (DATA+) on your ESP chip.
|
||||
|
||||
USB example uses Quactel BG96 modem device.
|
||||
|
||||
### Supported IDF versions
|
||||
|
||||
This example is only supported from `v4.2`, due to support of the console repl mode.
|
||||
This example is only supported from `v4.2`, due to support of the console repl mode.
|
||||
|
||||
USB example is supported from `v4.4`.
|
@ -0,0 +1,19 @@
|
||||
include($ENV{IDF_PATH}/tools/cmake/version.cmake)
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
# USB is supported on S2 and S3 targets and IDF version >= 4.4
|
||||
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "4.4")
|
||||
if(("${target}" STREQUAL "esp32s2") OR ("${target}" STREQUAL "esp32s3"))
|
||||
|
||||
idf_component_register(SRCS "esp_modem_usb.cpp" "esp_modem_usb_api_target.cpp"
|
||||
REQUIRES cdc_acm_host esp_modem
|
||||
REQUIRED_IDF_TARGETS esp32s2 esp32s3
|
||||
PRIV_INCLUDE_DIRS "private_include"
|
||||
INCLUDE_DIRS "include")
|
||||
|
||||
set_target_properties(${COMPONENT_LIB} PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
)
|
||||
|
||||
endif()
|
||||
endif()
|
@ -0,0 +1,191 @@
|
||||
// Copyright 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_modem_config.h"
|
||||
#include "esp_modem_usb_config.h"
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "usb/usb_host.h"
|
||||
#include "usb/cdc_acm_host.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "usb_terminal.hpp"
|
||||
|
||||
static const char *TAG = "usb_terminal";
|
||||
|
||||
/**
|
||||
* @brief USB Host task
|
||||
*
|
||||
* This task is created only if install_usb_host is set to true in DTE configuration.
|
||||
* In case the user doesn't want in install USB Host driver here, he must install it before creating UsbTerminal object.
|
||||
*
|
||||
* @param arg Unused
|
||||
*/
|
||||
void usb_host_task(void *arg)
|
||||
{
|
||||
while (1) {
|
||||
// Start handling system events
|
||||
uint32_t event_flags;
|
||||
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
|
||||
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
|
||||
ESP_LOGD(TAG, "No more clients: clean up\n");
|
||||
usb_host_device_free_all();
|
||||
}
|
||||
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
|
||||
ESP_LOGD(TAG, "All free: uninstall USB lib\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up USB Host
|
||||
vTaskDelay(10); // Short delay to allow clients clean-up
|
||||
usb_host_lib_handle_events(0, NULL); // Make sure there are now pending events
|
||||
usb_host_uninstall();
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
namespace esp_modem {
|
||||
class UsbTerminal : public Terminal, private CdcAcmDevice {
|
||||
public:
|
||||
explicit UsbTerminal(const esp_modem_dte_config *config)
|
||||
{
|
||||
const struct esp_modem_usb_term_config* usb_config = (struct esp_modem_usb_term_config*)(config->extension_config);
|
||||
|
||||
// Install USB Host driver
|
||||
if (usb_config->install_usb_host) {
|
||||
const usb_host_config_t host_config = {
|
||||
.skip_phy_setup = false,
|
||||
.intr_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
};
|
||||
throw_if_esp_fail(usb_host_install(&host_config), "USB Host install failed");
|
||||
ESP_LOGD(TAG, "USB Host installed");
|
||||
throw_if_false(pdTRUE == xTaskCreatePinnedToCore(usb_host_task, "usb_host", 4096, NULL, config->task_priority + 1, NULL, usb_config->xCoreID), "USB host task failed");
|
||||
}
|
||||
|
||||
// Install CDC-ACM driver
|
||||
const cdc_acm_host_driver_config_t esp_modem_cdc_acm_driver_config = {
|
||||
.driver_task_stack_size = config->task_stack_size,
|
||||
.driver_task_priority = config->task_priority,
|
||||
.xCoreID = (BaseType_t)usb_config->xCoreID
|
||||
};
|
||||
|
||||
// Silently continue of error: CDC-ACM driver might be already installed
|
||||
cdc_acm_host_install(&esp_modem_cdc_acm_driver_config);
|
||||
|
||||
// Open CDC-ACM device
|
||||
const cdc_acm_host_device_config_t esp_modem_cdc_acm_device_config = {
|
||||
.connection_timeout_ms = usb_config->timeout_ms,
|
||||
.out_buffer_size = config->dte_buffer_size,
|
||||
.event_cb = handle_notif,
|
||||
.data_cb = handle_rx,
|
||||
.user_arg = this
|
||||
};
|
||||
|
||||
if (usb_config->cdc_compliant) {
|
||||
throw_if_esp_fail(this->CdcAcmDevice::open(usb_config->vid, usb_config->pid,
|
||||
usb_config->interface_idx, &esp_modem_cdc_acm_device_config),
|
||||
"USB Device open failed");
|
||||
} else {
|
||||
throw_if_esp_fail(this->CdcAcmDevice::open_vendor_specific(usb_config->vid, usb_config->pid,
|
||||
usb_config->interface_idx, &esp_modem_cdc_acm_device_config),
|
||||
"USB Device open failed");
|
||||
}
|
||||
};
|
||||
|
||||
~UsbTerminal()
|
||||
{
|
||||
this->CdcAcmDevice::close();
|
||||
};
|
||||
|
||||
void start() override
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void stop() override
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int write(uint8_t *data, size_t len) override
|
||||
{
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_DEBUG);
|
||||
if (this->CdcAcmDevice::tx_blocking(data, len) != ESP_OK) {
|
||||
return -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int read(uint8_t *data, size_t len) override
|
||||
{
|
||||
// This function should never be called. UsbTerminal provides data through Terminal::on_read callback
|
||||
ESP_LOGW(TAG, "Unexpected call to UsbTerminal::read function");
|
||||
return -1;
|
||||
}
|
||||
|
||||
private:
|
||||
UsbTerminal() = delete;
|
||||
UsbTerminal(const UsbTerminal ©) = delete;
|
||||
UsbTerminal &operator=(const UsbTerminal ©) = delete;
|
||||
bool operator== (const UsbTerminal ¶m) const = delete;
|
||||
bool operator!= (const UsbTerminal ¶m) const = delete;
|
||||
|
||||
static void handle_rx(uint8_t *data, size_t data_len, void *user_arg)
|
||||
{
|
||||
ESP_LOG_BUFFER_HEXDUMP(TAG, data, data_len, ESP_LOG_DEBUG);
|
||||
UsbTerminal *this_terminal = static_cast<UsbTerminal *>(user_arg);
|
||||
if (data_len > 0 && this_terminal->on_read) {
|
||||
this_terminal->on_read(data, data_len);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Unhandled RX data");
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_notif(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx)
|
||||
{
|
||||
UsbTerminal *this_terminal = static_cast<UsbTerminal *>(user_ctx);
|
||||
|
||||
switch (event->type) {
|
||||
// Notifications like Ring, Rx Carrier indication or Network connection indication are not relevant for USB terminal
|
||||
case CDC_ACM_HOST_NETWORK_CONNECTION:
|
||||
case CDC_ACM_HOST_SERIAL_STATE:
|
||||
break;
|
||||
case CDC_ACM_HOST_DEVICE_DISCONNECTED:
|
||||
ESP_LOGW(TAG, "USB terminal disconnected");
|
||||
cdc_acm_host_close(cdc_hdl);
|
||||
if (this_terminal->on_error) {
|
||||
this_terminal->on_error(terminal_error::UNEXPECTED_CONTROL_FLOW);
|
||||
}
|
||||
break;
|
||||
case CDC_ACM_HOST_ERROR:
|
||||
ESP_LOGE(TAG, "Unexpected CDC-ACM error: %d.", event->data.error);
|
||||
if (this_terminal->on_error) {
|
||||
this_terminal->on_error(terminal_error::UNEXPECTED_CONTROL_FLOW);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
std::unique_ptr<Terminal> create_usb_terminal(const esp_modem_dte_config *config)
|
||||
{
|
||||
TRY_CATCH_RET_NULL(
|
||||
return std::make_unique<UsbTerminal>(config);
|
||||
)
|
||||
}
|
||||
} // namespace esp_modem
|
@ -0,0 +1,32 @@
|
||||
// Copyright 2021-2022 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
#include "usb_terminal.hpp"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include "cxx_include/esp_modem_netif.hpp"
|
||||
|
||||
#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
|
||||
static const char *TAG = "modem_usb_api_target";
|
||||
#endif
|
||||
|
||||
namespace esp_modem {
|
||||
std::shared_ptr<DTE> create_usb_dte(const dte_config *config)
|
||||
{
|
||||
TRY_CATCH_RET_NULL(
|
||||
auto term = create_usb_terminal(config);
|
||||
return std::make_shared<DTE>(config, std::move(term));
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// Copyright 2021-2022 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "cxx_include/esp_modem_dce.hpp"
|
||||
#include "cxx_include/esp_modem_dce_module.hpp"
|
||||
|
||||
namespace esp_modem {
|
||||
/**
|
||||
* @brief Create USB DTE
|
||||
*
|
||||
* @param config DTE configuration
|
||||
* @return shared ptr to DTE on success
|
||||
* nullptr on failure (either due to insufficient memory or wrong dte configuration)
|
||||
* if exceptions are disabled the API abort()'s on error
|
||||
*/
|
||||
std::shared_ptr<DTE> create_usb_dte(const dte_config *config);
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
// Copyright 2021-2022 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief USB configuration structure
|
||||
* @see USB host CDC-ACM driver documentation for details about interfaces settings
|
||||
*/
|
||||
struct esp_modem_usb_term_config {
|
||||
uint16_t vid; /*!< Vendor ID of the USB device */
|
||||
uint16_t pid; /*!< Product ID of the USB device */
|
||||
int interface_idx; /*!< USB Interface index that will be used */
|
||||
uint32_t timeout_ms; /*!< Time for a USB modem to connect to USB host. 0 means wait forever. */
|
||||
int xCoreID; /*!< Core affinity of created tasks: CDC-ACM driver task and optional USB Host task */
|
||||
bool cdc_compliant; /*!< Treat the USB device as CDC-compliant. Read CDC-ACM driver documentation for more details */
|
||||
bool install_usb_host; /*!< Flag whether USB Host driver should be installed */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ESP Mode USB DTE Default Configuration
|
||||
*
|
||||
* @param[in] _usb_config esp_modem_usb_term_config configuration structure. Can be obtained by ESP_MODEM_DEFAULT_USB_CONFIG
|
||||
*
|
||||
*/
|
||||
#define ESP_MODEM_DTE_DEFAULT_USB_CONFIG(_usb_config) \
|
||||
{ \
|
||||
.dte_buffer_size = 512, \
|
||||
.task_stack_size = 4096, \
|
||||
.task_priority = 5, \
|
||||
.extension_config = &_usb_config \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ESP Modem USB Default Configuration
|
||||
*
|
||||
* @param[in] _vid USB Vendor ID
|
||||
* @param[in] _pid USB Product ID
|
||||
* @see USB host CDC-ACM driver documentation for details about interfaces settings
|
||||
*/
|
||||
#define ESP_MODEM_DEFAULT_USB_CONFIG(_vid, _pid) \
|
||||
{ \
|
||||
.vid = _vid, \
|
||||
.pid = _pid, \
|
||||
.interface_idx = 0, \
|
||||
.timeout_ms = 0, \
|
||||
.xCoreID = 0, \
|
||||
.cdc_compliant = false, \
|
||||
.install_usb_host = true \
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
// Copyright 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
struct esp_modem_dte_config;
|
||||
|
||||
// Copy-pasted from esp_modem/private_include/exception_stub.hpp
|
||||
#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
|
||||
#define TRY_CATCH_OR_DO(block, action) \
|
||||
try { block \
|
||||
} catch (std::bad_alloc& e) { \
|
||||
ESP_LOGE(TAG, "Out of memory"); \
|
||||
action; \
|
||||
} catch (::esp_modem::esp_err_exception& e) { \
|
||||
esp_err_t err = e.get_err_t(); \
|
||||
ESP_LOGE(TAG, "%s: Exception caught with ESP err_code=%d", __func__, err); \
|
||||
ESP_LOGE(TAG, "%s", e.what()); \
|
||||
action; \
|
||||
}
|
||||
|
||||
#define TRY_CATCH_RET_NULL(block) TRY_CATCH_OR_DO(block, return nullptr)
|
||||
#else
|
||||
|
||||
#define TRY_CATCH_OR_DO(block, action) \
|
||||
block
|
||||
|
||||
#define TRY_CATCH_RET_NULL(block) \
|
||||
block
|
||||
|
||||
#endif
|
||||
|
||||
namespace esp_modem {
|
||||
|
||||
std::unique_ptr<Terminal> create_usb_terminal(const esp_modem_dte_config *config);
|
||||
|
||||
} // namespace esp_modem
|
@ -2,4 +2,5 @@ idf_component_register(SRCS "modem_console_main.cpp"
|
||||
"console_helper.cpp"
|
||||
"httpget_handle.c"
|
||||
"ping_handle.c"
|
||||
REQUIRES console esp_http_client nvs_flash esp_modem esp_modem_usb_dte
|
||||
INCLUDE_DIRS ".")
|
||||
|
@ -0,0 +1,17 @@
|
||||
|
||||
menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_SERIAL_CONFIG
|
||||
prompt "Type of serial connection to the modem"
|
||||
default EXAMPLE_SERIAL_CONFIG_UART
|
||||
config EXAMPLE_SERIAL_CONFIG_UART
|
||||
bool "UART"
|
||||
help
|
||||
Connect to modem via UART.
|
||||
config EXAMPLE_SERIAL_CONFIG_USB
|
||||
bool "USB"
|
||||
depends on IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
help
|
||||
Connect to modem via USB (CDC-ACM class). For IDF version >= 4.4.
|
||||
endchoice
|
||||
endmenu
|
@ -46,6 +46,7 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
case HTTP_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -105,4 +106,4 @@ void modem_console_register_http(void)
|
||||
.argtable = &http_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&http_cmd));
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,10 @@
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "esp_modem_config.h"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#if defined(CONFIG_USB_OTG_SUPPORTED)
|
||||
#include "esp_modem_usb_config.h"
|
||||
#include "cxx_include/esp_modem_usb_api.hpp"
|
||||
#endif
|
||||
#include "esp_log.h"
|
||||
#include "console_helper.hpp"
|
||||
#include "my_module_dce.hpp"
|
||||
@ -53,13 +57,29 @@ extern "C" void app_main(void)
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
// init the netif, DTE and DCE respectively
|
||||
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
|
||||
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(DEFAULT_APN);
|
||||
esp_netif_config_t ppp_netif_config = ESP_NETIF_DEFAULT_PPP();
|
||||
esp_netif_t *esp_netif = esp_netif_new(&ppp_netif_config);
|
||||
assert(esp_netif);
|
||||
|
||||
#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_UART)
|
||||
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
|
||||
auto uart_dte = create_uart_dte(&dte_config);
|
||||
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(DEFAULT_APN);
|
||||
auto dce = create_shiny_dce(&dce_config, uart_dte, esp_netif);
|
||||
|
||||
#elif defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
|
||||
struct esp_modem_usb_term_config usb_config = ESP_MODEM_DEFAULT_USB_CONFIG(0x2C7C, 0x0296); // VID and PID of BG96 modem
|
||||
// BG96 modem implements Vendor Specific class, that is CDC-ACM like. Interface for AT commands has index no. 2.
|
||||
usb_config.interface_idx = 2;
|
||||
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_USB_CONFIG(usb_config);
|
||||
ESP_LOGI(TAG, "Waiting for USB device connection...");
|
||||
auto dte = create_usb_dte(&dte_config);
|
||||
std::unique_ptr<DCE> dce = create_BG96_dce(&dce_config, dte, esp_netif);
|
||||
|
||||
#else
|
||||
#error Invalid serial connection to modem.
|
||||
#endif
|
||||
|
||||
assert(dce != nullptr);
|
||||
|
||||
// init console REPL environment
|
||||
|
@ -143,7 +143,21 @@ void app_main(void)
|
||||
// Init netif object
|
||||
esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config);
|
||||
assert(esp_netif);
|
||||
|
||||
#if CONFIG_EXAMPLE_MODEM_DEVICE_BG96 == 1
|
||||
ESP_LOGI(TAG, "Initializing esp_modem for the BG96 module...");
|
||||
esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_BG96, &dte_config, &dce_config, esp_netif);
|
||||
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM800 == 1
|
||||
ESP_LOGI(TAG, "Initializing esp_modem for the SIM800 module...");
|
||||
esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_SIM800, &dte_config, &dce_config, esp_netif);
|
||||
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600 == 1
|
||||
ESP_LOGI(TAG, "Initializing esp_modem for the SIM7600 module...");
|
||||
esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_SIM7600, &dte_config, &dce_config, esp_netif);
|
||||
#else
|
||||
ESP_LOGI(TAG, "Initializing esp_modem for a generic module...");
|
||||
esp_modem_dce_t *dce = esp_modem_new(&dte_config, &dce_config, esp_netif);
|
||||
#endif
|
||||
assert(dce);
|
||||
|
||||
#if CONFIG_EXAMPLE_NEED_SIM_PIN == 1
|
||||
// check if PIN needed
|
||||
|
@ -70,9 +70,9 @@ extern "C" void app_main(void)
|
||||
#if CONFIG_EXAMPLE_MODEM_DEVICE_BG96 == 1
|
||||
std::unique_ptr<DCE> dce = create_BG96_dce(&dce_config, dte, esp_netif);
|
||||
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM800 == 1
|
||||
std::unique_ptr<DCE> dce = create_SIM800_dce(&dce_config, uart_dte, esp_netif);
|
||||
std::unique_ptr<DCE> dce = create_SIM800_dce(&dce_config, dte, esp_netif);
|
||||
#elif CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600 == 1
|
||||
std::unique_ptr<DCE> dce = create_SIM7600_dce(&dce_config, uart_dte, esp_netif);
|
||||
std::unique_ptr<DCE> dce = create_SIM7600_dce(&dce_config, dte, esp_netif);
|
||||
#else
|
||||
#error "Unsupported device"
|
||||
#endif
|
||||
|
@ -1,5 +1,6 @@
|
||||
version: "0.1.11"
|
||||
version: "0.1.17"
|
||||
description: esp modem
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem
|
||||
dependencies:
|
||||
# Required IDF version
|
||||
idf:
|
||||
|
@ -82,6 +82,17 @@ std::shared_ptr<DTE> create_vfs_dte(const dte_config *config);
|
||||
*/
|
||||
std::unique_ptr<DCE> create_SIM7600_dce(const dce_config *config, std::shared_ptr<DTE> dte, esp_netif_t *netif);
|
||||
|
||||
/**
|
||||
* @brief Create DCE based on SIM7070 module
|
||||
*/
|
||||
std::unique_ptr<DCE> create_SIM7070_dce(const dce_config *config, std::shared_ptr<DTE> dte, esp_netif_t *netif);
|
||||
|
||||
/**
|
||||
* @brief Create DCE based on SIM7000 module
|
||||
*/
|
||||
std::unique_ptr<DCE> create_SIM7000_dce(const dce_config *config, std::shared_ptr<DTE> dte, esp_netif_t *netif);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Create DCE based on SIM800 module
|
||||
*/
|
||||
|
@ -44,7 +44,10 @@ DECLARE_ALL_COMMAND_APIS(declare name(Commandable *p, ...);)
|
||||
* @brief Following commands that are different for some specific modules
|
||||
*/
|
||||
command_result get_battery_status_sim7xxx(CommandableIf *t, int &voltage, int &bcs, int &bcl);
|
||||
command_result power_down_sim7xxx(CommandableIf *t);
|
||||
command_result set_gnss_power_mode_sim76xx(CommandableIf *t, int mode);
|
||||
command_result power_down_sim76xx(CommandableIf *t);
|
||||
command_result power_down_sim70xx(CommandableIf *t);
|
||||
command_result set_network_bands_sim76xx(CommandableIf *t, const std::string& mode, const int* bands, int size);
|
||||
command_result power_down_sim8xx(CommandableIf *t);
|
||||
command_result set_data_mode_sim8xx(CommandableIf *t);
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace esp_modem {
|
||||
*/
|
||||
class DCE_Mode {
|
||||
public:
|
||||
DCE_Mode(): mode(modem_mode::COMMAND_MODE) {}
|
||||
DCE_Mode(): mode(modem_mode::UNDEF) {}
|
||||
~DCE_Mode() = default;
|
||||
bool set(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m);
|
||||
modem_mode get();
|
||||
|
@ -117,6 +117,8 @@ private:
|
||||
enum class ModemType {
|
||||
GenericModule, /*!< Default generic module with the most common commands */
|
||||
SIM7600, /*!< Derived from the GenericModule, specifics applied to SIM7600 model */
|
||||
SIM7070, /*!< Derived from the GenericModule, specifics applied to SIM7070 model */
|
||||
SIM7000, /*!< Derived from the GenericModule, specifics applied to SIM7000 model */
|
||||
BG96, /*!< Derived from the GenericModule, specifics applied to BG69 model */
|
||||
SIM800, /*!< Derived from the GenericModule with specifics applied to SIM800 model */
|
||||
};
|
||||
@ -173,6 +175,10 @@ public:
|
||||
return build_shared_module<SIM800>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::SIM7600:
|
||||
return build_shared_module<SIM7600>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::SIM7070:
|
||||
return build_shared_module<SIM7070>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::SIM7000:
|
||||
return build_shared_module<SIM7000>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::BG96:
|
||||
return build_shared_module<BG96>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::GenericModule:
|
||||
@ -198,6 +204,10 @@ public:
|
||||
return build_unique<SIM800>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::SIM7600:
|
||||
return build_unique<SIM7600>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::SIM7070:
|
||||
return build_unique<SIM7070>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::SIM7000:
|
||||
return build_unique<SIM7000>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::BG96:
|
||||
return build_unique<BG96>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::GenericModule:
|
||||
@ -216,6 +226,10 @@ public:
|
||||
return build<SIM800>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::SIM7600:
|
||||
return build<SIM7600>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::SIM7070:
|
||||
return build<SIM7070>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::SIM7000:
|
||||
return build<SIM7000>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::BG96:
|
||||
return build<BG96>(cfg, std::forward<Args>(args)...);
|
||||
case ModemType::GenericModule:
|
||||
|
@ -119,9 +119,28 @@ protected:
|
||||
class SIM7600: public GenericModule {
|
||||
using GenericModule::GenericModule;
|
||||
public:
|
||||
command_result get_module_name(std::string &name) override;
|
||||
command_result get_battery_status(int &voltage, int &bcs, int &bcl) override;
|
||||
command_result power_down() override;
|
||||
command_result set_gnss_power_mode(int mode) override;
|
||||
command_result set_network_bands(const std::string& mode, const int* bands, int size) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Specific definition of the SIM7070 module
|
||||
*/
|
||||
class SIM7070: public GenericModule {
|
||||
using GenericModule::GenericModule;
|
||||
public:
|
||||
command_result power_down() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Specific definition of the SIM7000 module
|
||||
*/
|
||||
class SIM7000: public GenericModule {
|
||||
using GenericModule::GenericModule;
|
||||
public:
|
||||
command_result power_down() override;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -130,7 +149,6 @@ public:
|
||||
class SIM800: public GenericModule {
|
||||
using GenericModule::GenericModule;
|
||||
public:
|
||||
command_result get_module_name(std::string &name) override;
|
||||
command_result power_down() override;
|
||||
command_result set_data_mode() override;
|
||||
};
|
||||
@ -140,8 +158,6 @@ public:
|
||||
*/
|
||||
class BG96: public GenericModule {
|
||||
using GenericModule::GenericModule;
|
||||
public:
|
||||
command_result get_module_name(std::string &name) override;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -22,9 +22,9 @@
|
||||
#include <thread>
|
||||
|
||||
#else
|
||||
#include "freertos/event_groups.h"
|
||||
// forward declarations of FreeRTOS primitives
|
||||
struct QueueDefinition;
|
||||
typedef void *EventGroupHandle_t;
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -67,6 +67,12 @@ struct PdpContext {
|
||||
*/
|
||||
class CommandableIf {
|
||||
public:
|
||||
CommandableIf() = default;
|
||||
CommandableIf(const CommandableIf&) = delete;
|
||||
CommandableIf& operator=(const CommandableIf&) = delete;
|
||||
CommandableIf(CommandableIf&&) = delete;
|
||||
CommandableIf& operator=(CommandableIf&&) = delete;
|
||||
virtual ~CommandableIf() = default;
|
||||
/**
|
||||
* @brief Sends custom AT command
|
||||
* @param command Command to be sent
|
||||
@ -83,6 +89,12 @@ public:
|
||||
*/
|
||||
class ModuleIf {
|
||||
public:
|
||||
ModuleIf() = default;
|
||||
ModuleIf(const ModuleIf&) = delete;
|
||||
ModuleIf& operator=(const ModuleIf&) = delete;
|
||||
ModuleIf(ModuleIf&&) = delete;
|
||||
ModuleIf& operator=(ModuleIf&&) = delete;
|
||||
virtual ~ModuleIf() = default;
|
||||
/**
|
||||
* @brief Sets the data mode up (provides the necessary configuration to connect to the cellular network)
|
||||
* @return true on success
|
||||
|
@ -48,6 +48,8 @@ typedef enum esp_modem_dce_device
|
||||
{
|
||||
ESP_MODEM_DCE_GENETIC, /**< The most generic device */
|
||||
ESP_MODEM_DCE_SIM7600,
|
||||
ESP_MODEM_DCE_SIM7070,
|
||||
ESP_MODEM_DCE_SIM7000,
|
||||
ESP_MODEM_DCE_BG96,
|
||||
ESP_MODEM_DCE_SIM800,
|
||||
} esp_modem_dce_device_t;
|
||||
|
@ -83,10 +83,11 @@ struct esp_modem_vfs_term_config {
|
||||
struct esp_modem_dte_config {
|
||||
size_t dte_buffer_size; /*!< DTE buffer size */
|
||||
uint32_t task_stack_size; /*!< Terminal task stack size */
|
||||
int task_priority; /*!< Terminal task priority */
|
||||
unsigned task_priority; /*!< Terminal task priority */
|
||||
union {
|
||||
struct esp_modem_uart_term_config uart_config; /*!< Configuration for UART Terminal */
|
||||
struct esp_modem_vfs_term_config vfs_config; /*!< Configuration for VFS Terminal */
|
||||
void *extension_config; /*!< Configuration for app specific Terminal */
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
#define BOOL_IN(param, name) const bool _ARG(param, name)
|
||||
#define BOOL_OUT(param, name) bool& _ARG(param, name)
|
||||
#define INT_OUT(param, name) int& _ARG(param, name)
|
||||
#define INTEGER_LIST_IN(param, name) const int* _ARG(param, name)
|
||||
|
||||
#define STRUCT_OUT(struct_name, p1) struct_name& p1
|
||||
#else
|
||||
@ -36,6 +37,7 @@
|
||||
#define BOOL_IN(param, name) const bool _ARG(param, name)
|
||||
#define BOOL_OUT(param, name) bool* _ARG(param, name)
|
||||
#define INT_OUT(param, name) int* _ARG(param, name)
|
||||
#define INTEGER_LIST_IN(param, name) const int* _ARG(param, name)
|
||||
#define STRUCT_OUT(struct_name, p1) struct struct_name* p1
|
||||
#endif
|
||||
|
||||
@ -47,7 +49,7 @@
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(sync, command_result, 0) \
|
||||
/**
|
||||
* @brief Reads the operator name
|
||||
* @param[out] name module name
|
||||
* @param[out] operator name
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(get_operator_name, command_result, 1, STRING_OUT(p1, name)) \
|
||||
@ -200,7 +202,88 @@ ESP_MODEM_DECLARE_DCE_COMMAND(reset, command_result, 0) \
|
||||
* @param[in] baud Desired baud rate of the DTE
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_baud, command_result, 1, INT_IN(p1, baud))
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_baud, command_result, 1, INT_IN(p1, baud)) \
|
||||
\
|
||||
/**
|
||||
* @brief Force an attempt to connect to a specific operator
|
||||
* @param[in] mode mode of attempt
|
||||
* mode=0 - automatic
|
||||
* mode=1 - manual
|
||||
* mode=2 - deregister
|
||||
* mode=3 - set format for read operation
|
||||
* mode=4 - manual with fallback to automatic
|
||||
* @param[in] format what format the operator is given in
|
||||
* format=0 - long format
|
||||
* format=1 - short format
|
||||
* format=2 - numeric
|
||||
* @param[in] oper the operator to connect to
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_operator, command_result, 3, INT_IN(p1, mode), INT_IN(p2, format), STRING_IN(p3, oper)) \
|
||||
\
|
||||
/**
|
||||
* @brief Attach or detach from the GPRS service
|
||||
* @param[in] state 1-attach 0-detach
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_network_attachment_state, command_result, 1, INT_IN(p1, state)) \
|
||||
\
|
||||
/**
|
||||
* @brief Get network attachment state
|
||||
* @param[out] state 1-attached 0-detached
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(get_network_attachment_state, command_result, 1, INT_OUT(p1, state)) \
|
||||
\
|
||||
/**
|
||||
* @brief What mode the radio should be set to
|
||||
* @param[in] state state 1-full 0-minimum ...
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_radio_state, command_result, 1, INT_IN(p1, state)) \
|
||||
\
|
||||
/**
|
||||
* @brief Get current radio state
|
||||
* @param[out] state 1-full 0-minimum ...
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(get_radio_state, command_result, 1, INT_OUT(p1, state)) \
|
||||
\
|
||||
/**
|
||||
* @brief Set network mode
|
||||
* @param[in] mode preferred mode
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_network_mode, command_result, 1, INT_IN(p1, mode)) \
|
||||
\
|
||||
/**
|
||||
* @brief Preferred network mode (CAT-M and/or NB-IoT)
|
||||
* @param[in] mode preferred selection
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_preferred_mode, command_result, 1, INT_IN(p1, mode)) \
|
||||
\
|
||||
/**
|
||||
* @brief Set network bands for CAT-M or NB-IoT
|
||||
* @param[in] mode CAT-M or NB-IoT
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_network_bands, command_result, 3, STRING_IN(p1, mode), INTEGER_LIST_IN(p2, bands), INT_IN(p3, size)) \
|
||||
\
|
||||
/**
|
||||
* @brief Show network system mode
|
||||
* @param[out] mode current network mode
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(get_network_system_mode, command_result, 1, INT_OUT(p1, mode)) \
|
||||
\
|
||||
/**
|
||||
* @brief GNSS power control
|
||||
* @param[out] mode power mode (0 - off, 1 - on)
|
||||
* @return OK, FAIL or TIMEOUT
|
||||
*/ \
|
||||
ESP_MODEM_DECLARE_DCE_COMMAND(set_gnss_power_mode, command_result, 1, INT_IN(p1, mode)) \
|
||||
\
|
||||
|
||||
|
||||
#ifdef GENERATE_DOCS
|
||||
|
@ -3,18 +3,20 @@ set(LWIP_CONTRIB_DIR "$ENV{LWIP_CONTRIB_PATH}")
|
||||
|
||||
set(lwipcontribportunix_SRCS ${LWIP_CONTRIB_DIR}/ports/unix/port/sys_arch.c)
|
||||
|
||||
include(${LWIP_DIR}/src/Filelists.cmake)
|
||||
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
|
||||
|
||||
set (LWIP_INCLUDE_DIRS
|
||||
include(${LWIP_DIR}/src/Filelists.cmake)
|
||||
|
||||
set (LWIP_INCLUDE_DIRS
|
||||
"${LWIP_DIR}/src/include"
|
||||
"${LWIP_CONTRIB_DIR}/ports/unix/port/include")
|
||||
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/netif/slipif.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/core/ip4.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/core/ipv6/ip6.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/netif/slipif.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/core/ipv4/ip4.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/core/ipv6/ip6.c")
|
||||
endif()
|
||||
|
||||
|
||||
idf_component_register(SRCS esp_netif_linux.cpp tun_io.c ip6_stub.c ${lwipnoapps_SRCS} ${lwipcontribportunix_SRCS}
|
||||
idf_component_register(SRCS esp_netif_linux.cpp tun_io.c ip4_stub.c ip6_stub.c ${lwipnoapps_SRCS} ${lwipcontribportunix_SRCS}
|
||||
INCLUDE_DIRS include ${LWIP_INCLUDE_DIRS}
|
||||
PRIV_INCLUDE_DIRS .
|
||||
REQUIRES esp_system_protocols_linux)
|
||||
|
@ -90,43 +90,6 @@ public:
|
||||
extern "C" esp_netif_t *esp_netif_new(const esp_netif_config_t *config)
|
||||
{
|
||||
return new NetifStorage(config);
|
||||
// struct ifreq ifr = { };
|
||||
// esp_netif_t * netif = netif_storage;
|
||||
// if (netif == NULL) {
|
||||
// return NULL;
|
||||
// }
|
||||
// if ((netif->fd = open(config->dev_name, O_RDWR)) == -1) {
|
||||
// ESP_LOGE(TAG, "Cannot open %s", config->dev_name);
|
||||
// goto cleanup;
|
||||
// }
|
||||
// ifr.ifr_flags = IFF_TUN;
|
||||
// strncpy(ifr.ifr_name, config->if_name, IFNAMSIZ);
|
||||
//
|
||||
// if (ioctl(netif->fd, TUNSETIFF, (void *)&ifr) == -1) {
|
||||
// ESP_LOGE(TAG, "Cannot set ioctl TUNSETIFF %m");
|
||||
// goto cleanup;
|
||||
// }
|
||||
// ioctl(netif->fd, TUNSETNOCSUM, 1);
|
||||
//
|
||||
// netif->in_buf = new uint8_t[BUF_SIZE];
|
||||
// netif->out_buf = new uint8_t[BUF_SIZE];
|
||||
// if (netif->in_buf == nullptr || netif->out_buf == nullptr) {
|
||||
// goto cleanup;
|
||||
// }
|
||||
//
|
||||
// if (!ppp_netif_init(netif)) {
|
||||
// ESP_LOGE(TAG, "Cannot initialize pppos lwip netif %m");
|
||||
// goto cleanup;
|
||||
// }
|
||||
//
|
||||
// return netif;
|
||||
//
|
||||
//cleanup:
|
||||
// close(netif->fd);
|
||||
// delete[] netif->in_buf;
|
||||
// delete[] netif->out_buf;
|
||||
// delete netif_storage;
|
||||
// return nullptr;
|
||||
}
|
||||
|
||||
void esp_netif_destroy(esp_netif_t *netif)
|
||||
|
17
components/esp_modem/port/linux/esp_netif_linux/ip4_stub.c
Normal file
17
components/esp_modem/port/linux/esp_netif_linux/ip4_stub.c
Normal file
@ -0,0 +1,17 @@
|
||||
#include "lwip/ip4.h"
|
||||
|
||||
err_t
|
||||
ip4_output_if(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
|
||||
u8_t ttl, u8_t tos,
|
||||
u8_t proto, struct netif *netif)
|
||||
{ return ERR_OK; }
|
||||
|
||||
struct netif *
|
||||
ip4_route(const ip4_addr_t *dest)
|
||||
{ return NULL; }
|
||||
|
||||
err_t
|
||||
ip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
|
||||
u8_t ttl, u8_t tos,
|
||||
u8_t proto, struct netif *netif)
|
||||
{ return ERR_OK; }
|
@ -30,13 +30,6 @@ struct PdpContext;
|
||||
static const char *TAG = "modem_api";
|
||||
#endif
|
||||
|
||||
std::shared_ptr<DTE> create_uart_dte(const dte_config *config)
|
||||
{
|
||||
TRY_CATCH_RET_NULL(
|
||||
auto term = create_uart_terminal(config);
|
||||
return std::make_shared<DTE>(config, std::move(term));
|
||||
)
|
||||
}
|
||||
|
||||
std::shared_ptr<DTE> create_vfs_dte(const dte_config *config)
|
||||
{
|
||||
@ -61,6 +54,16 @@ std::unique_ptr<DCE> create_SIM7600_dce(const dce_config *config, std::shared_pt
|
||||
return create_modem_dce(dce_factory::ModemType::SIM7600, config, std::move(dte), netif);
|
||||
}
|
||||
|
||||
std::unique_ptr<DCE> create_SIM7070_dce(const dce_config *config, std::shared_ptr<DTE> dte, esp_netif_t *netif)
|
||||
{
|
||||
return create_modem_dce(dce_factory::ModemType::SIM7070, config, std::move(dte), netif);
|
||||
}
|
||||
|
||||
std::unique_ptr<DCE> create_SIM7000_dce(const dce_config *config, std::shared_ptr<DTE> dte, esp_netif_t *netif)
|
||||
{
|
||||
return create_modem_dce(dce_factory::ModemType::SIM7000, config, std::move(dte), netif);
|
||||
}
|
||||
|
||||
std::unique_ptr<DCE> create_SIM800_dce(const dce_config *config, std::shared_ptr<DTE> dte, esp_netif_t *netif)
|
||||
{
|
||||
return create_modem_dce(dce_factory::ModemType::SIM800, config, std::move(dte), netif);
|
||||
|
39
components/esp_modem/src/esp_modem_api_target.cpp
Normal file
39
components/esp_modem/src/esp_modem_api_target.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2021 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <cassert>
|
||||
#include "esp_log.h"
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "uart_terminal.hpp"
|
||||
#include "vfs_termial.hpp"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include "cxx_include/esp_modem_dce_factory.hpp"
|
||||
#include "esp_modem_config.h"
|
||||
#include "exception_stub.hpp"
|
||||
|
||||
namespace esp_modem {
|
||||
|
||||
#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
|
||||
static const char *TAG = "modem_api_target";
|
||||
#endif
|
||||
|
||||
std::shared_ptr<DTE> create_uart_dte(const dte_config *config)
|
||||
{
|
||||
TRY_CATCH_RET_NULL(
|
||||
auto term = create_uart_terminal(config);
|
||||
return std::make_shared<DTE>(config, std::move(term));
|
||||
)
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
@ -27,6 +27,10 @@
|
||||
#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;
|
||||
@ -55,6 +59,10 @@ static inline dce_factory::ModemType convert_modem_enum(esp_modem_dce_device_t m
|
||||
switch (module) {
|
||||
case ESP_MODEM_DCE_SIM7600:
|
||||
return esp_modem::dce_factory::ModemType::SIM7600;
|
||||
case ESP_MODEM_DCE_SIM7070:
|
||||
return esp_modem::dce_factory::ModemType::SIM7070;
|
||||
case ESP_MODEM_DCE_SIM7000:
|
||||
return esp_modem::dce_factory::ModemType::SIM7000;
|
||||
case ESP_MODEM_DCE_BG96:
|
||||
return esp_modem::dce_factory::ModemType::BG96;
|
||||
case ESP_MODEM_DCE_SIM800:
|
||||
@ -100,6 +108,14 @@ extern "C" void esp_modem_destroy(esp_modem_dce_t *dce_wrap)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -259,3 +275,100 @@ extern "C" esp_err_t esp_modem_power_down(esp_modem_dce_t *dce_wrap)
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ struct CMux::CMuxFrame {
|
||||
|
||||
void CMux::data_available(uint8_t *data, size_t len)
|
||||
{
|
||||
if (data && type == 0xFF && len > 0 && dlci > 0) {
|
||||
if (data && (type&FT_UIH) == FT_UIH && len > 0 && dlci > 0) { // valid payload on a virtual term
|
||||
int virtual_term = dlci - 1;
|
||||
if (virtual_term < MAX_TERMINALS_NUM && read_cb[virtual_term]) {
|
||||
// Post partial data (or defragment to post on CMUX footer)
|
||||
@ -187,10 +187,22 @@ bool CMux::on_header(CMuxFrame &frame)
|
||||
}
|
||||
size_t payload_offset = std::min(frame.len, 4 - frame_header_offset);
|
||||
memcpy(frame_header + frame_header_offset, frame.ptr, payload_offset);
|
||||
frame_header_offset += payload_offset;
|
||||
if ((frame_header[3] & 1) == 0) {
|
||||
if (frame_header_offset + frame.len <= 4) {
|
||||
frame_header_offset += frame.len;
|
||||
return false; // need read more
|
||||
}
|
||||
payload_offset = std::min(frame.len, 5 - frame_header_offset);
|
||||
memcpy(frame_header + frame_header_offset, frame.ptr, payload_offset);
|
||||
payload_len = frame_header[4] << 7;
|
||||
frame_header_offset += payload_offset - 1; // rewind frame_header back to hold only 6 bytes size
|
||||
} else {
|
||||
payload_len = 0;
|
||||
frame_header_offset += payload_offset;
|
||||
}
|
||||
dlci = frame_header[1] >> 2;
|
||||
type = frame_header[2];
|
||||
payload_len = (frame_header[3] >> 1);
|
||||
payload_len += (frame_header[3] >> 1);
|
||||
frame.advance(payload_offset);
|
||||
state = cmux_state::PAYLOAD;
|
||||
return true;
|
||||
|
@ -121,12 +121,18 @@ command_result power_down(CommandableIf *t)
|
||||
return generic_command(t, "AT+QPOWD=1\r", "POWERED DOWN", "ERROR", 1000);
|
||||
}
|
||||
|
||||
command_result power_down_sim7xxx(CommandableIf *t)
|
||||
command_result power_down_sim76xx(CommandableIf *t)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command_common(t, "AT+CPOF\r", 1000);
|
||||
}
|
||||
|
||||
command_result power_down_sim70xx(CommandableIf *t)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command(t, "AT+CPOWD=1\r", "POWER DOWN", "ERROR", 1000);
|
||||
}
|
||||
|
||||
command_result power_down_sim8xx(CommandableIf *t)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
@ -167,7 +173,7 @@ command_result get_battery_status(CommandableIf *t, int &voltage, int &bcs, int
|
||||
// Parsing +CBC: <bcs>,<bcl>,<voltage>
|
||||
out = out.substr(pattern.size());
|
||||
int pos, value, property = 0;
|
||||
while ((pos = out.find(',') != std::string::npos)) {
|
||||
while ((pos = out.find(',')) != std::string::npos) {
|
||||
if (std::from_chars(out.data(), out.data() + pos, value).ec == std::errc::invalid_argument) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
@ -269,7 +275,7 @@ command_result set_data_mode(CommandableIf *t)
|
||||
command_result set_data_mode_sim8xx(CommandableIf *t)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command(t, "ATD*99##\r", "CONNECT", "ERROR", 5000);
|
||||
return generic_command(t, "ATD*99#\r", "CONNECT", "ERROR", 5000);
|
||||
}
|
||||
|
||||
command_result resume_data_mode(CommandableIf *t)
|
||||
@ -399,4 +405,142 @@ command_result get_signal_quality(CommandableIf *t, int &rssi, int &ber)
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result set_operator(CommandableIf *t, int mode, int format, const std::string& oper)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command_common(t, "AT+COPS=" + std::to_string(mode) + "," + std::to_string(format) + ",\"" + oper + "\"\r", 90000);
|
||||
}
|
||||
|
||||
command_result set_network_attachment_state(CommandableIf *t, int state)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command_common(t, "AT+CGATT=" + std::to_string(state) + "\r");
|
||||
}
|
||||
|
||||
command_result get_network_attachment_state(CommandableIf *t, int &state)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
std::string_view out;
|
||||
auto ret = generic_get_string(t, "AT+CGATT?\r", out);
|
||||
if (ret != command_result::OK) {
|
||||
return ret;
|
||||
}
|
||||
constexpr std::string_view pattern = "+CGATT: ";
|
||||
constexpr int pos = pattern.size();
|
||||
if (out.find(pattern) == std::string::npos) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
if (std::from_chars(out.data() + pos, out.data() + out.size(), state).ec == std::errc::invalid_argument) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result set_radio_state(CommandableIf *t, int state)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command_common(t, "AT+CFUN=" + std::to_string(state) + "\r");
|
||||
}
|
||||
|
||||
command_result get_radio_state(CommandableIf *t, int &state)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
std::string_view out;
|
||||
auto ret = generic_get_string(t, "AT+CFUN?\r", out);
|
||||
if (ret != command_result::OK) {
|
||||
return ret;
|
||||
}
|
||||
constexpr std::string_view pattern = "+CFUN: ";
|
||||
constexpr int pos = pattern.size();
|
||||
if (out.find(pattern) == std::string::npos) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
if (std::from_chars(out.data() + pos, out.data() + out.size(), state).ec == std::errc::invalid_argument) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result set_network_mode(CommandableIf *t, int mode)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command_common(t, "AT+CNMP=" + std::to_string(mode) + "\r");
|
||||
}
|
||||
|
||||
command_result set_preferred_mode(CommandableIf *t, int mode)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command_common(t, "AT+CMNB=" + std::to_string(mode) + "\r");
|
||||
}
|
||||
|
||||
command_result set_network_bands(CommandableIf *t, const std::string& mode, const int* bands, int size)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
std::string band_string = "";
|
||||
for (int i = 0; i<size-1; ++i){
|
||||
band_string += std::to_string(bands[i]) + ",";
|
||||
}
|
||||
band_string += std::to_string(bands[size-1]);
|
||||
|
||||
return generic_command_common(t, "AT+CBANDCFG=\"" + mode + "\"," + band_string + "\r");
|
||||
}
|
||||
|
||||
// mode is expected to be 64bit string (in hex)
|
||||
// any_mode = "0xFFFFFFFF7FFFFFFF";
|
||||
command_result set_network_bands_sim76xx(CommandableIf *t, const std::string& mode, const int* bands, int size)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
static const char *hexDigits = "0123456789ABCDEF";
|
||||
uint64_t band_bits = 0;
|
||||
int hex_len = 16;
|
||||
std::string band_string(hex_len, '0');
|
||||
for (int i = 0; i<size; ++i) {
|
||||
// OR-operation to add bands
|
||||
auto band = bands[i]-1; // Sim7600 has 0-indexed band selection (band 20 has to be shifted 19 places)
|
||||
band_bits |= 1 << band;
|
||||
}
|
||||
for(int i=hex_len; i>0; i--){
|
||||
band_string[i-1] = hexDigits[(band_bits >> ((hex_len-i)*4)) & 0xF];
|
||||
}
|
||||
return generic_command_common(t, "AT+CNBP=" + mode + ",0x" + band_string + "\r");
|
||||
}
|
||||
|
||||
command_result get_network_system_mode(CommandableIf *t, int &mode)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
std::string_view out;
|
||||
auto ret = generic_get_string(t, "AT+CNSMOD?\r", out);
|
||||
if (ret != command_result::OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr std::string_view pattern = "+CNSMOD: ";
|
||||
int mode_pos = out.find(",") + 1; // Skip "<n>,"
|
||||
if (out.find(pattern) == std::string::npos) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
if (std::from_chars(out.data() + mode_pos, out.data() + out.size(), mode).ec == std::errc::invalid_argument) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result set_gnss_power_mode(CommandableIf *t, int mode)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command_common(t, "AT+CGNSPWR=" + std::to_string(mode) + "\r");
|
||||
}
|
||||
|
||||
command_result set_gnss_power_mode_sim76xx(CommandableIf *t, int mode)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__ );
|
||||
return generic_command_common(t, "AT+CGPS=" + std::to_string(mode) + "\r");
|
||||
}
|
||||
|
||||
} // esp_modem::dce_commands
|
@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "cxx_include/esp_modem_dce.hpp"
|
||||
#include "esp_log.h"
|
||||
@ -63,7 +65,9 @@ bool DCE_Mode::set(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m)
|
||||
if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE) {
|
||||
return false;
|
||||
}
|
||||
device->set_mode(modem_mode::CMUX_MODE);
|
||||
device->set_mode(modem_mode::CMUX_MODE); // switch the device into CMUX mode
|
||||
usleep(100'000); // some devices need a few ms to switch
|
||||
|
||||
if (!dte->set_mode(modem_mode::CMUX_MODE)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -50,26 +50,34 @@ DECLARE_ALL_COMMAND_APIS(return_type name(...) )
|
||||
//
|
||||
// Handle specific commands for specific supported modems
|
||||
//
|
||||
command_result SIM7600::get_module_name(std::string &name)
|
||||
{
|
||||
name = "7600";
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result SIM7600::get_battery_status(int &voltage, int &bcs, int &bcl)
|
||||
{
|
||||
return dce_commands::get_battery_status_sim7xxx(dte.get(), voltage, bcs, bcl);
|
||||
}
|
||||
|
||||
command_result SIM7600::power_down()
|
||||
command_result SIM7600::set_network_bands(const std::string& mode, const int* bands, int size)
|
||||
{
|
||||
return dce_commands::power_down_sim7xxx(dte.get());
|
||||
return dce_commands::set_network_bands_sim76xx(dte.get(), mode, bands, size);
|
||||
}
|
||||
|
||||
command_result SIM800::get_module_name(std::string &name)
|
||||
command_result SIM7600::set_gnss_power_mode(int mode)
|
||||
{
|
||||
name = "800L";
|
||||
return command_result::OK;
|
||||
return dce_commands::set_gnss_power_mode_sim76xx(dte.get(), mode);
|
||||
}
|
||||
|
||||
command_result SIM7600::power_down()
|
||||
{
|
||||
return dce_commands::power_down_sim76xx(dte.get());
|
||||
}
|
||||
|
||||
command_result SIM7070::power_down()
|
||||
{
|
||||
return dce_commands::power_down_sim70xx(dte.get());
|
||||
}
|
||||
|
||||
command_result SIM7000::power_down()
|
||||
{
|
||||
return dce_commands::power_down_sim70xx(dte.get());
|
||||
}
|
||||
|
||||
command_result SIM800::power_down()
|
||||
@ -82,10 +90,4 @@ command_result SIM800::set_data_mode()
|
||||
return dce_commands::set_data_mode_sim8xx(dte.get());
|
||||
}
|
||||
|
||||
command_result BG96::get_module_name(std::string &name)
|
||||
{
|
||||
name = "BG96";
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
}
|
@ -111,4 +111,4 @@ void Task::Delay(uint32_t ms)
|
||||
vTaskDelay(pdMS_TO_TICKS(ms));
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
||||
} // namespace esp_modem
|
||||
|
@ -89,4 +89,9 @@ void Task::Relinquish()
|
||||
usleep(0);
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
||||
void Task::Delay(uint32_t ms)
|
||||
{
|
||||
usleep(ms*1000);
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
||||
|
@ -12,4 +12,4 @@ project(host_modem_test)
|
||||
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)
|
||||
target_link_options(${esp_modem} INTERFACE -fsanitize=address -fsanitize=undefined)
|
||||
|
@ -6,5 +6,9 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(${COMPONENT_LIB} PRIVATE Threads::Threads)
|
||||
|
||||
target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17)
|
||||
set_target_properties(${COMPONENT_LIB} PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS ON
|
||||
)
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE "-DCONFIG_IDF_TARGET_LINUX")
|
||||
|
@ -15,6 +15,10 @@ void LoopbackTerm::stop()
|
||||
|
||||
int LoopbackTerm::write(uint8_t *data, size_t len)
|
||||
{
|
||||
if (inject_by) { // injection test: ignore what we write, but respond with injected data
|
||||
auto ret = std::async(&LoopbackTerm::batch_read, this);
|
||||
return len;
|
||||
}
|
||||
if (len > 2 && (data[len - 1] == '\r' || data[len - 1] == '+') ) { // Simple AT responder
|
||||
std::string command((char *)data, len);
|
||||
std::string response;
|
||||
@ -28,8 +32,10 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
|
||||
response = "CONNECT\r\n";
|
||||
} else if (command.find("AT+CSQ\r") != std::string::npos) {
|
||||
response = "+CSQ: 123,456\n\r\nOK\r\n";
|
||||
} else if (command.find("AT+CGMM\r") != std::string::npos) {
|
||||
response = "0G Dummy Model\n\r\nOK\r\n";
|
||||
} else if (command.find("AT+CBC\r") != std::string::npos) {
|
||||
response = is_bg96 ? "+CBC: 1,2,123456V\r\r\n\r\nOK\r\n\n\r\n" :
|
||||
response = is_bg96 ? "+CBC: 1,20,123456\r\r\n\r\nOK\r\n\n\r\n" :
|
||||
"+CBC: 123.456V\r\r\n\r\nOK\r\n\n\r\n";
|
||||
} else if (command.find("AT+CPIN=1234\r") != std::string::npos) {
|
||||
response = "OK\r\n";
|
||||
@ -65,6 +71,8 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
|
||||
int LoopbackTerm::read(uint8_t *data, size_t len)
|
||||
{
|
||||
size_t read_len = std::min(data_len, len);
|
||||
if (inject_by && read_len > inject_by)
|
||||
read_len = inject_by;
|
||||
if (read_len) {
|
||||
if (loopback_data.capacity() < len) {
|
||||
loopback_data.reserve(len);
|
||||
@ -76,8 +84,30 @@ int LoopbackTerm::read(uint8_t *data, size_t len)
|
||||
return read_len;
|
||||
}
|
||||
|
||||
LoopbackTerm::LoopbackTerm(bool is_bg96): loopback_data(), data_len(0), pin_ok(false), is_bg96(is_bg96) {}
|
||||
LoopbackTerm::LoopbackTerm(bool is_bg96): loopback_data(), data_len(0), pin_ok(false), is_bg96(is_bg96), inject_by(0) {}
|
||||
|
||||
LoopbackTerm::LoopbackTerm(): loopback_data(), data_len(0), pin_ok(false), is_bg96(false) {}
|
||||
LoopbackTerm::LoopbackTerm(): loopback_data(), data_len(0), pin_ok(false), is_bg96(false), inject_by(0) {}
|
||||
|
||||
int LoopbackTerm::inject(uint8_t *data, size_t len, size_t injected_by)
|
||||
{
|
||||
if (data == nullptr) {
|
||||
inject_by = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
loopback_data.resize(len);
|
||||
memcpy(&loopback_data[0], data, len);
|
||||
data_len = len;
|
||||
inject_by = injected_by;
|
||||
return len;
|
||||
}
|
||||
|
||||
void LoopbackTerm::batch_read()
|
||||
{
|
||||
while (data_len > 0) {
|
||||
on_read(nullptr, std::min(inject_by, data_len));
|
||||
Task::Delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
LoopbackTerm::~LoopbackTerm() = default;
|
||||
|
@ -12,6 +12,13 @@ public:
|
||||
|
||||
~LoopbackTerm() override;
|
||||
|
||||
/**
|
||||
* @brief Inject user data to the terminal, to respond.
|
||||
* inject_by defines batch sizes: the read callback is called multiple times
|
||||
* with partial data of `inject_by` size
|
||||
*/
|
||||
int inject(uint8_t *data, size_t len, size_t inject_by);
|
||||
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
@ -24,10 +31,12 @@ private:
|
||||
STARTED,
|
||||
STOPPED
|
||||
};
|
||||
void batch_read();
|
||||
status_t status;
|
||||
SignalGroup signal;
|
||||
std::vector<uint8_t> loopback_data;
|
||||
size_t data_len;
|
||||
bool pin_ok;
|
||||
bool is_bg96;
|
||||
size_t inject_by;
|
||||
};
|
||||
|
@ -7,12 +7,27 @@
|
||||
|
||||
using namespace esp_modem;
|
||||
|
||||
TEST_CASE("Test polymorphic delete for custom device/dte", "[esp_modem]")
|
||||
{
|
||||
auto term = std::make_unique<LoopbackTerm>(true);
|
||||
auto dte = std::make_shared<DTE>(std::move(term));
|
||||
CHECK(term == nullptr);
|
||||
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("APN");
|
||||
|
||||
// Create custom device and DTE manually to check for a potential undefined behaviour
|
||||
auto device = new GenericModule(std::move(dte), &dce_config);
|
||||
device->power_down();
|
||||
delete device;
|
||||
auto custom_dte = new DTE(std::make_unique<LoopbackTerm>(false));
|
||||
custom_dte->command("AT", nullptr, 0);
|
||||
delete custom_dte;
|
||||
}
|
||||
|
||||
TEST_CASE("DCE AT parser", "[esp_modem]")
|
||||
{
|
||||
auto term = std::make_unique<LoopbackTerm>(true);
|
||||
auto dte = std::make_shared<DTE>(std::move(term));
|
||||
CHECK(term == nullptr);
|
||||
|
||||
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("APN");
|
||||
esp_netif_t netif{};
|
||||
auto dce = create_BG96_dce(&dce_config, dte, &netif);
|
||||
@ -24,7 +39,7 @@ TEST_CASE("DCE AT parser", "[esp_modem]")
|
||||
CHECK(dce->get_battery_status(milli_volt, bcl, bcs) == command_result::OK);
|
||||
CHECK(milli_volt == 123456);
|
||||
CHECK(bcl == 1);
|
||||
CHECK(bcs == 2);
|
||||
CHECK(bcs == 20);
|
||||
|
||||
int rssi, ber;
|
||||
CHECK(dce->get_signal_quality(rssi, ber) == command_result::OK);
|
||||
@ -37,6 +52,10 @@ TEST_CASE("DCE AT parser", "[esp_modem]")
|
||||
CHECK(dce->set_pin("1234") == command_result::OK);
|
||||
CHECK(dce->read_pin(pin_ok) == command_result::OK);
|
||||
CHECK(pin_ok == true);
|
||||
|
||||
std::string model;
|
||||
CHECK(dce->get_module_name(model) == command_result::OK);
|
||||
CHECK(model == "0G Dummy Model");
|
||||
}
|
||||
|
||||
|
||||
@ -109,6 +128,7 @@ TEST_CASE("DCE modes", "[esp_modem]")
|
||||
auto dce = create_SIM7600_dce(&dce_config, dte, &netif);
|
||||
CHECK(dce != nullptr);
|
||||
|
||||
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
|
||||
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == false);
|
||||
CHECK(dce->set_mode(esp_modem::modem_mode::DATA_MODE) == true);
|
||||
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
|
||||
@ -135,3 +155,47 @@ TEST_CASE("DCE CMUX test", "[esp_modem]")
|
||||
}, 1000);
|
||||
CHECK(ret == command_result::OK);
|
||||
}
|
||||
|
||||
TEST_CASE("Test CMUX protocol by injecting payloads", "[esp_modem]")
|
||||
{
|
||||
auto term = std::make_unique<LoopbackTerm>();
|
||||
auto loopback = term.get();
|
||||
auto dte = std::make_shared<DTE>(std::move(term));
|
||||
CHECK(term == nullptr);
|
||||
|
||||
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("APN");
|
||||
esp_netif_t netif{};
|
||||
auto dce = create_SIM7600_dce(&dce_config, dte, &netif);
|
||||
CHECK(dce != nullptr);
|
||||
|
||||
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MODE) == true);
|
||||
const auto test_command = "Test\n";
|
||||
// 1 byte payload size
|
||||
uint8_t test_payload[] = {0xf9, 0x05, 0xff, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x0a, 0xbb, 0xf9 };
|
||||
loopback->inject(&test_payload[0], sizeof(test_payload), 1);
|
||||
auto ret = dce->command(test_command, [&](uint8_t *data, size_t len) {
|
||||
std::string response((char *) data, len);
|
||||
CHECK(response == test_command);
|
||||
return command_result::OK;
|
||||
}, 1000);
|
||||
CHECK(ret == command_result::OK);
|
||||
|
||||
// 2 byte payload size
|
||||
uint8_t long_payload[453] = { 0xf9, 0x05, 0xef, 0x7c, 0x03, 0x7e }; // header
|
||||
long_payload[5] = 0x7e; // payload to validate
|
||||
long_payload[449] = 0x7e;
|
||||
long_payload[450] = '\n';
|
||||
long_payload[451] = 0x53; // footer
|
||||
long_payload[452] = 0xf9;
|
||||
for (int i=0; i<5; ++i) {
|
||||
// inject the whole payload (i=0) and then per 1,2,3,4 bytes (i)
|
||||
loopback->inject(&long_payload[0], sizeof(long_payload), i==0?sizeof(long_payload):i);
|
||||
auto ret = dce->command("ignore", [&](uint8_t *data, size_t len) {
|
||||
CHECK(data[0] == 0x7e);
|
||||
CHECK(data[len-2] == 0x7e);
|
||||
CHECK(data[len-1] == '\n');
|
||||
return command_result::OK;
|
||||
}, 1000);
|
||||
CHECK(ret == command_result::OK);
|
||||
}
|
||||
}
|
||||
|
@ -3,4 +3,8 @@ idf_component_register(SRCS "pppd_test.cpp"
|
||||
INCLUDE_DIRS "$ENV{IDF_PATH}/tools/catch"
|
||||
REQUIRES esp_modem)
|
||||
|
||||
target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17)
|
||||
set_target_properties(${COMPONENT_LIB} PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS ON
|
||||
)
|
||||
|
93
components/esp_websocket_client/.gitignore
vendored
Normal file
93
components/esp_websocket_client/.gitignore
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
.config
|
||||
*.o
|
||||
*.pyc
|
||||
|
||||
# gtags
|
||||
GTAGS
|
||||
GRTAGS
|
||||
GPATH
|
||||
|
||||
# emacs
|
||||
.dir-locals.el
|
||||
|
||||
# emacs temp file suffixes
|
||||
*~
|
||||
.#*
|
||||
\#*#
|
||||
|
||||
# eclipse setting
|
||||
.settings
|
||||
|
||||
# MacOS directory files
|
||||
.DS_Store
|
||||
|
||||
# Components Unit Test Apps files
|
||||
components/**/build
|
||||
components/**/sdkconfig
|
||||
components/**/sdkconfig.old
|
||||
|
||||
# Example project files
|
||||
examples/**/sdkconfig
|
||||
examples/**/sdkconfig.old
|
||||
examples/**/build
|
||||
|
||||
# Doc build artifacts
|
||||
docs/_build/
|
||||
docs/doxygen_sqlite3.db
|
||||
|
||||
# Downloaded font files
|
||||
docs/_static/DejaVuSans.ttf
|
||||
docs/_static/NotoSansSC-Regular.otf
|
||||
|
||||
# Unit test app files
|
||||
tools/unit-test-app/sdkconfig
|
||||
tools/unit-test-app/sdkconfig.old
|
||||
tools/unit-test-app/build
|
||||
tools/unit-test-app/builds
|
||||
tools/unit-test-app/output
|
||||
tools/unit-test-app/test_configs
|
||||
|
||||
# Unit Test CMake compile log folder
|
||||
log_ut_cmake
|
||||
|
||||
# test application build files
|
||||
test/**/build
|
||||
test/**/sdkconfig
|
||||
test/**/sdkconfig.old
|
||||
|
||||
# IDF monitor test
|
||||
tools/test_idf_monitor/outputs
|
||||
|
||||
TEST_LOGS
|
||||
|
||||
# gcov coverage reports
|
||||
*.gcda
|
||||
*.gcno
|
||||
coverage.info
|
||||
coverage_report/
|
||||
|
||||
test_multi_heap_host
|
||||
|
||||
# VS Code Settings
|
||||
.vscode/
|
||||
|
||||
# VIM files
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Clion IDE CMake build & config
|
||||
.idea/
|
||||
cmake-build-*/
|
||||
|
||||
# Results for the checking of the Python coding style and static analysis
|
||||
.mypy_cache
|
||||
flake8_output.txt
|
||||
|
||||
# ESP-IDF default build directory name
|
||||
build
|
||||
|
||||
# lock files for examples and components
|
||||
dependencies.lock
|
||||
|
||||
# ignore generated docs
|
||||
docs/html
|
12
components/esp_websocket_client/CMakeLists.txt
Normal file
12
components/esp_websocket_client/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
if(NOT CONFIG_WS_TRANSPORT AND NOT CMAKE_BUILD_EARLY_EXPANSION)
|
||||
message(STATUS "Websocket transport is disabled so the esp_websocket_client component will not be built")
|
||||
# note: the component is still included in the build so it can become visible again in config
|
||||
# without needing to re-run CMake. However no source or header files are built.
|
||||
idf_component_register()
|
||||
return()
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS "esp_websocket_client.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES lwip esp-tls tcp_transport http_parser
|
||||
PRIV_REQUIRES esp_timer)
|
202
components/esp_websocket_client/LICENSE
Normal file
202
components/esp_websocket_client/LICENSE
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
11
components/esp_websocket_client/README.md
Normal file
11
components/esp_websocket_client/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# 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
|
||||
|
||||
Get started with example test [Example](examples/README.md):
|
||||
|
||||
## Documentation
|
||||
|
||||
* View the full [html documentation](https://espressif.github.io/esp-protocols/esp_websocket_client/index.html)
|
75
components/esp_websocket_client/docs/Doxyfile
Normal file
75
components/esp_websocket_client/docs/Doxyfile
Normal file
@ -0,0 +1,75 @@
|
||||
# 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 = "ESP Protocols Programming Guide"
|
||||
|
||||
## 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_websocket_client.h
|
||||
|
||||
## 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
|
||||
GENERATE_RTF = NO
|
||||
|
||||
## 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.
|
||||
DOC_SINGLE_GROUP ## To conditionally remove groups from the documentation and create a 'flat' document without affecting documentation in upstream files.
|
21
components/esp_websocket_client/docs/conf_common.py
Normal file
21
components/esp_websocket_client/docs/conf_common.py
Normal file
@ -0,0 +1,21 @@
|
||||
from esp_docs.conf_docs import * # noqa: F403,F401
|
||||
|
||||
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']
|
24
components/esp_websocket_client/docs/en/conf.py
Normal file
24
components/esp_websocket_client/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 - 2022, Espressif Systems (Shanghai) Co., Ltd'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
language = 'en'
|
127
components/esp_websocket_client/docs/en/index.rst
Normal file
127
components/esp_websocket_client/docs/en/index.rst
Normal file
@ -0,0 +1,127 @@
|
||||
ESP WebSocket Client
|
||||
====================
|
||||
|
||||
Overview
|
||||
--------
|
||||
The ESP WebSocket client is an implementation of `WebSocket protocol client <https://tools.ietf.org/html/rfc6455>`_ for {IDF_TARGET_NAME}
|
||||
|
||||
Features
|
||||
--------
|
||||
* Supports WebSocket over TCP, TLS with mbedtls
|
||||
* Easy to setup with URI
|
||||
* Multiple instances (Multiple clients in one application)
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
URI
|
||||
^^^
|
||||
|
||||
- Supports ``ws``, ``wss`` schemes
|
||||
- WebSocket samples:
|
||||
|
||||
- ``ws://echo.websocket.org``: WebSocket over TCP, default port 80
|
||||
- ``wss://echo.websocket.org``: WebSocket over SSL, default port 443
|
||||
|
||||
Minimal configurations:
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "ws://echo.websocket.org",
|
||||
};
|
||||
|
||||
The WebSocket client supports the use of both path and query in the URI. Sample:
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "ws://echo.websocket.org/connectionhandler?id=104",
|
||||
};
|
||||
|
||||
|
||||
If there are any options related to the URI in
|
||||
:cpp:type:`esp_websocket_client_config_t`, the option defined by the URI will be
|
||||
overridden. Sample:
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "ws://echo.websocket.org:123",
|
||||
.port = 4567,
|
||||
};
|
||||
//WebSocket client will connect to websocket.org using port 4567
|
||||
|
||||
TLS
|
||||
^^^
|
||||
|
||||
Configuration:
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "wss://echo.websocket.org",
|
||||
.cert_pem = (const char *)websocket_org_pem_start,
|
||||
};
|
||||
|
||||
.. note:: If you want to verify the server, then you need to provide a certificate in PEM format, and provide to ``cert_pem`` in :cpp:type:`websocket_client_config_t`. If no certficate is provided then the TLS connection will default to not requiring verification.
|
||||
|
||||
PEM certificate for this example could be extracted from an openssl `s_client` command connecting to websocket.org.
|
||||
In case a host operating system has `openssl` and `sed` packages installed, one could execute the following command to download and save the root or intermediate root certificate to a file (Note for Windows users: Both Linux like environment or Windows native packages may be used).
|
||||
```
|
||||
echo "" | openssl s_client -showcerts -connect websocket.org:443 | sed -n "1,/Root/d; /BEGIN/,/END/p" | openssl x509 -outform PEM >websocket_org.pem
|
||||
```
|
||||
|
||||
This command will extract the second certificate in the chain and save it as a pem-file.
|
||||
|
||||
Subprotocol
|
||||
^^^^^^^^^^^
|
||||
|
||||
The subprotocol field in the config struct can be used to request a subprotocol
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "ws://websocket.org",
|
||||
.subprotocol = "soap",
|
||||
};
|
||||
|
||||
.. note:: The client is indifferent to the subprotocol field in the server response and will accept the connection no matter what the server replies.
|
||||
|
||||
For more options on :cpp:type:`esp_websocket_client_config_t`, please refer to API reference below
|
||||
|
||||
Events
|
||||
------
|
||||
* `WEBSOCKET_EVENT_CONNECTED`: The client has successfully established a connection to the server. The client is now ready to send and receive data. Contains no event data.
|
||||
* `WEBSOCKET_EVENT_DISCONNECTED`: The client has aborted the connection due to the transport layer failing to read data, e.g. because the server is unavailable. Contains no event data.
|
||||
* `WEBSOCKET_EVENT_DATA`: The client has successfully received and parsed a WebSocket frame. The event data contains a pointer to the payload data, the length of the payload data as well as the opcode of the received frame. A message may be fragmented into multiple events if the length exceeds the buffer size. This event will also be posted for non-payload frames, e.g. pong or connection close frames.
|
||||
* `WEBSOCKET_EVENT_ERROR`: Not used in the current implementation of the client.
|
||||
|
||||
If the client handle is needed in the event handler it can be accessed through the pointer passed to the event handler:
|
||||
|
||||
.. code:: c
|
||||
|
||||
esp_websocket_client_handle_t client = (esp_websocket_client_handle_t)handler_args;
|
||||
|
||||
|
||||
Limitations and Known Issues
|
||||
----------------------------
|
||||
* The client is able to request the use of a subprotocol from the server during the handshake, but does not do any subprotocol related checks on the response from the server.
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
A simple WebSocket example that uses esp_websocket_client to establish a websocket connection and send/receive data with the `websocket.org <https://websocket.org>`_ server can be found here: :example:`example <../examples>`.
|
||||
|
||||
Sending Text Data
|
||||
^^^^^^^^^^^^^^^^^
|
||||
The WebSocket client supports sending data as a text data frame, which informs the application layer that the payload data is text data encoded as UTF-8. Example:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_websocket_client.inc
|
||||
|
27
components/esp_websocket_client/docs/generate_docs
Executable file
27
components/esp_websocket_client/docs/generate_docs
Executable file
@ -0,0 +1,27 @@
|
||||
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
|
||||
|
961
components/esp_websocket_client/esp_websocket_client.c
Normal file
961
components/esp_websocket_client/esp_websocket_client.c
Normal file
@ -0,0 +1,961 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_websocket_client.h"
|
||||
#include "esp_transport.h"
|
||||
#include "esp_transport_tcp.h"
|
||||
#include "esp_transport_ssl.h"
|
||||
#include "esp_transport_ws.h"
|
||||
/* using uri parser */
|
||||
#include "http_parser.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
static const char *TAG = "WEBSOCKET_CLIENT";
|
||||
|
||||
#define WEBSOCKET_TCP_DEFAULT_PORT (80)
|
||||
#define WEBSOCKET_SSL_DEFAULT_PORT (443)
|
||||
#define WEBSOCKET_BUFFER_SIZE_BYTE (1024)
|
||||
#define WEBSOCKET_RECONNECT_TIMEOUT_MS (10*1000)
|
||||
#define WEBSOCKET_TASK_PRIORITY (5)
|
||||
#define WEBSOCKET_TASK_STACK (4*1024)
|
||||
#define WEBSOCKET_NETWORK_TIMEOUT_MS (10*1000)
|
||||
#define WEBSOCKET_PING_INTERVAL_SEC (10)
|
||||
#define WEBSOCKET_EVENT_QUEUE_SIZE (1)
|
||||
#define WEBSOCKET_PINGPONG_TIMEOUT_SEC (120)
|
||||
#define WEBSOCKET_KEEP_ALIVE_IDLE (5)
|
||||
#define WEBSOCKET_KEEP_ALIVE_INTERVAL (5)
|
||||
#define WEBSOCKET_KEEP_ALIVE_COUNT (3)
|
||||
|
||||
#define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \
|
||||
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted"); \
|
||||
action; \
|
||||
}
|
||||
|
||||
#define ESP_WS_CLIENT_ERR_OK_CHECK(TAG, err, action) { \
|
||||
esp_err_t _esp_ws_err_to_check = err; \
|
||||
if (_esp_ws_err_to_check != ESP_OK) { \
|
||||
ESP_LOGE(TAG,"%s(%d): Expected ESP_OK; reported: %d", __FUNCTION__, __LINE__, _esp_ws_err_to_check); \
|
||||
action; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ESP_WS_CLIENT_STATE_CHECK(TAG, a, action) if ((a->state) < WEBSOCKET_STATE_INIT) { \
|
||||
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Websocket already stop"); \
|
||||
action; \
|
||||
}
|
||||
|
||||
const static int STOPPED_BIT = BIT0;
|
||||
const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame was sent by the client
|
||||
// and we are waiting for the server to continue with clean close
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS);
|
||||
|
||||
typedef struct {
|
||||
int task_stack;
|
||||
int task_prio;
|
||||
char *uri;
|
||||
char *host;
|
||||
char *path;
|
||||
char *scheme;
|
||||
char *username;
|
||||
char *password;
|
||||
int port;
|
||||
bool auto_reconnect;
|
||||
void *user_context;
|
||||
int network_timeout_ms;
|
||||
char *subprotocol;
|
||||
char *user_agent;
|
||||
char *headers;
|
||||
int pingpong_timeout_sec;
|
||||
size_t ping_interval_sec;
|
||||
} websocket_config_storage_t;
|
||||
|
||||
typedef enum {
|
||||
WEBSOCKET_STATE_ERROR = -1,
|
||||
WEBSOCKET_STATE_UNKNOW = 0,
|
||||
WEBSOCKET_STATE_INIT,
|
||||
WEBSOCKET_STATE_CONNECTED,
|
||||
WEBSOCKET_STATE_WAIT_TIMEOUT,
|
||||
WEBSOCKET_STATE_CLOSING,
|
||||
} websocket_client_state_t;
|
||||
|
||||
struct esp_websocket_client {
|
||||
esp_event_loop_handle_t event_handle;
|
||||
TaskHandle_t task_handle;
|
||||
esp_transport_list_handle_t transport_list;
|
||||
esp_transport_handle_t transport;
|
||||
websocket_config_storage_t *config;
|
||||
websocket_client_state_t state;
|
||||
uint64_t keepalive_tick_ms;
|
||||
uint64_t reconnect_tick_ms;
|
||||
uint64_t ping_tick_ms;
|
||||
uint64_t pingpong_tick_ms;
|
||||
int wait_timeout_ms;
|
||||
int auto_reconnect;
|
||||
bool run;
|
||||
bool wait_for_pong_resp;
|
||||
EventGroupHandle_t status_bits;
|
||||
SemaphoreHandle_t lock;
|
||||
char *rx_buffer;
|
||||
char *tx_buffer;
|
||||
int buffer_size;
|
||||
bool last_fin;
|
||||
ws_transport_opcodes_t last_opcode;
|
||||
int payload_len;
|
||||
int payload_offset;
|
||||
esp_transport_keep_alive_t keep_alive_cfg;
|
||||
struct ifreq *if_name;
|
||||
};
|
||||
|
||||
static uint64_t _tick_get_ms(void)
|
||||
{
|
||||
return esp_timer_get_time()/1000;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle_t client,
|
||||
esp_websocket_event_id_t event,
|
||||
const char *data,
|
||||
int data_len)
|
||||
{
|
||||
esp_err_t err;
|
||||
esp_websocket_event_data_t event_data;
|
||||
|
||||
event_data.client = client;
|
||||
event_data.user_context = client->config->user_context;
|
||||
event_data.data_ptr = data;
|
||||
event_data.data_len = data_len;
|
||||
event_data.fin = client->last_fin;
|
||||
event_data.op_code = client->last_opcode;
|
||||
event_data.payload_len = client->payload_len;
|
||||
event_data.payload_offset = client->payload_offset;
|
||||
|
||||
if ((err = esp_event_post_to(client->event_handle,
|
||||
WEBSOCKET_EVENTS, event,
|
||||
&event_data,
|
||||
sizeof(esp_websocket_event_data_t),
|
||||
portMAX_DELAY)) != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
return esp_event_loop_run(client->event_handle, 0);
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client)
|
||||
{
|
||||
ESP_WS_CLIENT_STATE_CHECK(TAG, client, return ESP_FAIL);
|
||||
esp_transport_close(client->transport);
|
||||
|
||||
if (client->config->auto_reconnect) {
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms);
|
||||
}
|
||||
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)
|
||||
{
|
||||
ESP_WS_CLIENT_STATE_CHECK(TAG, client, return ESP_FAIL);
|
||||
esp_transport_close(client->transport);
|
||||
|
||||
if (client->config->auto_reconnect) {
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms);
|
||||
}
|
||||
client->state = WEBSOCKET_STATE_WAIT_TIMEOUT;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_ERROR, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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;
|
||||
cfg->task_prio = config->task_prio;
|
||||
if (cfg->task_prio <= 0) {
|
||||
cfg->task_prio = WEBSOCKET_TASK_PRIORITY;
|
||||
}
|
||||
|
||||
cfg->task_stack = config->task_stack;
|
||||
if (cfg->task_stack == 0) {
|
||||
cfg->task_stack = WEBSOCKET_TASK_STACK;
|
||||
}
|
||||
|
||||
if (config->host) {
|
||||
cfg->host = strdup(config->host);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->host, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (config->port) {
|
||||
cfg->port = config->port;
|
||||
}
|
||||
|
||||
if (config->username) {
|
||||
free(cfg->username);
|
||||
cfg->username = strdup(config->username);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->username, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (config->password) {
|
||||
free(cfg->password);
|
||||
cfg->password = strdup(config->password);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->password, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (config->uri) {
|
||||
free(cfg->uri);
|
||||
cfg->uri = strdup(config->uri);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->uri, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (config->path) {
|
||||
free(cfg->path);
|
||||
cfg->path = strdup(config->path);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->path, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (config->subprotocol) {
|
||||
free(cfg->subprotocol);
|
||||
cfg->subprotocol = strdup(config->subprotocol);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->subprotocol, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (config->user_agent) {
|
||||
free(cfg->user_agent);
|
||||
cfg->user_agent = strdup(config->user_agent);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->user_agent, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (config->headers) {
|
||||
free(cfg->headers);
|
||||
cfg->headers = strdup(config->headers);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->headers, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
|
||||
cfg->user_context = config->user_context;
|
||||
cfg->auto_reconnect = true;
|
||||
if (config->disable_auto_reconnect) {
|
||||
cfg->auto_reconnect = false;
|
||||
}
|
||||
|
||||
if (config->disable_pingpong_discon){
|
||||
cfg->pingpong_timeout_sec = 0;
|
||||
} else if (config->pingpong_timeout_sec) {
|
||||
cfg->pingpong_timeout_sec = config->pingpong_timeout_sec;
|
||||
} else {
|
||||
cfg->pingpong_timeout_sec = WEBSOCKET_PINGPONG_TIMEOUT_SEC;
|
||||
}
|
||||
|
||||
if (config->network_timeout_ms <= 0) {
|
||||
cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS;
|
||||
ESP_LOGW(TAG, "`network_timeout_ms` is not set, or it is less than or equal to zero, using default time out %d (milliseconds)", WEBSOCKET_NETWORK_TIMEOUT_MS);
|
||||
} else {
|
||||
cfg->network_timeout_ms = config->network_timeout_ms;
|
||||
}
|
||||
|
||||
if (config->ping_interval_sec == 0) {
|
||||
cfg->ping_interval_sec = WEBSOCKET_PING_INTERVAL_SEC;
|
||||
} else {
|
||||
cfg->ping_interval_sec = config->ping_interval_sec;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
websocket_config_storage_t *cfg = client->config;
|
||||
if (client->config == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
free(cfg->host);
|
||||
free(cfg->uri);
|
||||
free(cfg->path);
|
||||
free(cfg->scheme);
|
||||
free(cfg->username);
|
||||
free(cfg->password);
|
||||
free(cfg->subprotocol);
|
||||
free(cfg->user_agent);
|
||||
free(cfg->headers);
|
||||
memset(cfg, 0, sizeof(websocket_config_storage_t));
|
||||
free(client->config);
|
||||
client->config = NULL;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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);
|
||||
if (trans) {
|
||||
const esp_transport_ws_config_t config = {
|
||||
.ws_path = client->config->path,
|
||||
.sub_protocol = client->config->subprotocol,
|
||||
.user_agent = client->config->user_agent,
|
||||
.headers = client->config->headers,
|
||||
.propagate_control_frames = true
|
||||
};
|
||||
return esp_transport_ws_set_config(trans, &config);
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config)
|
||||
{
|
||||
esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client));
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client, return NULL);
|
||||
|
||||
esp_event_loop_args_t event_args = {
|
||||
.queue_size = WEBSOCKET_EVENT_QUEUE_SIZE,
|
||||
.task_name = NULL // no task will be created
|
||||
};
|
||||
|
||||
if (esp_event_loop_create(&event_args, &client->event_handle) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error create event handler for websocket client");
|
||||
free(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (config->keep_alive_enable == true) {
|
||||
client->keep_alive_cfg.keep_alive_enable = true;
|
||||
client->keep_alive_cfg.keep_alive_idle = (config->keep_alive_idle == 0) ? WEBSOCKET_KEEP_ALIVE_IDLE : config->keep_alive_idle;
|
||||
client->keep_alive_cfg.keep_alive_interval = (config->keep_alive_interval == 0) ? WEBSOCKET_KEEP_ALIVE_INTERVAL : config->keep_alive_interval;
|
||||
client->keep_alive_cfg.keep_alive_count = (config->keep_alive_count == 0) ? WEBSOCKET_KEEP_ALIVE_COUNT : config->keep_alive_count;
|
||||
}
|
||||
|
||||
if (config->if_name) {
|
||||
client->if_name = calloc(1, sizeof(struct ifreq) + 1);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->if_name, goto _websocket_init_fail);
|
||||
memcpy(client->if_name, config->if_name, sizeof(struct ifreq));
|
||||
}
|
||||
|
||||
client->lock = xSemaphoreCreateRecursiveMutex();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail);
|
||||
|
||||
client->config = calloc(1, sizeof(websocket_config_storage_t));
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail);
|
||||
|
||||
client->transport_list = esp_transport_list_init();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->transport_list, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT);
|
||||
esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup
|
||||
esp_transport_tcp_set_keep_alive(tcp, &client->keep_alive_cfg);
|
||||
esp_transport_tcp_set_interface_name(tcp, client->if_name);
|
||||
|
||||
esp_transport_handle_t ws = esp_transport_ws_init(tcp);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, ws, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_set_default_port(ws, WEBSOCKET_TCP_DEFAULT_PORT);
|
||||
esp_transport_list_add(client->transport_list, ws, "ws");
|
||||
if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) {
|
||||
asprintf(&client->config->scheme, "ws");
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
|
||||
}
|
||||
|
||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, ssl, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_set_default_port(ssl, WEBSOCKET_SSL_DEFAULT_PORT);
|
||||
esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup
|
||||
if (config->use_global_ca_store == true) {
|
||||
esp_transport_ssl_enable_global_ca_store(ssl);
|
||||
} else if (config->cert_pem) {
|
||||
if (!config->cert_len) {
|
||||
esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem));
|
||||
} else {
|
||||
esp_transport_ssl_set_cert_data_der(ssl, config->cert_pem, config->cert_len);
|
||||
}
|
||||
}
|
||||
if (config->client_cert) {
|
||||
if (!config->client_cert_len) {
|
||||
esp_transport_ssl_set_client_cert_data(ssl, config->client_cert, strlen(config->client_cert));
|
||||
} else {
|
||||
esp_transport_ssl_set_client_cert_data_der(ssl, config->client_cert, config->client_cert_len);
|
||||
}
|
||||
}
|
||||
if (config->client_key) {
|
||||
if (!config->client_key_len) {
|
||||
esp_transport_ssl_set_client_key_data(ssl, config->client_key, strlen(config->client_key));
|
||||
} else {
|
||||
esp_transport_ssl_set_client_key_data_der(ssl, config->client_key, config->client_key_len);
|
||||
}
|
||||
}
|
||||
if (config->skip_cert_common_name_check) {
|
||||
esp_transport_ssl_skip_common_name_check(ssl);
|
||||
}
|
||||
if (config->reconnect_timeout_ms <= 0) {
|
||||
client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS;
|
||||
ESP_LOGW(TAG, "`reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out %d (milliseconds)", WEBSOCKET_RECONNECT_TIMEOUT_MS);
|
||||
} else {
|
||||
client->wait_timeout_ms = config->reconnect_timeout_ms;
|
||||
}
|
||||
esp_transport_handle_t wss = esp_transport_ws_init(ssl);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, wss, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_set_default_port(wss, WEBSOCKET_SSL_DEFAULT_PORT);
|
||||
|
||||
esp_transport_list_add(client->transport_list, wss, "wss");
|
||||
if (config->transport == WEBSOCKET_TRANSPORT_OVER_SSL) {
|
||||
asprintf(&client->config->scheme, "wss");
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
|
||||
}
|
||||
|
||||
if (config->uri) {
|
||||
if (esp_websocket_client_set_uri(client, config->uri) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Invalid uri");
|
||||
goto _websocket_init_fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (esp_websocket_client_set_config(client, config) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set the configuration");
|
||||
goto _websocket_init_fail;
|
||||
}
|
||||
|
||||
if (client->config->scheme == NULL) {
|
||||
asprintf(&client->config->scheme, "ws");
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
|
||||
}
|
||||
|
||||
ESP_WS_CLIENT_ERR_OK_CHECK(TAG, set_websocket_transport_optional_settings(client, "ws"), goto _websocket_init_fail;)
|
||||
ESP_WS_CLIENT_ERR_OK_CHECK(TAG, set_websocket_transport_optional_settings(client, "wss"), goto _websocket_init_fail;)
|
||||
|
||||
client->keepalive_tick_ms = _tick_get_ms();
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
client->ping_tick_ms = _tick_get_ms();
|
||||
client->wait_for_pong_resp = false;
|
||||
|
||||
int buffer_size = config->buffer_size;
|
||||
if (buffer_size <= 0) {
|
||||
buffer_size = WEBSOCKET_BUFFER_SIZE_BYTE;
|
||||
}
|
||||
client->rx_buffer = malloc(buffer_size);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->rx_buffer, {
|
||||
goto _websocket_init_fail;
|
||||
});
|
||||
client->tx_buffer = malloc(buffer_size);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->tx_buffer, {
|
||||
goto _websocket_init_fail;
|
||||
});
|
||||
client->status_bits = xEventGroupCreate();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->status_bits, {
|
||||
goto _websocket_init_fail;
|
||||
});
|
||||
|
||||
client->buffer_size = buffer_size;
|
||||
return client;
|
||||
|
||||
_websocket_init_fail:
|
||||
esp_websocket_client_destroy(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (client->run) {
|
||||
esp_websocket_client_stop(client);
|
||||
}
|
||||
if (client->event_handle) {
|
||||
esp_event_loop_delete(client->event_handle);
|
||||
}
|
||||
if (client->if_name) {
|
||||
free(client->if_name);
|
||||
}
|
||||
esp_websocket_client_destroy_config(client);
|
||||
esp_transport_list_destroy(client->transport_list);
|
||||
vQueueDelete(client->lock);
|
||||
free(client->tx_buffer);
|
||||
free(client->rx_buffer);
|
||||
if (client->status_bits) {
|
||||
vEventGroupDelete(client->status_bits);
|
||||
}
|
||||
free(client);
|
||||
client = NULL;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, const char *uri)
|
||||
{
|
||||
if (client == NULL || uri == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
struct http_parser_url puri;
|
||||
http_parser_url_init(&puri);
|
||||
int parser_status = http_parser_parse_url(uri, strlen(uri), 0, &puri);
|
||||
if (parser_status != 0) {
|
||||
ESP_LOGE(TAG, "Error parse uri = %s", uri);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (puri.field_data[UF_SCHEMA].len) {
|
||||
free(client->config->scheme);
|
||||
asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (puri.field_data[UF_HOST].len) {
|
||||
free(client->config->host);
|
||||
asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->host, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
|
||||
if (puri.field_data[UF_PATH].len || puri.field_data[UF_QUERY].len) {
|
||||
free(client->config->path);
|
||||
if (puri.field_data[UF_QUERY].len == 0) {
|
||||
asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off);
|
||||
} else if (puri.field_data[UF_PATH].len == 0) {
|
||||
asprintf(&client->config->path, "/?%.*s", puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
|
||||
} else {
|
||||
asprintf(&client->config->path, "%.*s?%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off,
|
||||
puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
|
||||
}
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (puri.field_data[UF_PORT].off) {
|
||||
client->config->port = strtol((const char*)(uri + puri.field_data[UF_PORT].off), NULL, 10);
|
||||
}
|
||||
|
||||
if (puri.field_data[UF_USERINFO].len) {
|
||||
char *user_info = NULL;
|
||||
asprintf(&user_info, "%.*s", puri.field_data[UF_USERINFO].len, uri + puri.field_data[UF_USERINFO].off);
|
||||
if (user_info) {
|
||||
char *pass = strchr(user_info, ':');
|
||||
if (pass) {
|
||||
pass[0] = 0; //terminal username
|
||||
pass ++;
|
||||
free(client->config->password);
|
||||
client->config->password = strdup(pass);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->password, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
free(client->config->username);
|
||||
client->config->username = strdup(user_info);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->username, return ESP_ERR_NO_MEM);
|
||||
free(user_info);
|
||||
} else {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
|
||||
{
|
||||
int rlen;
|
||||
client->payload_offset = 0;
|
||||
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");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
client->payload_len = esp_transport_ws_get_read_payload_len(client->transport);
|
||||
client->last_fin = esp_transport_ws_get_fin_flag(client->transport);
|
||||
client->last_opcode = esp_transport_ws_get_read_opcode(client->transport);
|
||||
|
||||
if (rlen == 0 && client->last_opcode == WS_TRANSPORT_OPCODES_NONE ) {
|
||||
ESP_LOGV(TAG, "esp_transport_read timeouts");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen);
|
||||
|
||||
client->payload_offset += rlen;
|
||||
} while (client->payload_offset < client->payload_len);
|
||||
|
||||
// if a PING message received -> send out the PONG, this will not work for PING messages with payload longer than buffer len
|
||||
if (client->last_opcode == WS_TRANSPORT_OPCODES_PING) {
|
||||
const char *data = (client->payload_len == 0) ? NULL : client->rx_buffer;
|
||||
ESP_LOGD(TAG, "Sending PONG with payload len=%d", client->payload_len);
|
||||
esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len,
|
||||
client->config->network_timeout_ms);
|
||||
} else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) {
|
||||
client->wait_for_pong_resp = false;
|
||||
} else if (client->last_opcode == WS_TRANSPORT_OPCODES_CLOSE) {
|
||||
ESP_LOGD(TAG, "Received close frame");
|
||||
client->state = WEBSOCKET_STATE_CLOSING;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const uint8_t *data, int len, TickType_t timeout);
|
||||
|
||||
static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout);
|
||||
|
||||
static void esp_websocket_client_task(void *pv)
|
||||
{
|
||||
const int lock_timeout = portMAX_DELAY;
|
||||
esp_websocket_client_handle_t client = (esp_websocket_client_handle_t) pv;
|
||||
client->run = true;
|
||||
|
||||
//get transport by scheme
|
||||
client->transport = esp_transport_list_get_transport(client->transport_list, client->config->scheme);
|
||||
|
||||
if (client->transport == NULL) {
|
||||
ESP_LOGE(TAG, "There are no transports valid, stop websocket client");
|
||||
client->run = false;
|
||||
}
|
||||
//default port
|
||||
if (client->config->port == 0) {
|
||||
client->config->port = esp_transport_get_default_port(client->transport);
|
||||
}
|
||||
|
||||
client->state = WEBSOCKET_STATE_INIT;
|
||||
xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
|
||||
int read_select = 0;
|
||||
while (client->run) {
|
||||
if (xSemaphoreTakeRecursive(client->lock, lock_timeout) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to lock ws-client tasks, exiting the task...");
|
||||
break;
|
||||
}
|
||||
switch ((int)client->state) {
|
||||
case WEBSOCKET_STATE_INIT:
|
||||
if (client->transport == NULL) {
|
||||
ESP_LOGE(TAG, "There are no transport");
|
||||
client->run = false;
|
||||
break;
|
||||
}
|
||||
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);
|
||||
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;
|
||||
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
|
||||
// if closing hasn't been initiated
|
||||
if (_tick_get_ms() - client->ping_tick_ms > client->config->ping_interval_sec*1000) {
|
||||
client->ping_tick_ms = _tick_get_ms();
|
||||
ESP_LOGD(TAG, "Sending PING...");
|
||||
esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms);
|
||||
|
||||
if (!client->wait_for_pong_resp && client->config->pingpong_timeout_sec) {
|
||||
client->pingpong_tick_ms = _tick_get_ms();
|
||||
client->wait_for_pong_resp = true;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (read_select == 0) {
|
||||
ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_read()...");
|
||||
break;
|
||||
}
|
||||
client->ping_tick_ms = _tick_get_ms();
|
||||
|
||||
if (esp_websocket_client_recv(client) == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Error receive data");
|
||||
esp_websocket_client_abort_connection(client);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case WEBSOCKET_STATE_WAIT_TIMEOUT:
|
||||
|
||||
if (!client->config->auto_reconnect) {
|
||||
client->run = false;
|
||||
break;
|
||||
}
|
||||
if (_tick_get_ms() - client->reconnect_tick_ms > client->wait_timeout_ms) {
|
||||
client->state = WEBSOCKET_STATE_INIT;
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
ESP_LOGD(TAG, "Reconnecting...");
|
||||
}
|
||||
break;
|
||||
case WEBSOCKET_STATE_CLOSING:
|
||||
// if closing not initiated by the client echo the close message back
|
||||
if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) {
|
||||
ESP_LOGD(TAG, "Closing initiated by the server, sending close frame");
|
||||
esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_CLOSE | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms);
|
||||
xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGD(TAG, "Client run iteration in a default state: %d", client->state);
|
||||
break;
|
||||
}
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
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);
|
||||
}
|
||||
} else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) {
|
||||
// waiting for reconnecting...
|
||||
vTaskDelay(client->wait_timeout_ms / 2 / portTICK_PERIOD_MS);
|
||||
} else if (WEBSOCKET_STATE_CLOSING == client->state &&
|
||||
(CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits))) {
|
||||
ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server");
|
||||
int ret = esp_transport_ws_poll_connection_closed(client->transport, 1000);
|
||||
if (ret == 0) {
|
||||
// still waiting
|
||||
break;
|
||||
}
|
||||
if (ret < 0) {
|
||||
ESP_LOGW(TAG, "Connection terminated while waiting for clean TCP close");
|
||||
}
|
||||
client->run = false;
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CLOSED, NULL, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_transport_close(client->transport);
|
||||
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (client->state >= WEBSOCKET_STATE_INIT) {
|
||||
ESP_LOGE(TAG, "The client has started");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (xTaskCreate(esp_websocket_client_task, "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;
|
||||
}
|
||||
xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!client->run) {
|
||||
ESP_LOGW(TAG, "Client was not started");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* A running client cannot be stopped from the websocket task/event handler */
|
||||
TaskHandle_t running_task = xTaskGetCurrentTaskHandle();
|
||||
if (running_task == client->task_handle) {
|
||||
ESP_LOGE(TAG, "Client cannot be stopped from websocket task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
|
||||
client->run = false;
|
||||
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout)
|
||||
{
|
||||
uint8_t *close_status_data = NULL;
|
||||
// RFC6455#section-5.5.1: The Close frame MAY contain a body (indicated by total_len >= 2)
|
||||
if (total_len >= 2) {
|
||||
close_status_data = calloc(1, total_len);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, close_status_data, return -1);
|
||||
// RFC6455#section-5.5.1: The first two bytes of the body MUST be a 2-byte representing a status
|
||||
uint16_t *code_network_order = (uint16_t *) close_status_data;
|
||||
*code_network_order = htons(code);
|
||||
memcpy(close_status_data + 2, additional_data, total_len - 2);
|
||||
}
|
||||
int ret = esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_CLOSE, close_status_data, total_len, timeout);
|
||||
free(close_status_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t esp_websocket_client_close_with_optional_body(esp_websocket_client_handle_t client, bool send_body, int code, const char *data, int len, TickType_t timeout)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!client->run) {
|
||||
ESP_LOGW(TAG, "Client was not started");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* A running client cannot be stopped from the websocket task/event handler */
|
||||
TaskHandle_t running_task = xTaskGetCurrentTaskHandle();
|
||||
if (running_task == client->task_handle) {
|
||||
ESP_LOGE(TAG, "Client cannot be stopped from websocket task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (send_body) {
|
||||
esp_websocket_client_send_close(client, code, data, len + 2, portMAX_DELAY); // len + 2 -> always sending the code
|
||||
} else {
|
||||
esp_websocket_client_send_close(client, 0, NULL, 0, portMAX_DELAY); // only opcode frame
|
||||
}
|
||||
|
||||
// Set closing bit to prevent from sending PING frames while connected
|
||||
xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT);
|
||||
|
||||
if (STOPPED_BIT & xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, timeout)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// If could not close gracefully within timeout, stop the client and disconnect
|
||||
client->run = false;
|
||||
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_close_with_code(esp_websocket_client_handle_t client, int code, const char *data, int len, TickType_t timeout)
|
||||
{
|
||||
return esp_websocket_client_close_with_optional_body(client, true, code, data, len, timeout);
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickType_t timeout)
|
||||
{
|
||||
return esp_websocket_client_close_with_optional_body(client, false, 0, NULL, 0, timeout);
|
||||
}
|
||||
|
||||
int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
|
||||
{
|
||||
return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, (const uint8_t *)data, len, timeout);
|
||||
}
|
||||
|
||||
int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
|
||||
{
|
||||
return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout);
|
||||
}
|
||||
|
||||
static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const uint8_t *data, int len, TickType_t timeout)
|
||||
{
|
||||
int need_write = len;
|
||||
int wlen = 0, widx = 0;
|
||||
int ret = ESP_FAIL;
|
||||
|
||||
if (client == NULL || len < 0 ||
|
||||
(opcode != WS_TRANSPORT_OPCODES_CLOSE && (data == NULL || len <= 0))) {
|
||||
ESP_LOGE(TAG, "Invalid arguments");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (xSemaphoreTakeRecursive(client->lock, timeout) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Could not lock ws-client within %d timeout", timeout);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (!esp_websocket_client_is_connected(client)) {
|
||||
ESP_LOGE(TAG, "Websocket client is not connected");
|
||||
goto unlock_and_return;
|
||||
}
|
||||
|
||||
if (client->transport == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid transport");
|
||||
goto unlock_and_return;
|
||||
}
|
||||
uint32_t current_opcode = opcode;
|
||||
while (widx < len || current_opcode) { // allow for sending "current_opcode" only message with len==0
|
||||
if (need_write > client->buffer_size) {
|
||||
need_write = client->buffer_size;
|
||||
} else {
|
||||
current_opcode |= WS_TRANSPORT_OPCODES_FIN;
|
||||
}
|
||||
memcpy(client->tx_buffer, data + widx, need_write);
|
||||
// send with ws specific way and specific opcode
|
||||
wlen = esp_transport_ws_send_raw(client->transport, current_opcode, (char *)client->tx_buffer, need_write,
|
||||
(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_client_abort_connection(client);
|
||||
goto unlock_and_return;
|
||||
}
|
||||
current_opcode = 0;
|
||||
widx += wlen;
|
||||
need_write = len - widx;
|
||||
|
||||
}
|
||||
ret = widx;
|
||||
unlock_and_return:
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return false;
|
||||
}
|
||||
return client->state == WEBSOCKET_STATE_CONNECTED;
|
||||
}
|
||||
|
||||
size_t esp_websocket_client_get_ping_interval_sec(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
ESP_LOGW(TAG, "Client was not initialized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (client->config == NULL) {
|
||||
ESP_LOGW(TAG, "No config available to change the ping interval");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return client->config->ping_interval_sec;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_set_ping_interval_sec(esp_websocket_client_handle_t client, size_t ping_interval_sec)
|
||||
{
|
||||
if (client == NULL) {
|
||||
ESP_LOGW(TAG, "Client was not initialized");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (client->config == NULL) {
|
||||
ESP_LOGW(TAG, "No config available to change the ping interval");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
client->config->ping_interval_sec = ping_interval_sec == 0 ? WEBSOCKET_PING_INTERVAL_SEC : ping_interval_sec;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client,
|
||||
esp_websocket_event_id_t event,
|
||||
esp_event_handler_t event_handler,
|
||||
void *event_handler_arg)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return esp_event_handler_register_with(client->event_handle, WEBSOCKET_EVENTS, event, event_handler, event_handler_arg);
|
||||
}
|
10
components/esp_websocket_client/examples/CMakeLists.txt
Normal file
10
components/esp_websocket_client/examples/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
# The following 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.5)
|
||||
|
||||
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")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(websocket_example)
|
89
components/esp_websocket_client/examples/README.md
Normal file
89
components/esp_websocket_client/examples/README.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Websocket Sample application
|
||||
|
||||
This example will shows how to set up and communicate over a websocket.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example can be executed on any ESP32 board, the only required interface is WiFi and connection to internet or a local server.
|
||||
|
||||
### Configure the project
|
||||
|
||||
* Open the project configuration menu (`idf.py menuconfig`)
|
||||
* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu.
|
||||
* Configure the websocket endpoint URI under "Example Configuration", if "WEBSOCKET_URI_FROM_STDIN" is selected then the example application will connect to the URI it reads from stdin (used for testing)
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (482) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (2492) example_connect: Ethernet Link Up
|
||||
I (4472) tcpip_adapter: eth ip: 192.168.2.137, mask: 255.255.255.0, gw: 192.168.2.2
|
||||
I (4472) example_connect: Connected to Ethernet
|
||||
I (4472) example_connect: IPv4 address: 192.168.2.137
|
||||
I (4472) example_connect: IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed4:a92b
|
||||
I (4482) WEBSOCKET: Connecting to ws://echo.websocket.events...
|
||||
I (5012) WEBSOCKET: WEBSOCKET_EVENT_CONNECTED
|
||||
I (5492) WEBSOCKET: Sending hello 0000
|
||||
I (6052) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (6052) WEBSOCKET: Received=hello 0000
|
||||
|
||||
I (6492) WEBSOCKET: Sending hello 0001
|
||||
I (7052) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (7052) WEBSOCKET: Received=hello 0001
|
||||
|
||||
I (7492) WEBSOCKET: Sending hello 0002
|
||||
I (8082) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (8082) WEBSOCKET: Received=hello 0002
|
||||
|
||||
I (8492) WEBSOCKET: Sending hello 0003
|
||||
I (9152) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (9162) WEBSOCKET: Received=hello 0003
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Python Flask echo server
|
||||
|
||||
By default, the `ws://echo.websocket.events` endpoint is used. You can setup a Python websocket echo server locally and try the `ws://<your-ip>:5000` endpoint. To do this, install Flask-sock Python package
|
||||
|
||||
```
|
||||
pip install flask-sock
|
||||
```
|
||||
|
||||
and start a Flask websocket echo server locally by executing the following Python code:
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
from flask_sock import Sock
|
||||
|
||||
app = Flask(__name__)
|
||||
sock = Sock(app)
|
||||
|
||||
|
||||
@sock.route('/')
|
||||
def echo(ws):
|
||||
while True:
|
||||
data = ws.receive()
|
||||
ws.send(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# To run your Flask + WebSocket server in production you can use Gunicorn:
|
||||
# gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app
|
||||
app.run(host="0.0.0.0", debug=True)
|
||||
```
|
||||
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "websocket_example.c"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,23 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice WEBSOCKET_URI_SOURCE
|
||||
prompt "Websocket URI source"
|
||||
default WEBSOCKET_URI_FROM_STRING
|
||||
help
|
||||
Selects the source of the URI used in the example.
|
||||
|
||||
config WEBSOCKET_URI_FROM_STRING
|
||||
bool "From string"
|
||||
|
||||
config WEBSOCKET_URI_FROM_STDIN
|
||||
bool "From stdin"
|
||||
endchoice
|
||||
|
||||
config WEBSOCKET_URI
|
||||
string "Websocket endpoint URI"
|
||||
depends on WEBSOCKET_URI_FROM_STRING
|
||||
default "ws://echo.websocket.events"
|
||||
help
|
||||
URL of websocket endpoint this example connects to and sends echo
|
||||
|
||||
endmenu
|
@ -0,0 +1,153 @@
|
||||
/* ESP HTTP Client 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 <stdio.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_event.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_websocket_client.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#define NO_DATA_TIMEOUT_SEC 5
|
||||
|
||||
static const char *TAG = "WEBSOCKET";
|
||||
|
||||
static TimerHandle_t shutdown_signal_timer;
|
||||
static SemaphoreHandle_t shutdown_sema;
|
||||
|
||||
static void shutdown_signaler(TimerHandle_t xTimer)
|
||||
{
|
||||
ESP_LOGI(TAG, "No data received for %d seconds, signaling shutdown", NO_DATA_TIMEOUT_SEC);
|
||||
xSemaphoreGive(shutdown_sema);
|
||||
}
|
||||
|
||||
#if CONFIG_WEBSOCKET_URI_FROM_STDIN
|
||||
static void get_string(char *line, size_t size)
|
||||
{
|
||||
int count = 0;
|
||||
while (count < size) {
|
||||
int c = fgetc(stdin);
|
||||
if (c == '\n') {
|
||||
line[count] = '\0';
|
||||
break;
|
||||
} else if (c > 0 && c < 127) {
|
||||
line[count] = c;
|
||||
++count;
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */
|
||||
|
||||
static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
|
||||
switch (event_id) {
|
||||
case WEBSOCKET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DATA:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
|
||||
ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
|
||||
if (data->op_code == 0x08 && data->data_len == 2) {
|
||||
ESP_LOGW(TAG, "Received closed message with code=%d", 256*data->data_ptr[0] + data->data_ptr[1]);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr);
|
||||
}
|
||||
ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);
|
||||
|
||||
xTimerReset(shutdown_signal_timer, portMAX_DELAY);
|
||||
break;
|
||||
case WEBSOCKET_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void websocket_app_start(void)
|
||||
{
|
||||
esp_websocket_client_config_t websocket_cfg = {};
|
||||
|
||||
shutdown_signal_timer = xTimerCreate("Websocket shutdown timer", NO_DATA_TIMEOUT_SEC * 1000 / portTICK_PERIOD_MS,
|
||||
pdFALSE, NULL, shutdown_signaler);
|
||||
shutdown_sema = xSemaphoreCreateBinary();
|
||||
|
||||
#if CONFIG_WEBSOCKET_URI_FROM_STDIN
|
||||
char line[128];
|
||||
|
||||
ESP_LOGI(TAG, "Please enter uri of websocket endpoint");
|
||||
get_string(line, sizeof(line));
|
||||
|
||||
websocket_cfg.uri = line;
|
||||
ESP_LOGI(TAG, "Endpoint uri: %s\n", line);
|
||||
|
||||
#else
|
||||
websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
|
||||
#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
|
||||
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
|
||||
|
||||
esp_websocket_client_start(client);
|
||||
xTimerStart(shutdown_signal_timer, portMAX_DELAY);
|
||||
char data[32];
|
||||
int i = 0;
|
||||
while (i < 5) {
|
||||
if (esp_websocket_client_is_connected(client)) {
|
||||
int len = sprintf(data, "hello %04d", i++);
|
||||
ESP_LOGI(TAG, "Sending %s", data);
|
||||
esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
xSemaphoreTake(shutdown_sema, portMAX_DELAY);
|
||||
esp_websocket_client_close(client, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Websocket Stopped");
|
||||
esp_websocket_client_destroy(client);
|
||||
}
|
||||
|
||||
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("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());
|
||||
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());
|
||||
|
||||
websocket_app_start();
|
||||
}
|
24
components/esp_websocket_client/examples/pytest.ini
Normal file
24
components/esp_websocket_client/examples/pytest.ini
Normal file
@ -0,0 +1,24 @@
|
||||
[pytest]
|
||||
# only the files with prefix `pytest_` would be recognized as pytest test scripts.
|
||||
python_files = pytest_*.py
|
||||
|
||||
addopts =
|
||||
-s
|
||||
--embedded-services esp,idf
|
||||
-W ignore::_pytest.warning_types.PytestExperimentalApiWarning
|
||||
--tb short
|
||||
|
||||
# ignore DeprecationWarning
|
||||
filterwarnings =
|
||||
ignore:Call to deprecated create function (.*)\(\):DeprecationWarning
|
||||
|
||||
# log related
|
||||
log_cli = True
|
||||
log_cli_level = INFO
|
||||
log_cli_format = %(asctime)s %(levelname)s %(message)s
|
||||
log_cli_date_format = %Y-%m-%d %H:%M:%S
|
||||
|
||||
log_file = test.log
|
||||
log_file_level = INFO
|
||||
log_file_format = %(asctime)s %(levelname)s %(message)s
|
||||
log_file_date_format = %Y-%m-%d %H:%M:%S
|
128
components/esp_websocket_client/examples/pytest_websocket.py
Normal file
128
components/esp_websocket_client/examples/pytest_websocket.py
Normal file
@ -0,0 +1,128 @@
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import socket
|
||||
import string
|
||||
from threading import Event, Thread
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(('8.8.8.8', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
IP = '127.0.0.1'
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
|
||||
class WebsocketTestEcho(WebSocket):
|
||||
|
||||
def handleMessage(self):
|
||||
self.sendMessage(self.data)
|
||||
print('Server sent: {}'.format(self.data))
|
||||
|
||||
def handleConnected(self):
|
||||
print('Connection from: {}'.format(self.address))
|
||||
|
||||
def handleClose(self):
|
||||
print('{} closed the connection'.format(self.address))
|
||||
|
||||
|
||||
# Simple Websocket server for testing purposes
|
||||
class Websocket(object):
|
||||
|
||||
def send_data(self, data):
|
||||
for nr, conn in self.server.connections.items():
|
||||
conn.sendMessage(data)
|
||||
|
||||
def run(self):
|
||||
self.server = SimpleWebSocketServer('', self.port, WebsocketTestEcho)
|
||||
while not self.exit_event.is_set():
|
||||
self.server.serveonce()
|
||||
|
||||
def __init__(self, port):
|
||||
self.port = port
|
||||
self.exit_event = Event()
|
||||
self.thread = Thread(target=self.run)
|
||||
self.thread.start()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.exit_event.set()
|
||||
self.thread.join(10)
|
||||
if self.thread.is_alive():
|
||||
Utility.console_log('Thread cannot be joined', 'orange')
|
||||
|
||||
|
||||
def test_examples_protocol_websocket(dut):
|
||||
"""
|
||||
steps:
|
||||
1. obtain IP address
|
||||
2. connect to uri specified in the config
|
||||
3. send and receive data
|
||||
"""
|
||||
def test_echo(dut):
|
||||
dut.expect('WEBSOCKET_EVENT_CONNECTED')
|
||||
for i in range(0, 5):
|
||||
dut.expect(re.compile(b'Received=hello (\\d)'))
|
||||
print('All echos received')
|
||||
|
||||
def test_close(dut):
|
||||
code = dut.expect(re.compile(b'WEBSOCKET: Received closed message with code=(\\d*)'))[0]
|
||||
print('Received close frame with code {}'.format(code))
|
||||
|
||||
def test_recv_long_msg(dut, websocket, msg_len, repeats):
|
||||
send_msg = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(msg_len))
|
||||
|
||||
for _ in range(repeats):
|
||||
websocket.send_data(send_msg)
|
||||
|
||||
recv_msg = ''
|
||||
while len(recv_msg) < msg_len:
|
||||
match = dut.expect(re.compile(b'Received=([a-zA-Z0-9]*).*')).group(1).decode()
|
||||
recv_msg += match
|
||||
|
||||
if recv_msg == send_msg:
|
||||
print('Sent message and received message are equal')
|
||||
else:
|
||||
raise ValueError('DUT received string do not match sent string, \nexpected: {}\nwith length {}\
|
||||
\nreceived: {}\nwith length {}'.format(send_msg, len(send_msg), recv_msg, len(recv_msg)))
|
||||
|
||||
# Starting of the test
|
||||
try:
|
||||
if dut.app.sdkconfig.get('WEBSOCKET_URI_FROM_STDIN') is True:
|
||||
uri_from_stdin = True
|
||||
else:
|
||||
uri = dut.app.sdkconfig['WEBSOCKET_URI']
|
||||
uri_from_stdin = False
|
||||
|
||||
except Exception:
|
||||
print('ENV_TEST_FAILURE: Cannot find uri settings in sdkconfig')
|
||||
raise
|
||||
|
||||
if uri_from_stdin:
|
||||
server_port = 8080
|
||||
with Websocket(server_port) as ws:
|
||||
uri = 'ws://{}:{}'.format(get_my_ip(), server_port)
|
||||
print('DUT connecting to {}'.format(uri))
|
||||
dut.expect('Please enter uri of websocket endpoint', timeout=30)
|
||||
dut.write(uri)
|
||||
test_echo(dut)
|
||||
# Message length should exceed DUT's buffer size to test fragmentation, default is 1024 byte
|
||||
test_recv_long_msg(dut, ws, 2000, 3)
|
||||
test_close(dut)
|
||||
else:
|
||||
print('DUT connecting to {}'.format(uri))
|
||||
test_echo(dut)
|
||||
|
@ -0,0 +1,4 @@
|
||||
pytest-embedded-serial-esp
|
||||
pytest-embedded-idf
|
||||
junit_xml
|
||||
SimpleWebSocketServer
|
12
components/esp_websocket_client/examples/sdkconfig.ci
Normal file
12
components/esp_websocket_client/examples/sdkconfig.ci
Normal file
@ -0,0 +1,12 @@
|
||||
CONFIG_WEBSOCKET_URI_FROM_STDIN=n
|
||||
CONFIG_WEBSOCKET_URI_FROM_STRING=y
|
||||
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
|
||||
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
|
5
components/esp_websocket_client/idf_component.yml
Normal file
5
components/esp_websocket_client/idf_component.yml
Normal file
@ -0,0 +1,5 @@
|
||||
version: "0.0.1"
|
||||
description: esp websocket client
|
||||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
279
components/esp_websocket_client/include/esp_websocket_client.h
Normal file
279
components/esp_websocket_client/include/esp_websocket_client.h
Normal file
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _ESP_WEBSOCKET_CLIENT_H_
|
||||
#define _ESP_WEBSOCKET_CLIENT_H_
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include <sys/socket.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct esp_websocket_client *esp_websocket_client_handle_t;
|
||||
|
||||
ESP_EVENT_DECLARE_BASE(WEBSOCKET_EVENTS); // declaration of the task events family
|
||||
|
||||
/**
|
||||
* @brief Websocket Client events id
|
||||
*/
|
||||
typedef enum {
|
||||
WEBSOCKET_EVENT_ANY = -1,
|
||||
WEBSOCKET_EVENT_ERROR = 0, /*!< This event occurs when there are any errors during execution */
|
||||
WEBSOCKET_EVENT_CONNECTED, /*!< Once the Websocket has been connected to the server, no data exchange has been performed */
|
||||
WEBSOCKET_EVENT_DISCONNECTED, /*!< The connection has been disconnected */
|
||||
WEBSOCKET_EVENT_DATA, /*!< When receiving data from the server, possibly multiple portions of the packet */
|
||||
WEBSOCKET_EVENT_CLOSED, /*!< The connection has been closed cleanly */
|
||||
WEBSOCKET_EVENT_MAX
|
||||
} esp_websocket_event_id_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket event data
|
||||
*/
|
||||
typedef struct {
|
||||
const char *data_ptr; /*!< Data pointer */
|
||||
int data_len; /*!< Data length */
|
||||
bool fin; /*!< Fin flag */
|
||||
uint8_t op_code; /*!< Received opcode */
|
||||
esp_websocket_client_handle_t client; /*!< esp_websocket_client_handle_t context */
|
||||
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_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket Client transport
|
||||
*/
|
||||
typedef enum {
|
||||
WEBSOCKET_TRANSPORT_UNKNOWN = 0x0, /*!< Transport unknown */
|
||||
WEBSOCKET_TRANSPORT_OVER_TCP, /*!< Transport over tcp */
|
||||
WEBSOCKET_TRANSPORT_OVER_SSL, /*!< Transport over ssl */
|
||||
} esp_websocket_transport_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket client setup configuration
|
||||
*/
|
||||
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 *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 */
|
||||
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. */
|
||||
size_t cert_len; /*!< Length of the buffer pointed to by cert_pem. May be 0 for null-terminated pem */
|
||||
const char *client_cert; /*!< Pointer to certificate data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_key` has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_cert_len. */
|
||||
size_t client_cert_len; /*!< Length of the buffer pointed to by client_cert. May be 0 for null-terminated pem */
|
||||
const char *client_key; /*!< Pointer to private key data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_cert` has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_key_len */
|
||||
size_t client_key_len; /*!< Length of the buffer pointed to by client_key_pem. May be 0 for null-terminated pem */
|
||||
esp_websocket_transport_t transport; /*!< Websocket transport type, see `esp_websocket_transport_t */
|
||||
const char *subprotocol; /*!< Websocket subprotocol */
|
||||
const char *user_agent; /*!< Websocket user-agent */
|
||||
const char *headers; /*!< Websocket additional headers */
|
||||
int pingpong_timeout_sec; /*!< Period before connection is aborted due to no PONGs received */
|
||||
bool disable_pingpong_discon; /*!< Disable auto-disconnect due to no PONG received within pingpong_timeout_sec */
|
||||
bool use_global_ca_store; /*!< Use a global ca_store for all the connections in which this bool is set. */
|
||||
bool skip_cert_common_name_check;/*!< Skip any validation of server certificate CN field */
|
||||
bool keep_alive_enable; /*!< Enable keep-alive timeout */
|
||||
int keep_alive_idle; /*!< Keep-alive idle time. Default is 5 (second) */
|
||||
int keep_alive_interval; /*!< Keep-alive interval time. Default is 5 (second) */
|
||||
int keep_alive_count; /*!< Keep-alive packet retry send count. Default is 3 counts */
|
||||
int reconnect_timeout_ms; /*!< Reconnect after this value in miliseconds if disable_auto_reconnect is not enabled (defaults to 10s) */
|
||||
int network_timeout_ms; /*!< Abort network operation if it is not completed after this value, in milliseconds (defaults to 10s) */
|
||||
size_t ping_interval_sec; /*!< Websocket ping interval, defaults to 10 seconds if not set */
|
||||
struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */
|
||||
} esp_websocket_client_config_t;
|
||||
|
||||
/**
|
||||
* @brief Start a Websocket session
|
||||
* This function must be the first function to call,
|
||||
* and it returns a esp_websocket_client_handle_t that you must use as input to other functions in the interface.
|
||||
* This call MUST have a corresponding call to esp_websocket_client_destroy when the operation is complete.
|
||||
*
|
||||
* @param[in] config The configuration
|
||||
*
|
||||
* @return
|
||||
* - `esp_websocket_client_handle_t`
|
||||
* - NULL if any errors
|
||||
*/
|
||||
esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Set URL for client, when performing this behavior, the options in the URL will replace the old ones
|
||||
* Must stop the WebSocket client before set URI if the client has been connected
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] uri The uri
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, const char *uri);
|
||||
|
||||
/**
|
||||
* @brief Open the WebSocket connection
|
||||
*
|
||||
* @param[in] client The client
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client);
|
||||
|
||||
/**
|
||||
* @brief Stops the WebSocket connection without websocket closing handshake
|
||||
*
|
||||
* This API stops ws client and closes TCP connection directly without sending
|
||||
* close frames. It is a good practice to close the connection in a clean way
|
||||
* using esp_websocket_client_close().
|
||||
*
|
||||
* Notes:
|
||||
* - Cannot be called from the websocket event handler
|
||||
*
|
||||
* @param[in] client The client
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client);
|
||||
|
||||
/**
|
||||
* @brief Destroy the WebSocket connection and free all resources.
|
||||
* This function must be the last function to call for an session.
|
||||
* It is the opposite of the esp_websocket_client_init function and must be called with the same handle as input that a esp_websocket_client_init call returned.
|
||||
* This might close all connections this handle has used.
|
||||
*
|
||||
* Notes:
|
||||
* - Cannot be called from the websocket event handler
|
||||
*
|
||||
* @param[in] client The client
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client);
|
||||
|
||||
/**
|
||||
* @brief Write binary data to the WebSocket connection (data send with WS OPCODE=02, i.e. binary)
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] data The data
|
||||
* @param[in] len The length
|
||||
* @param[in] timeout Write data timeout in RTOS ticks
|
||||
*
|
||||
* @return
|
||||
* - Number of data was sent
|
||||
* - (-1) if any errors
|
||||
*/
|
||||
int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Write textual data to the WebSocket connection (data send with WS OPCODE=01, i.e. text)
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] data The data
|
||||
* @param[in] len The length
|
||||
* @param[in] timeout Write data timeout in RTOS ticks
|
||||
*
|
||||
* @return
|
||||
* - Number of data was sent
|
||||
* - (-1) if any errors
|
||||
*/
|
||||
int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Close the WebSocket connection in a clean way
|
||||
*
|
||||
* Sequence of clean close initiated by client:
|
||||
* * Client sends CLOSE frame
|
||||
* * Client waits until server echos the CLOSE frame
|
||||
* * Client waits until server closes the connection
|
||||
* * Client is stopped the same way as by the `esp_websocket_client_stop()`
|
||||
*
|
||||
* Notes:
|
||||
* - Cannot be called from the websocket event handler
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] timeout Timeout in RTOS ticks for waiting
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickType_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Close the WebSocket connection in a clean way with custom code/data
|
||||
* Closing sequence is the same as for esp_websocket_client_close()
|
||||
*
|
||||
* Notes:
|
||||
* - Cannot be called from the websocket event handler
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] code Close status code as defined in RFC6455 section-7.4
|
||||
* @param[in] data Additional data to closing message
|
||||
* @param[in] len The length of the additional data
|
||||
* @param[in] timeout Timeout in RTOS ticks for waiting
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_close_with_code(esp_websocket_client_handle_t client, int code, const char *data, int len, TickType_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Check the WebSocket client connection state
|
||||
*
|
||||
* @param[in] client The client handle
|
||||
*
|
||||
* @return
|
||||
* - true
|
||||
* - false
|
||||
*/
|
||||
bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client);
|
||||
|
||||
/**
|
||||
* @brief Get the ping interval sec for client.
|
||||
*
|
||||
* @param[in] client The client
|
||||
*
|
||||
* @return The ping interval in sec
|
||||
*/
|
||||
size_t esp_websocket_client_get_ping_interval_sec(esp_websocket_client_handle_t client);
|
||||
|
||||
/**
|
||||
* @brief Set new ping interval sec for client.
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] ping_interval_sec The new interval
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_set_ping_interval_sec(esp_websocket_client_handle_t client, size_t ping_interval_sec);
|
||||
|
||||
/**
|
||||
* @brief Register the Websocket Events
|
||||
*
|
||||
* @param client The client handle
|
||||
* @param event The event id
|
||||
* @param event_handler The callback function
|
||||
* @param event_handler_arg User context
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client,
|
||||
esp_websocket_event_id_t event,
|
||||
esp_event_handler_t event_handler,
|
||||
void *event_handler_arg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
2
components/esp_websocket_client/test/CMakeLists.txt
Normal file
2
components/esp_websocket_client/test/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRC_DIRS "."
|
||||
PRIV_REQUIRES cmock test_utils esp_websocket_client)
|
56
components/esp_websocket_client/test/test_websocket_client.c
Normal file
56
components/esp_websocket_client/test/test_websocket_client.c
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*
|
||||
* This test 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 <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <esp_websocket_client.h>
|
||||
|
||||
#include "unity.h"
|
||||
#include "memory_checks.h"
|
||||
|
||||
static void test_leak_setup(const char * file, long line)
|
||||
{
|
||||
printf("%s:%ld\n", file, line);
|
||||
test_utils_record_free_mem();
|
||||
}
|
||||
|
||||
TEST_CASE("websocket init and deinit", "[websocket][leaks=0]")
|
||||
{
|
||||
test_leak_setup(__FILE__, __LINE__);
|
||||
const esp_websocket_client_config_t websocket_cfg = {
|
||||
// no connection takes place, but the uri has to be valid for init() to succeed
|
||||
.uri = "ws://echo.websocket.org",
|
||||
};
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
TEST_ASSERT_NOT_EQUAL(NULL, client);
|
||||
esp_websocket_client_destroy(client);
|
||||
}
|
||||
|
||||
TEST_CASE("websocket init with invalid url", "[websocket][leaks=0]")
|
||||
{
|
||||
test_leak_setup(__FILE__, __LINE__);
|
||||
const esp_websocket_client_config_t websocket_cfg = {
|
||||
.uri = "INVALID",
|
||||
};
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
TEST_ASSERT_NULL(client);
|
||||
}
|
||||
|
||||
TEST_CASE("websocket set url with invalid url", "[websocket][leaks=0]")
|
||||
{
|
||||
test_leak_setup(__FILE__, __LINE__);
|
||||
const esp_websocket_client_config_t websocket_cfg = {};
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
TEST_ASSERT_NOT_EQUAL(NULL, client);
|
||||
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_websocket_client_set_uri(client, "INVALID"));
|
||||
esp_websocket_client_destroy(client);
|
||||
}
|
22
components/mdns/CMakeLists.txt
Normal file
22
components/mdns/CMakeLists.txt
Normal file
@ -0,0 +1,22 @@
|
||||
if(CONFIG_MDNS_NETWORKING_SOCKET)
|
||||
set(MDNS_NETWORKING "mdns_networking_socket.c")
|
||||
else()
|
||||
set(MDNS_NETWORKING "mdns_networking_lwip.c")
|
||||
endif()
|
||||
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
if(${target} STREQUAL "linux")
|
||||
set(dependencies esp_system_protocols_linux)
|
||||
set(srcs "mdns.c" ${MDNS_NETWORKING})
|
||||
else()
|
||||
set(dependencies lwip console esp_netif)
|
||||
set(private_dependencies esp_timer)
|
||||
set(srcs "mdns.c" ${MDNS_NETWORKING} "mdns_console.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS "private_include"
|
||||
REQUIRES ${dependencies}
|
||||
PRIV_REQUIRES ${private_dependencies})
|
127
components/mdns/Kconfig
Normal file
127
components/mdns/Kconfig
Normal file
@ -0,0 +1,127 @@
|
||||
menu "mDNS"
|
||||
|
||||
config MDNS_MAX_INTERFACES
|
||||
int "Max number of interfaces"
|
||||
range 1 9
|
||||
default 3
|
||||
help
|
||||
Number of network interfaces to be served by the mDNS library.
|
||||
Lowering this number helps to reduce some static RAM usage.
|
||||
|
||||
config MDNS_MAX_SERVICES
|
||||
int "Max number of services"
|
||||
range 1 64
|
||||
default 10
|
||||
help
|
||||
Services take up a certain amount of memory, and allowing fewer
|
||||
services to be open at the same time conserves memory. Specify
|
||||
the maximum amount of services here. The valid value is from 1
|
||||
to 64.
|
||||
|
||||
config MDNS_TASK_PRIORITY
|
||||
int "mDNS task priority"
|
||||
range 1 255
|
||||
default 1
|
||||
help
|
||||
Allows setting mDNS task priority. Please do not set the task priority
|
||||
higher than priorities of system tasks. Compile time warning/error
|
||||
would be emitted if the chosen task priority were too high.
|
||||
|
||||
config MDNS_TASK_STACK_SIZE
|
||||
int "mDNS task stack size"
|
||||
default 4096
|
||||
help
|
||||
Allows setting mDNS task stacksize.
|
||||
|
||||
choice MDNS_TASK_AFFINITY
|
||||
prompt "mDNS task affinity"
|
||||
default MDNS_TASK_AFFINITY_CPU0
|
||||
help
|
||||
Allows setting mDNS tasks affinity, i.e. whether the task is pinned to
|
||||
CPU0, pinned to CPU1, or allowed to run on any CPU.
|
||||
|
||||
config MDNS_TASK_AFFINITY_NO_AFFINITY
|
||||
bool "No affinity"
|
||||
config MDNS_TASK_AFFINITY_CPU0
|
||||
bool "CPU0"
|
||||
config MDNS_TASK_AFFINITY_CPU1
|
||||
bool "CPU1"
|
||||
depends on !FREERTOS_UNICORE
|
||||
|
||||
endchoice
|
||||
|
||||
config MDNS_TASK_AFFINITY
|
||||
hex
|
||||
default FREERTOS_NO_AFFINITY if MDNS_TASK_AFFINITY_NO_AFFINITY
|
||||
default 0x0 if MDNS_TASK_AFFINITY_CPU0
|
||||
default 0x1 if MDNS_TASK_AFFINITY_CPU1
|
||||
|
||||
config MDNS_SERVICE_ADD_TIMEOUT_MS
|
||||
int "mDNS adding service timeout (ms)"
|
||||
range 10 30000
|
||||
default 2000
|
||||
help
|
||||
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
|
||||
default 100
|
||||
help
|
||||
Configures period of mDNS timer, which periodically transmits packets
|
||||
and schedules mDNS searches.
|
||||
|
||||
config MDNS_NETWORKING_SOCKET
|
||||
bool "Use BSD sockets for mDNS networking"
|
||||
default n
|
||||
help
|
||||
Enables optional mDNS networking implementation using BSD sockets
|
||||
in UDP multicast mode.
|
||||
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_MULTIPLE_INSTANCE
|
||||
bool "Multiple instances under the same service type"
|
||||
default y
|
||||
help
|
||||
Enables adding multiple service instances under the same service type.
|
||||
|
||||
menu "MDNS Predefined interfaces"
|
||||
|
||||
config MDNS_PREDEF_NETIF_STA
|
||||
bool "Use predefined interface for WiFi Station"
|
||||
default y
|
||||
help
|
||||
Set up mDNS for the default WiFi station.
|
||||
Disable this option if you do not need mDNS on default WiFi STA.
|
||||
|
||||
config MDNS_PREDEF_NETIF_AP
|
||||
bool "Use predefined interface for WiFi Access Point"
|
||||
default y
|
||||
help
|
||||
Set up mDNS for the default WiFi Access Point.
|
||||
Disable this option if you do not need mDNS on default WiFi AP.
|
||||
|
||||
config MDNS_PREDEF_NETIF_ETH
|
||||
bool "Use predefined interface for Ethernet"
|
||||
depends on ETH_ENABLED
|
||||
default y
|
||||
help
|
||||
Set up mDNS for the default Ethernet interface.
|
||||
Disable this option if you do not need mDNS on default Ethernet.
|
||||
|
||||
endmenu # MDNS Predefined interfaces
|
||||
|
||||
endmenu
|
12
components/mdns/README.md
Normal file
12
components/mdns/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# mDNS Service
|
||||
|
||||
mDNS is a multicast UDP service that is used to provide local network service and host discovery.
|
||||
|
||||
## Examples
|
||||
|
||||
Get started with example test [Example](examples/README.md):
|
||||
|
||||
## Documentation
|
||||
|
||||
* View the full [documentation(English)](https://espressif.github.io/esp-protocols/mdns/en/index.html)
|
||||
* View the full [documentation(Chinese)](https://espressif.github.io/esp-protocols/mdns/zh_CN/index.html)
|
75
components/mdns/docs/Doxyfile
Executable file
75
components/mdns/docs/Doxyfile
Executable file
@ -0,0 +1,75 @@
|
||||
# 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 = "ESP Protocols Programming Guide"
|
||||
|
||||
## 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/mdns.h
|
||||
|
||||
## 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
|
||||
GENERATE_RTF = NO
|
||||
|
||||
## 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.
|
||||
DOC_SINGLE_GROUP ## To conditionally remove groups from the documentation and create a 'flat' document without affecting documentation in upstream files.
|
21
components/mdns/docs/conf_common.py
Normal file
21
components/mdns/docs/conf_common.py
Normal file
@ -0,0 +1,21 @@
|
||||
from esp_docs.conf_docs import * # noqa: F403,F401
|
||||
|
||||
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-protocols'
|
||||
|
||||
# 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', 'zh_CN']
|
24
components/mdns/docs/en/conf.py
Normal file
24
components/mdns/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 - 2022, Espressif Systems (Shanghai) Co., Ltd'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
language = 'en'
|
205
components/mdns/docs/en/index.rst
Normal file
205
components/mdns/docs/en/index.rst
Normal file
@ -0,0 +1,205 @@
|
||||
mDNS Service
|
||||
============
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
mDNS is a multicast UDP service that is used to provide local network service and host discovery.
|
||||
|
||||
mDNS is installed by default on most operating systems or is available as separate package. On ``Mac OS`` it is installed by default and is called ``Bonjour``. Apple releases an installer for ``Windows`` that can be found `on Apple's support page <https://support.apple.com/downloads/bonjour%2520for%2520windows>`_. On ``Linux``, mDNS is provided by `avahi <https://github.com/lathiat/avahi>`_ and is usually installed by default.
|
||||
|
||||
mDNS Properties
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
* ``hostname``: the hostname that the device will respond to. If not set, the ``hostname`` will be read from the interface. Example: ``my-{IDF_TARGET_PATH_NAME}`` will resolve to ``my-{IDF_TARGET_PATH_NAME}.local``
|
||||
* ``default_instance``: friendly name for your device, like ``Jhon's {IDF_TARGET_NAME} Thing``. If not set, ``hostname`` will be used.
|
||||
|
||||
Example method to start mDNS for the STA interface and set ``hostname`` and ``default_instance``:
|
||||
|
||||
.. highlight:: c
|
||||
|
||||
::
|
||||
|
||||
void start_mdns_service()
|
||||
{
|
||||
//initialize mDNS service
|
||||
esp_err_t err = mdns_init();
|
||||
if (err) {
|
||||
printf("MDNS Init failed: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
//set hostname
|
||||
mdns_hostname_set("my-{IDF_TARGET_PATH_NAME}");
|
||||
//set default instance
|
||||
mdns_instance_name_set("Jhon's {IDF_TARGET_NAME} Thing");
|
||||
}
|
||||
|
||||
mDNS Services
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
mDNS can advertise information about network services that your device offers. Each service is defined by a few properties.
|
||||
|
||||
* ``instance_name``: friendly name for your service, like ``Jhon's E{IDF_TARGET_NAME} Web Server``. If not defined, ``default_instance`` will be used.
|
||||
* ``service_type``: (required) service type, prepended with underscore. Some common types can be found `here <http://www.dns-sd.org/serviceTypes.html>`_.
|
||||
* ``proto``: (required) protocol that the service runs on, prepended with underscore. Example: ``_tcp`` or ``_udp``
|
||||
* ``port``: (required) network port that the service runs on
|
||||
* ``txt``: ``{var, val}`` array of strings, used to define properties for your service
|
||||
|
||||
Example method to add a few services and different properties::
|
||||
|
||||
void add_mdns_services()
|
||||
{
|
||||
//add our services
|
||||
mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0);
|
||||
mdns_service_add(NULL, "_arduino", "_tcp", 3232, NULL, 0);
|
||||
mdns_service_add(NULL, "_myservice", "_udp", 1234, NULL, 0);
|
||||
|
||||
//NOTE: services must be added before their properties can be set
|
||||
//use custom instance for the web server
|
||||
mdns_service_instance_name_set("_http", "_tcp", "Jhon's {IDF_TARGET_NAME} Web Server");
|
||||
|
||||
mdns_txt_item_t serviceTxtData[3] = {
|
||||
{"board","{{IDF_TARGET_PATH_NAME}}"},
|
||||
{"u","user"},
|
||||
{"p","password"}
|
||||
};
|
||||
//set txt data for service (will free and replace current data)
|
||||
mdns_service_txt_set("_http", "_tcp", serviceTxtData, 3);
|
||||
|
||||
//change service port
|
||||
mdns_service_port_set("_myservice", "_udp", 4321);
|
||||
}
|
||||
|
||||
mDNS Query
|
||||
^^^^^^^^^^
|
||||
|
||||
mDNS provides methods for browsing for services and resolving host's IP/IPv6 addresses.
|
||||
|
||||
Results for services are returned as a linked list of ``mdns_result_t`` objects.
|
||||
|
||||
Example method to resolve host IPs::
|
||||
|
||||
void resolve_mdns_host(const char * host_name)
|
||||
{
|
||||
printf("Query A: %s.local", host_name);
|
||||
|
||||
struct ip4_addr addr;
|
||||
addr.addr = 0;
|
||||
|
||||
esp_err_t err = mdns_query_a(host_name, 2000, &addr);
|
||||
if(err){
|
||||
if(err == ESP_ERR_NOT_FOUND){
|
||||
printf("Host was not found!");
|
||||
return;
|
||||
}
|
||||
printf("Query Failed");
|
||||
return;
|
||||
}
|
||||
|
||||
printf(IPSTR, IP2STR(&addr));
|
||||
}
|
||||
|
||||
Example method to resolve local services::
|
||||
|
||||
static const char * if_str[] = {"STA", "AP", "ETH", "MAX"};
|
||||
static const char * ip_protocol_str[] = {"V4", "V6", "MAX"};
|
||||
|
||||
void mdns_print_results(mdns_result_t * results){
|
||||
mdns_result_t * r = results;
|
||||
mdns_ip_addr_t * a = NULL;
|
||||
int i = 1, t;
|
||||
while(r){
|
||||
printf("%d: Interface: %s, Type: %s\n", i++, if_str[r->tcpip_if], ip_protocol_str[r->ip_protocol]);
|
||||
if(r->instance_name){
|
||||
printf(" PTR : %s\n", r->instance_name);
|
||||
}
|
||||
if(r->hostname){
|
||||
printf(" SRV : %s.local:%u\n", r->hostname, r->port);
|
||||
}
|
||||
if(r->txt_count){
|
||||
printf(" TXT : [%u] ", r->txt_count);
|
||||
for(t=0; t<r->txt_count; t++){
|
||||
printf("%s=%s; ", r->txt[t].key, r->txt[t].value);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
a = r->addr;
|
||||
while(a){
|
||||
if(a->addr.type == IPADDR_TYPE_V6){
|
||||
printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6));
|
||||
} else {
|
||||
printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4)));
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void find_mdns_service(const char * service_name, const char * proto)
|
||||
{
|
||||
ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto);
|
||||
|
||||
mdns_result_t * results = NULL;
|
||||
esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results);
|
||||
if(err){
|
||||
ESP_LOGE(TAG, "Query Failed");
|
||||
return;
|
||||
}
|
||||
if(!results){
|
||||
ESP_LOGW(TAG, "No results found!");
|
||||
return;
|
||||
}
|
||||
|
||||
mdns_print_results(results);
|
||||
mdns_query_results_free(results);
|
||||
}
|
||||
|
||||
Example of using the methods above::
|
||||
|
||||
void my_app_some_method(){
|
||||
//search for {IDF_TARGET_PATH_NAME}-mdns.local
|
||||
resolve_mdns_host("{IDF_TARGET_PATH_NAME}-mdns");
|
||||
|
||||
//search for HTTP servers
|
||||
find_mdns_service("_http", "_tcp");
|
||||
//or file servers
|
||||
find_mdns_service("_smb", "_tcp"); //windows sharing
|
||||
find_mdns_service("_afpovertcp", "_tcp"); //apple sharing
|
||||
find_mdns_service("_nfs", "_tcp"); //NFS server
|
||||
find_mdns_service("_ftp", "_tcp"); //FTP server
|
||||
//or networked printer
|
||||
find_mdns_service("_printer", "_tcp");
|
||||
find_mdns_service("_ipp", "_tcp");
|
||||
}
|
||||
|
||||
|
||||
Performance Optimization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Execution Speed
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- mDNS creates a task with default low priority 1 ``CONFIG_MDNS_TASK_PRIORITY`` (If ``CONFIG_FREERTOS_UNICORE`` enabeled it pinned to CPU0 (``CONFIG_MDNS_TASK_AFFINITY``).
|
||||
Please check `Maximizing Execution Speed <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/performance/speed.html>`_ for more details.
|
||||
|
||||
Minimizing RAM Usage
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- mDNS creates a tasks with stack sizes configured by ``CONFIG_MDNS_TASK_STACK_SIZE``.
|
||||
Please check `Minimizing RAM Usage <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/performance/ram-usage.html>`_ for more details.
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
|
||||
mDNS server/scanner example: :example:`<../examples>`.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/mdns.inc
|
||||
|
||||
|
28
components/mdns/docs/generate_docs
Executable file
28
components/mdns/docs/generate_docs
Executable file
@ -0,0 +1,28 @@
|
||||
build-docs --target esp32 --language en
|
||||
build-docs --target esp32 --language zh_CN
|
||||
|
||||
cp -rf _build/en/esp32/html html_en
|
||||
cp -rf _build/zh_CN/esp32/html html_zh_CN
|
||||
rm -rf _build __pycache__ tee
|
||||
|
||||
# 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>" | tee -a html_en/index.html html_zh_CN/index.html > /dev/null
|
27
components/mdns/docs/zh_CN/conf.py
Normal file
27
components/mdns/docs/zh_CN/conf.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- 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
|
||||
|
||||
import datetime
|
||||
|
||||
current_year = datetime.datetime.now().year
|
||||
|
||||
# General information about the project.
|
||||
project = u'ESP-IDF 编程指南'
|
||||
copyright = u'2016 - {} 乐鑫信息科技(上海)股份有限公司'.format(current_year)
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
language = 'zh_CN'
|
189
components/mdns/docs/zh_CN/index.rst
Normal file
189
components/mdns/docs/zh_CN/index.rst
Normal file
@ -0,0 +1,189 @@
|
||||
mDNS 服务
|
||||
=========
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
概述
|
||||
----
|
||||
|
||||
mDNS 是一种组播 UDP 服务,用来提供本地网络服务和主机发现。
|
||||
|
||||
绝大多数的操作系统默认都会安装 mDNS 服务,或者提供单独的安装包。``Mac OS`` 默认会安装名为 ``Bonjour`` 的服务(该服务基于 mDNS),此外 Apple 还发布了适用于 Windows 系统的安装程序,可以在 `官方支持 <https://support.apple.com/downloads/bonjour%2520for%2520windows>`_ 找到。在 ``Linux`` 上,mDNS 服务由 `avahi <https://github.com/lathiat/avahi>`_ 提供,通常也会被默认安装。
|
||||
|
||||
mDNS 属性
|
||||
^^^^^^^^^
|
||||
|
||||
* ``hostname``:设备会去响应的主机名,如果没有设置,会根据设备的网络接口名定义 ``hostname`` 。例如,``my-{IDF_TARGET_PATH_NAME}`` 会被解析为 ``my-{IDF_TARGET_PATH_NAME}.local``。
|
||||
* ``default_instance``:默认实例名(即易记的设备名),例如 ``Jhon's {IDF_TARGET_NAME} Thing``。如果没有设置,将会使用 ``hostname``。
|
||||
|
||||
以下为 STA 接口启动 mDNS 服务并设置 ``hostname`` 和 ``default_instance`` 的示例方法:
|
||||
|
||||
.. highlight:: c
|
||||
|
||||
::
|
||||
|
||||
void start_mdns_service()
|
||||
{
|
||||
// 初始化 mDNS 服务
|
||||
esp_err_t err = mdns_init();
|
||||
if (err) {
|
||||
printf("MDNS Init failed: %d\n", err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置 hostname
|
||||
mdns_hostname_set("my-{IDF_TARGET_PATH_NAME}");
|
||||
// 设置默认实例
|
||||
mdns_instance_name_set("Jhon's {IDF_TARGET_NAME} Thing");
|
||||
}
|
||||
|
||||
mDNS 服务
|
||||
^^^^^^^^^
|
||||
|
||||
mDNS 可以广播设备能够提供的网络服务的相关信息,每个服务会由以下属性构成。
|
||||
|
||||
* ``instance_name``:实例名(即易记的服务名),例如 ``Jhon's {IDF_TARGET_NAME} Web Server``。如果没有定义,会使用 ``default_instance``。
|
||||
* ``service_type``:(必需)服务类型,以下划线为前缀,`这里 <http://www.dns-sd.org/serviceTypes.html>`_ 列出了常见的类型。
|
||||
* ``proto``:(必需)服务运行所依赖的协议,以下划线为前缀,例如 ``_tcp`` 或者 ``_udp``。
|
||||
* ``port``:(必需)服务运行所用的端口号。
|
||||
* ``txt``:形如 ``{var, val}`` 的字符串数组,用于定义服务的属性。
|
||||
|
||||
添加一些服务和不同属性的示例方法::
|
||||
|
||||
void add_mdns_services()
|
||||
{
|
||||
// 添加服务
|
||||
mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0);
|
||||
mdns_service_add(NULL, "_arduino", "_tcp", 3232, NULL, 0);
|
||||
mdns_service_add(NULL, "_myservice", "_udp", 1234, NULL, 0);
|
||||
|
||||
// 注意:必须先添加服务,然后才能设置其属性
|
||||
// web 服务器使用自定义的实例名
|
||||
mdns_service_instance_name_set("_http", "_tcp", "Jhon's {IDF_TARGET_NAME} Web Server");
|
||||
|
||||
mdns_txt_item_t serviceTxtData[3] = {
|
||||
{"board","{IDF_TARGET_PATH_NAME}"},
|
||||
{"u","user"},
|
||||
{"p","password"}
|
||||
};
|
||||
// 设置服务的文本数据(会释放并替换当前数据)
|
||||
mdns_service_txt_set("_http", "_tcp", serviceTxtData, 3);
|
||||
|
||||
// 修改服务端口号
|
||||
mdns_service_port_set("_myservice", "_udp", 4321);
|
||||
}
|
||||
|
||||
mDNS 查询
|
||||
^^^^^^^^^
|
||||
|
||||
mDNS 提供查询服务和解析主机 IP/IPv6 地址的方法。
|
||||
|
||||
服务查询的结果会作为 ``mdns_result_t`` 类型对象的链表返回。
|
||||
|
||||
解析主机 IP 地址的示例方法::
|
||||
|
||||
void resolve_mdns_host(const char * host_name)
|
||||
{
|
||||
printf("Query A: %s.local", host_name);
|
||||
|
||||
struct ip4_addr addr;
|
||||
addr.addr = 0;
|
||||
|
||||
esp_err_t err = mdns_query_a(host_name, 2000, &addr);
|
||||
if(err){
|
||||
if(err == ESP_ERR_NOT_FOUND){
|
||||
printf("Host was not found!");
|
||||
return;
|
||||
}
|
||||
printf("Query Failed");
|
||||
return;
|
||||
}
|
||||
|
||||
printf(IPSTR, IP2STR(&addr));
|
||||
}
|
||||
|
||||
解析本地服务的示例方法::
|
||||
|
||||
static const char * if_str[] = {"STA", "AP", "ETH", "MAX"};
|
||||
static const char * ip_protocol_str[] = {"V4", "V6", "MAX"};
|
||||
|
||||
void mdns_print_results(mdns_result_t * results){
|
||||
mdns_result_t * r = results;
|
||||
mdns_ip_addr_t * a = NULL;
|
||||
int i = 1, t;
|
||||
while(r){
|
||||
printf("%d: Interface: %s, Type: %s\n", i++, if_str[r->tcpip_if], ip_protocol_str[r->ip_protocol]);
|
||||
if(r->instance_name){
|
||||
printf(" PTR : %s\n", r->instance_name);
|
||||
}
|
||||
if(r->hostname){
|
||||
printf(" SRV : %s.local:%u\n", r->hostname, r->port);
|
||||
}
|
||||
if(r->txt_count){
|
||||
printf(" TXT : [%u] ", r->txt_count);
|
||||
for(t=0; t<r->txt_count; t++){
|
||||
printf("%s=%s; ", r->txt[t].key, r->txt[t].value);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
a = r->addr;
|
||||
while(a){
|
||||
if(a->addr.type == IPADDR_TYPE_V6){
|
||||
printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6));
|
||||
} else {
|
||||
printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4)));
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void find_mdns_service(const char * service_name, const char * proto)
|
||||
{
|
||||
ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto);
|
||||
|
||||
mdns_result_t * results = NULL;
|
||||
esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results);
|
||||
if(err){
|
||||
ESP_LOGE(TAG, "Query Failed");
|
||||
return;
|
||||
}
|
||||
if(!results){
|
||||
ESP_LOGW(TAG, "No results found!");
|
||||
return;
|
||||
}
|
||||
|
||||
mdns_print_results(results);
|
||||
mdns_query_results_free(results);
|
||||
}
|
||||
|
||||
使用上述方法的示例::
|
||||
|
||||
void my_app_some_method(){
|
||||
// 搜索 {IDF_TARGET_PATH_NAME}-mdns.local
|
||||
resolve_mdns_host("{IDF_TARGET_PATH_NAME}-mdns");
|
||||
|
||||
// 搜索 HTTP 服务器
|
||||
find_mdns_service("_http", "_tcp");
|
||||
// 或者搜索文件服务器
|
||||
find_mdns_service("_smb", "_tcp"); // Windows 系统的共享服务
|
||||
find_mdns_service("_afpovertcp", "_tcp"); // Apple AFP 文件共享服务
|
||||
find_mdns_service("_nfs", "_tcp"); // NFS 服务器
|
||||
find_mdns_service("_ftp", "_tcp"); // FTP 服务器
|
||||
// 或者网络打印机
|
||||
find_mdns_service("_printer", "_tcp");
|
||||
find_mdns_service("_ipp", "_tcp");
|
||||
}
|
||||
|
||||
应用示例
|
||||
--------
|
||||
|
||||
有关 mDNS 服务器和查询器的应用示例请参考 :example:`<../examples>`。
|
||||
|
||||
API 参考
|
||||
--------
|
||||
|
||||
.. include-build-file:: inc/mdns.inc
|
||||
|
||||
|
10
components/mdns/examples/CMakeLists.txt
Normal file
10
components/mdns/examples/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
# The following 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.5)
|
||||
|
||||
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")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(mdns_test)
|
91
components/mdns/examples/README.md
Normal file
91
components/mdns/examples/README.md
Normal file
@ -0,0 +1,91 @@
|
||||
# mDNS example
|
||||
|
||||
Shows how to use mDNS to advertise lookup services and hosts
|
||||
|
||||
## Example workflow
|
||||
|
||||
- mDNS is initialized with host name and instance name defined through the project configuration and `_http._tcp` service is added to be advertised
|
||||
- A delegated host `esp32-delegated._local` is added and another `_http._tcp` service is added for this host.
|
||||
- WiFi STA is started and trying to connect to the access point defined through the project configuration
|
||||
- The system event handler is used to pass the network events to mDNS so the service is aware when the interface comes up or down
|
||||
- GPIO0 (BOOT Button) is initialized as pulled-up input that can be monitored for button press
|
||||
- Example task is started to check if the button is pressed so it can execute the mDNS queries defined
|
||||
|
||||
### Configure the project
|
||||
|
||||
* Open the project configuration menu (`idf.py menuconfig`)
|
||||
|
||||
* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu
|
||||
* Set `mDNS Hostname` as host name prefix for the device and its instance name in `mDNS Instance Name`
|
||||
* Disable `Resolve test services` to prevent the example from querying defined names/services on startup (cause warnings in example logs, as illustrated below)
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
- Wait for WiFi to connect to your access point
|
||||
- You can now ping the device at `[board-hostname].local`, where `[board-hostname]` is preconfigured hostname, `esp32-mdns` by default.
|
||||
- You can also browse for `_http._tcp` on the same network to find the advertised service
|
||||
- Pressing the BOOT button will start querying the local network for the predefined in `check_button` hosts and services
|
||||
- Note that for purpose of CI tests, configuration options of `MDNS_RESOLVE_TEST_SERVICES` and `MDNS_ADD_MAC_TO_HOSTNAME` are available, but disabled by default. If enabled, then the hostname suffix of last 3 bytes from device MAC address is added, e.g. `esp32-mdns-80FFFF`, and a query for test service is issued.
|
||||
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
```
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (276) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (276) mdns-test: mdns hostname set to: [esp32-mdns]
|
||||
I (286) wifi: wifi driver task: 3ffc2fa4, prio:23, stack:3584, core=0
|
||||
I (286) wifi: wifi firmware version: a3be639
|
||||
I (286) wifi: config NVS flash: enabled
|
||||
I (296) wifi: config nano formating: disabled
|
||||
I (296) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (306) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (336) wifi: Init dynamic tx buffer num: 32
|
||||
I (336) wifi: Init data frame dynamic rx buffer num: 32
|
||||
I (336) wifi: Init management frame dynamic rx buffer num: 32
|
||||
I (346) wifi: Init static rx buffer size: 1600
|
||||
I (346) wifi: Init static rx buffer num: 10
|
||||
I (346) wifi: Init dynamic rx buffer num: 32
|
||||
I (356) mdns-test: Setting WiFi configuration SSID myssid...
|
||||
I (426) phy: phy_version: 4000, b6198fa, Sep 3 2018, 15:11:06, 0, 0
|
||||
I (426) wifi: mode : sta (30:ae:a4:80:FF:FF)
|
||||
I (426) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (1756) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1
|
||||
I (2736) wifi: state: init -> auth (b0)
|
||||
I (2756) wifi: state: auth -> assoc (0)
|
||||
I (2766) wifi: state: assoc -> run (10)
|
||||
I (2786) wifi: connected with myssid, channel 11
|
||||
I (2786) wifi: pm start, type: 1
|
||||
|
||||
I (4786) event: sta ip: 192.168.0.139, mask: 255.255.255.0, gw: 192.168.0.2
|
||||
I (21126) mdns-test: Query A: esp32.local
|
||||
W (23176) mdns-test: ESP_ERR_NOT_FOUND: Host was not found!
|
||||
I (23176) mdns-test: Query PTR: _arduino._tcp.local
|
||||
W (26276) mdns-test: No results found!
|
||||
I (26276) mdns-test: Query PTR: _http._tcp.local
|
||||
1: Interface: STA, Type: V6
|
||||
PTR : HP Color LaserJet MFP M277dw (7C2E10)
|
||||
SRV : NPI7C2E10.local:80
|
||||
A : 254.128.0.0
|
||||
2: Interface: STA, Type: V4
|
||||
PTR : switch4e4919
|
||||
SRV : switch4e4919.local:80
|
||||
TXT : [1] path=/config/authentication_page.htm;
|
||||
A : 192.168.0.118
|
||||
I (29396) mdns-test: Query PTR: _printer._tcp.local
|
||||
1: Interface: STA, Type: V6
|
||||
PTR : HP Color LaserJet MFP M277dw (7C2E10)
|
||||
SRV : NPI7C2E10.local:515
|
||||
A : 254.128.0.0
|
||||
2: Interface: STA, Type: V4
|
||||
PTR : HP Color LaserJet MFP M277dw (7C2E10)
|
||||
```
|
2
components/mdns/examples/main/CMakeLists.txt
Normal file
2
components/mdns/examples/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "mdns_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
55
components/mdns/examples/main/Kconfig.projbuild
Normal file
55
components/mdns/examples/main/Kconfig.projbuild
Normal file
@ -0,0 +1,55 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
|
||||
|
||||
config MDNS_HOSTNAME
|
||||
string "mDNS Hostname"
|
||||
default "esp32-mdns"
|
||||
help
|
||||
mDNS Hostname for example to use
|
||||
|
||||
config MDNS_INSTANCE
|
||||
string "mDNS Instance Name"
|
||||
default "ESP32 with mDNS"
|
||||
help
|
||||
mDNS Instance Name for example to use
|
||||
|
||||
config MDNS_PUBLISH_DELEGATE_HOST
|
||||
bool "Publish a delegated host"
|
||||
help
|
||||
Enable publishing a delegated host other than ESP32.
|
||||
The example will also add a mock service for this host.
|
||||
|
||||
config MDNS_RESOLVE_TEST_SERVICES
|
||||
bool "Resolve test services"
|
||||
default n
|
||||
help
|
||||
Enable resolving test services on startup.
|
||||
These services are advertized and evaluated in automated tests.
|
||||
When executed locally, these will not be resolved and warnings appear in the log.
|
||||
Please set to false to disable initial querying to avoid warnings.
|
||||
|
||||
config MDNS_ADD_MAC_TO_HOSTNAME
|
||||
bool "Add mac suffix to hostname"
|
||||
default n
|
||||
help
|
||||
If enabled, a portion of MAC address is added to the hostname, this is used
|
||||
for evaluation of tests in CI
|
||||
|
||||
config MDNS_BUTTON_GPIO
|
||||
int "Button GPIO to trigger querries"
|
||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX
|
||||
default 0
|
||||
help
|
||||
Set the GPIO number used as mDNS test button
|
||||
|
||||
config MDNS_ADD_CUSTOM_NETIF
|
||||
bool "Add user netif to mdns service"
|
||||
default n
|
||||
help
|
||||
If enabled, we try to add a custom netif to mdns service.
|
||||
Note that for using with common connection example code, we have to disable
|
||||
all predefined interfaces in mdns component setup (since we're adding one
|
||||
of the default interfaces)
|
||||
|
||||
endmenu
|
343
components/mdns/examples/main/mdns_example_main.c
Normal file
343
components/mdns/examples/main/mdns_example_main.c
Normal file
@ -0,0 +1,343 @@
|
||||
/* MDNS-SD Query and advertise 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 <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_netif_ip_addr.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#include "mdns.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "netdb.h"
|
||||
|
||||
|
||||
#define EXAMPLE_MDNS_INSTANCE CONFIG_MDNS_INSTANCE
|
||||
#define EXAMPLE_BUTTON_GPIO CONFIG_MDNS_BUTTON_GPIO
|
||||
|
||||
static const char * TAG = "mdns-test";
|
||||
static char * generate_hostname(void);
|
||||
|
||||
#if CONFIG_MDNS_RESOLVE_TEST_SERVICES == 1
|
||||
static void query_mdns_host_with_gethostbyname(char * host);
|
||||
static void query_mdns_host_with_getaddrinfo(char * host);
|
||||
#endif
|
||||
|
||||
static void initialise_mdns(void)
|
||||
{
|
||||
char * hostname = generate_hostname();
|
||||
|
||||
//initialize mDNS
|
||||
ESP_ERROR_CHECK( mdns_init() );
|
||||
//set mDNS hostname (required if you want to advertise services)
|
||||
ESP_ERROR_CHECK( mdns_hostname_set(hostname) );
|
||||
ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname);
|
||||
//set default mDNS instance name
|
||||
ESP_ERROR_CHECK( mdns_instance_name_set(EXAMPLE_MDNS_INSTANCE) );
|
||||
|
||||
//structure with TXT records
|
||||
mdns_txt_item_t serviceTxtData[3] = {
|
||||
{"board", "esp32"},
|
||||
{"u", "user"},
|
||||
{"p", "password"}
|
||||
};
|
||||
|
||||
//initialize service
|
||||
ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer", "_http", "_tcp", 80, serviceTxtData, 3) );
|
||||
ESP_ERROR_CHECK( mdns_service_subtype_add_for_host("ESP32-WebServer", "_http", "_tcp", NULL, "_server") );
|
||||
#if CONFIG_MDNS_MULTIPLE_INSTANCE
|
||||
ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer1", "_http", "_tcp", 80, NULL, 0) );
|
||||
#endif
|
||||
|
||||
#if CONFIG_MDNS_PUBLISH_DELEGATE_HOST
|
||||
char *delegated_hostname;
|
||||
if (-1 == asprintf(&delegated_hostname, "%s-delegated", hostname)) {
|
||||
abort();
|
||||
}
|
||||
|
||||
mdns_ip_addr_t addr4, addr6;
|
||||
esp_netif_str_to_ip4("10.0.0.1", &addr4.addr.u_addr.ip4);
|
||||
addr4.addr.type = ESP_IPADDR_TYPE_V4;
|
||||
esp_netif_str_to_ip6("fd11:22::1", &addr6.addr.u_addr.ip6);
|
||||
addr6.addr.type = ESP_IPADDR_TYPE_V6;
|
||||
addr4.next = &addr6;
|
||||
addr6.next = NULL;
|
||||
ESP_ERROR_CHECK( mdns_delegate_hostname_add(delegated_hostname, &addr4) );
|
||||
ESP_ERROR_CHECK( mdns_service_add_for_host("test0", "_http", "_tcp", delegated_hostname, 1234, serviceTxtData, 3) );
|
||||
free(delegated_hostname);
|
||||
#endif // CONFIG_MDNS_PUBLISH_DELEGATE_HOST
|
||||
|
||||
//add another TXT item
|
||||
ESP_ERROR_CHECK( mdns_service_txt_item_set("_http", "_tcp", "path", "/foobar") );
|
||||
//change TXT item value
|
||||
ESP_ERROR_CHECK( mdns_service_txt_item_set_with_explicit_value_len("_http", "_tcp", "u", "admin", strlen("admin")) );
|
||||
free(hostname);
|
||||
}
|
||||
|
||||
/* these strings match mdns_ip_protocol_t enumeration */
|
||||
static const char * ip_protocol_str[] = {"V4", "V6", "MAX"};
|
||||
|
||||
static void mdns_print_results(mdns_result_t *results)
|
||||
{
|
||||
mdns_result_t *r = results;
|
||||
mdns_ip_addr_t *a = NULL;
|
||||
int i = 1, t;
|
||||
while (r) {
|
||||
printf("%d: Interface: %s, Type: %s, TTL: %u\n", i++, esp_netif_get_ifkey(r->esp_netif), ip_protocol_str[r->ip_protocol],
|
||||
r->ttl);
|
||||
if (r->instance_name) {
|
||||
printf(" PTR : %s.%s.%s\n", r->instance_name, r->service_type, r->proto);
|
||||
}
|
||||
if (r->hostname) {
|
||||
printf(" SRV : %s.local:%u\n", r->hostname, r->port);
|
||||
}
|
||||
if (r->txt_count) {
|
||||
printf(" TXT : [%zu] ", r->txt_count);
|
||||
for (t = 0; t < r->txt_count; t++) {
|
||||
printf("%s=%s(%d); ", r->txt[t].key, r->txt[t].value ? r->txt[t].value : "NULL", r->txt_value_len[t]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
a = r->addr;
|
||||
while (a) {
|
||||
if (a->addr.type == ESP_IPADDR_TYPE_V6) {
|
||||
printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6));
|
||||
} else {
|
||||
printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4)));
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
r = r->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void query_mdns_service(const char * service_name, const char * proto)
|
||||
{
|
||||
ESP_LOGI(TAG, "Query PTR: %s.%s.local", service_name, proto);
|
||||
|
||||
mdns_result_t * results = NULL;
|
||||
esp_err_t err = mdns_query_ptr(service_name, proto, 3000, 20, &results);
|
||||
if(err){
|
||||
ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
if(!results){
|
||||
ESP_LOGW(TAG, "No results found!");
|
||||
return;
|
||||
}
|
||||
|
||||
mdns_print_results(results);
|
||||
mdns_query_results_free(results);
|
||||
}
|
||||
|
||||
static bool check_and_print_result(mdns_search_once_t *search)
|
||||
{
|
||||
// Check if any result is available
|
||||
mdns_result_t * result = NULL;
|
||||
if (!mdns_query_async_get_results(search, 0, &result, NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!result) { // search timeout, but no result
|
||||
return true;
|
||||
}
|
||||
|
||||
// If yes, print the result
|
||||
mdns_ip_addr_t * a = result->addr;
|
||||
while (a) {
|
||||
if(a->addr.type == ESP_IPADDR_TYPE_V6){
|
||||
printf(" AAAA: " IPV6STR "\n", IPV62STR(a->addr.u_addr.ip6));
|
||||
} else {
|
||||
printf(" A : " IPSTR "\n", IP2STR(&(a->addr.u_addr.ip4)));
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
// and free the result
|
||||
mdns_query_results_free(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void query_mdns_hosts_async(const char * host_name)
|
||||
{
|
||||
ESP_LOGI(TAG, "Query both A and AAA: %s.local", host_name);
|
||||
|
||||
mdns_search_once_t *s_a = mdns_query_async_new(host_name, NULL, NULL, MDNS_TYPE_A, 1000, 1, NULL);
|
||||
mdns_search_once_t *s_aaaa = mdns_query_async_new(host_name, NULL, NULL, MDNS_TYPE_AAAA, 1000, 1, NULL);
|
||||
while (s_a || s_aaaa) {
|
||||
if (s_a && check_and_print_result(s_a)) {
|
||||
ESP_LOGI(TAG, "Query A %s.local finished", host_name);
|
||||
mdns_query_async_delete(s_a);
|
||||
s_a = NULL;
|
||||
}
|
||||
if (s_aaaa && check_and_print_result(s_aaaa)) {
|
||||
ESP_LOGI(TAG, "Query AAAA %s.local finished", host_name);
|
||||
mdns_query_async_delete(s_aaaa);
|
||||
s_aaaa = NULL;
|
||||
}
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
static void query_mdns_host(const char * host_name)
|
||||
{
|
||||
ESP_LOGI(TAG, "Query A: %s.local", host_name);
|
||||
|
||||
struct esp_ip4_addr addr;
|
||||
addr.addr = 0;
|
||||
|
||||
esp_err_t err = mdns_query_a(host_name, 2000, &addr);
|
||||
if(err){
|
||||
if(err == ESP_ERR_NOT_FOUND){
|
||||
ESP_LOGW(TAG, "%s: Host was not found!", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
ESP_LOGE(TAG, "Query Failed: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Query A: %s.local resolved to: " IPSTR, host_name, IP2STR(&addr));
|
||||
}
|
||||
|
||||
static void initialise_button(void)
|
||||
{
|
||||
gpio_config_t io_conf = {0};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.pin_bit_mask = BIT64(EXAMPLE_BUTTON_GPIO);
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pull_up_en = 1;
|
||||
io_conf.pull_down_en = 0;
|
||||
gpio_config(&io_conf);
|
||||
}
|
||||
|
||||
static void check_button(void)
|
||||
{
|
||||
static bool old_level = true;
|
||||
bool new_level = gpio_get_level(EXAMPLE_BUTTON_GPIO);
|
||||
if (!new_level && old_level) {
|
||||
query_mdns_hosts_async("esp32-mdns");
|
||||
query_mdns_host("esp32");
|
||||
query_mdns_service("_arduino", "_tcp");
|
||||
query_mdns_service("_http", "_tcp");
|
||||
query_mdns_service("_printer", "_tcp");
|
||||
query_mdns_service("_ipp", "_tcp");
|
||||
query_mdns_service("_afpovertcp", "_tcp");
|
||||
query_mdns_service("_smb", "_tcp");
|
||||
query_mdns_service("_ftp", "_tcp");
|
||||
query_mdns_service("_nfs", "_tcp");
|
||||
}
|
||||
old_level = new_level;
|
||||
}
|
||||
|
||||
static void mdns_example_task(void *pvParameters)
|
||||
{
|
||||
#if CONFIG_MDNS_RESOLVE_TEST_SERVICES == 1
|
||||
/* Send initial queries that are started by CI tester */
|
||||
query_mdns_host("tinytester");
|
||||
query_mdns_host_with_gethostbyname("tinytester-lwip.local");
|
||||
query_mdns_host_with_getaddrinfo("tinytester-lwip.local");
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
check_button();
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
initialise_mdns();
|
||||
|
||||
/* 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());
|
||||
|
||||
#if defined(CONFIG_MDNS_ADD_CUSTOM_NETIF) && !defined(CONFIG_MDNS_PREDEF_NETIF_STA) && !defined(CONFIG_MDNS_PREDEF_NETIF_ETH)
|
||||
/* Demonstration of adding a custom netif to mdns service, but we're adding the default example one,
|
||||
* so we must disable all predefined interfaces (PREDEF_NETIF_STA, AP and ETH) first
|
||||
*/
|
||||
ESP_ERROR_CHECK(mdns_register_netif(EXAMPLE_INTERFACE));
|
||||
/* It is not enough to just register the interface, we have to enable is manually.
|
||||
* 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));
|
||||
#endif
|
||||
initialise_button();
|
||||
xTaskCreate(&mdns_example_task, "mdns_example_task", 2048, NULL, 5, NULL);
|
||||
}
|
||||
|
||||
/** Generate host name based on sdkconfig, optionally adding a portion of MAC address to it.
|
||||
* @return host name string allocated from the heap
|
||||
*/
|
||||
static char* generate_hostname(void)
|
||||
{
|
||||
#ifndef CONFIG_MDNS_ADD_MAC_TO_HOSTNAME
|
||||
return strdup(CONFIG_MDNS_HOSTNAME);
|
||||
#else
|
||||
uint8_t mac[6];
|
||||
char *hostname;
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
if (-1 == asprintf(&hostname, "%s-%02X%02X%02X", CONFIG_MDNS_HOSTNAME, mac[3], mac[4], mac[5])) {
|
||||
abort();
|
||||
}
|
||||
return hostname;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CONFIG_MDNS_RESOLVE_TEST_SERVICES == 1
|
||||
/**
|
||||
* @brief Executes gethostbyname and displays list of resolved addresses.
|
||||
* Note: This function is used only to test advertised mdns hostnames resolution
|
||||
*/
|
||||
static void query_mdns_host_with_gethostbyname(char * host)
|
||||
{
|
||||
struct hostent *res = gethostbyname(host);
|
||||
if (res) {
|
||||
unsigned int i = 0;
|
||||
while (res->h_addr_list[i] != NULL) {
|
||||
ESP_LOGI(TAG, "gethostbyname: %s resolved to: %s", host, inet_ntoa(*(struct in_addr *) (res->h_addr_list[i])));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executes getaddrinfo and displays list of resolved addresses.
|
||||
* Note: This function is used only to test advertised mdns hostnames resolution
|
||||
*/
|
||||
static void query_mdns_host_with_getaddrinfo(char * host)
|
||||
{
|
||||
struct addrinfo hints;
|
||||
struct addrinfo * res;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if (!getaddrinfo(host, NULL, &hints, &res)) {
|
||||
while (res) {
|
||||
ESP_LOGI(TAG, "getaddrinfo: %s resolved to: %s", host,
|
||||
res->ai_family == AF_INET?
|
||||
inet_ntoa(((struct sockaddr_in *) res->ai_addr)->sin_addr):
|
||||
inet_ntoa(((struct sockaddr_in6 *) res->ai_addr)->sin6_addr));
|
||||
res = res->ai_next;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
166
components/mdns/examples/mdns_example_test.py
Normal file
166
components/mdns/examples/mdns_example_test.py
Normal file
@ -0,0 +1,166 @@
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import time
|
||||
from threading import Event, Thread
|
||||
|
||||
import dpkt
|
||||
import dpkt.dns
|
||||
import ttfw_idf
|
||||
from tiny_test_fw import DUT
|
||||
from tiny_test_fw.Utility import console_log
|
||||
|
||||
|
||||
def get_dns_query_for_esp(esp_host):
|
||||
dns = dpkt.dns.DNS(b'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01')
|
||||
dns.qd[0].name = esp_host + u'.local'
|
||||
console_log('Created query for esp host: {} '.format(dns.__repr__()))
|
||||
return dns.pack()
|
||||
|
||||
|
||||
def get_dns_answer_to_mdns(tester_host):
|
||||
dns = dpkt.dns.DNS(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA
|
||||
dns.rcode = dpkt.dns.DNS_RCODE_NOERR
|
||||
arr = dpkt.dns.DNS.RR()
|
||||
arr.cls = dpkt.dns.DNS_IN
|
||||
arr.type = dpkt.dns.DNS_A
|
||||
arr.name = tester_host
|
||||
arr.ip = socket.inet_aton('127.0.0.1')
|
||||
dns.an.append(arr)
|
||||
console_log('Created answer to mdns query: {} '.format(dns.__repr__()))
|
||||
return dns.pack()
|
||||
|
||||
|
||||
def get_dns_answer_to_mdns_lwip(tester_host, id):
|
||||
dns = dpkt.dns.DNS(b'\x5e\x39\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x0a\x64\x61\x76\x69\x64'
|
||||
b'\x2d\x63\x6f\x6d\x70\x05\x6c\x6f\x63\x61\x6c\x00\x00\x01\x00\x01\xc0\x0c'
|
||||
b'\x00\x01\x00\x01\x00\x00\x00\x0a\x00\x04\xc0\xa8\x0a\x6c')
|
||||
dns.qd[0].name = tester_host
|
||||
dns.an[0].name = tester_host
|
||||
dns.an[0].ip = socket.inet_aton('127.0.0.1')
|
||||
dns.an[0].rdata = socket.inet_aton('127.0.0.1')
|
||||
dns.id = id
|
||||
print('Created answer to mdns (lwip) query: {} '.format(dns.__repr__()))
|
||||
return dns.pack()
|
||||
|
||||
|
||||
def mdns_server(esp_host, events):
|
||||
UDP_IP = '0.0.0.0'
|
||||
UDP_PORT = 5353
|
||||
MCAST_GRP = '224.0.0.251'
|
||||
TESTER_NAME = u'tinytester.local'
|
||||
TESTER_NAME_LWIP = u'tinytester-lwip.local'
|
||||
QUERY_TIMEOUT = 0.2
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
sock.setblocking(False)
|
||||
sock.bind((UDP_IP, UDP_PORT))
|
||||
mreq = struct.pack('4sl', socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
|
||||
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
||||
last_query_timepoint = time.time()
|
||||
while not events['stop'].is_set():
|
||||
try:
|
||||
current_time = time.time()
|
||||
if current_time - last_query_timepoint > QUERY_TIMEOUT:
|
||||
last_query_timepoint = current_time
|
||||
if not events['esp_answered'].is_set():
|
||||
sock.sendto(get_dns_query_for_esp(esp_host), (MCAST_GRP, UDP_PORT))
|
||||
if not events['esp_delegated_answered'].is_set():
|
||||
sock.sendto(get_dns_query_for_esp(esp_host + '-delegated'), (MCAST_GRP, UDP_PORT))
|
||||
timeout = max(0, QUERY_TIMEOUT - (current_time - last_query_timepoint))
|
||||
read_socks, _, _ = select.select([sock], [], [], timeout)
|
||||
if not read_socks:
|
||||
continue
|
||||
data, addr = sock.recvfrom(1024)
|
||||
dns = dpkt.dns.DNS(data)
|
||||
if len(dns.qd) > 0 and dns.qd[0].type == dpkt.dns.DNS_A:
|
||||
if dns.qd[0].name == TESTER_NAME:
|
||||
console_log('Received query: {} '.format(dns.__repr__()))
|
||||
sock.sendto(get_dns_answer_to_mdns(TESTER_NAME), (MCAST_GRP, UDP_PORT))
|
||||
elif dns.qd[0].name == TESTER_NAME_LWIP:
|
||||
console_log('Received query: {} '.format(dns.__repr__()))
|
||||
sock.sendto(get_dns_answer_to_mdns_lwip(TESTER_NAME_LWIP, dns.id), addr)
|
||||
if len(dns.an) > 0 and dns.an[0].type == dpkt.dns.DNS_A:
|
||||
console_log('Received answer from {}'.format(dns.an[0].name))
|
||||
if dns.an[0].name == esp_host + u'.local':
|
||||
console_log('Received answer to esp32-mdns query: {}'.format(dns.__repr__()))
|
||||
events['esp_answered'].set()
|
||||
if dns.an[0].name == esp_host + u'-delegated.local':
|
||||
console_log('Received answer to esp32-mdns-delegate query: {}'.format(dns.__repr__()))
|
||||
events['esp_delegated_answered'].set()
|
||||
except socket.timeout:
|
||||
break
|
||||
except dpkt.UnpackError:
|
||||
continue
|
||||
|
||||
|
||||
def test_examples_protocol_mdns(env, config):
|
||||
"""
|
||||
steps: |
|
||||
1. obtain IP address + init mdns example
|
||||
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
|
||||
"""
|
||||
dut1 = env.get_dut('mdns-test', 'examples/protocols/mdns', dut_class=ttfw_idf.ESP32DUT, app_config_name=config)
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, 'mdns_test.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance('mdns-test_bin_size', '{}KB'.format(bin_size // 1024))
|
||||
# 1. start mdns application
|
||||
dut1.start_app()
|
||||
# 2. get the dut host name (and IP address)
|
||||
specific_host = dut1.expect(re.compile(r'mdns hostname set to: \[([^\]]+)\]'), timeout=30)[0]
|
||||
|
||||
mdns_server_events = {'stop': Event(), 'esp_answered': Event(), 'esp_delegated_answered': Event()}
|
||||
mdns_responder = Thread(target=mdns_server, args=(str(specific_host), mdns_server_events))
|
||||
try:
|
||||
ip_address = dut1.expect(re.compile(r' eth ip: ([^,]+),'), timeout=30)[0]
|
||||
console_log('Connected to AP with IP: {}'.format(ip_address))
|
||||
except DUT.ExpectTimeout:
|
||||
raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP')
|
||||
try:
|
||||
# 3. check the mdns name is accessible
|
||||
mdns_responder.start()
|
||||
if not mdns_server_events['esp_answered'].wait(timeout=30):
|
||||
raise ValueError('Test has failed: did not receive mdns answer within timeout')
|
||||
if not mdns_server_events['esp_delegated_answered'].wait(timeout=30):
|
||||
raise ValueError('Test has failed: did not receive mdns answer for delegated host within timeout')
|
||||
# 4. check DUT output if mdns advertized host is resolved
|
||||
dut1.expect(re.compile(r'mdns-test: Query A: tinytester.local resolved to: 127.0.0.1'), timeout=30)
|
||||
dut1.expect(re.compile(r'mdns-test: gethostbyname: tinytester-lwip.local resolved to: 127.0.0.1'), timeout=30)
|
||||
dut1.expect(re.compile(r'mdns-test: getaddrinfo: tinytester-lwip.local resolved to: 127.0.0.1'), timeout=30)
|
||||
# 5. check the DUT answers to `dig` command
|
||||
dig_output = subprocess.check_output(['dig', '+short', '-p', '5353', '@224.0.0.251',
|
||||
'{}.local'.format(specific_host)])
|
||||
console_log('Resolving {} using "dig" succeeded with:\n{}'.format(specific_host, dig_output))
|
||||
if not ip_address.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))
|
||||
finally:
|
||||
mdns_server_events['stop'].set()
|
||||
mdns_responder.join()
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
|
||||
def test_examples_protocol_mdns_default(env, _):
|
||||
test_examples_protocol_mdns(env, 'eth_def')
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
|
||||
def test_examples_protocol_mdns_socket(env, _):
|
||||
test_examples_protocol_mdns(env, 'eth_socket')
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
|
||||
def test_examples_protocol_mdns_custom_netif(env, _):
|
||||
test_examples_protocol_mdns(env, 'eth_custom_netif')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_mdns_default()
|
19
components/mdns/examples/sdkconfig.ci.eth_custom_netif
Normal file
19
components/mdns/examples/sdkconfig.ci.eth_custom_netif
Normal file
@ -0,0 +1,19 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
CONFIG_MDNS_RESOLVE_TEST_SERVICES=y
|
||||
CONFIG_MDNS_ADD_MAC_TO_HOSTNAME=y
|
||||
CONFIG_MDNS_PUBLISH_DELEGATE_HOST=y
|
||||
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_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_IP101=y
|
||||
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=y
|
||||
CONFIG_MDNS_BUTTON_GPIO=32
|
15
components/mdns/examples/sdkconfig.ci.eth_def
Normal file
15
components/mdns/examples/sdkconfig.ci.eth_def
Normal file
@ -0,0 +1,15 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
CONFIG_MDNS_RESOLVE_TEST_SERVICES=y
|
||||
CONFIG_MDNS_ADD_MAC_TO_HOSTNAME=y
|
||||
CONFIG_MDNS_PUBLISH_DELEGATE_HOST=y
|
||||
CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_IP101=y
|
||||
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=y
|
||||
CONFIG_MDNS_BUTTON_GPIO=32
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user