Compare commits

..

96 Commits

Author SHA1 Message Date
d63f831fff Merge pull request #466 from gabsuren/websocket_bump_1.2.2
bump(websocket): 1.2.1 -> 1.2.2
2024-01-19 19:27:37 +04:00
f62db5cfa2 Merge pull request #492 from euripedesrocha/mqttcxx_0_3_0
bump(mqtt_cxx): 0.2.0 -> 0.3.0
2024-01-19 08:42:52 -03:00
60174f290e bump(mqtt_cxx): 0.2.0 -> 0.3.0
0.3.0
Bug Fixes
- Incorrect documentation link (aa4e9d57)
- reference protocol_examples_common from IDF (e6c0538d)
- specify override_path in example manifest files (fa005c63)
2024-01-19 09:43:07 +01:00
efa26b7062 Merge pull request #484 from david-cermak/fix/modem_docs_ota
fix(modem): Documented some tips on failing OTA
2024-01-18 17:43:03 +01:00
7b777948fc Merge pull request #487 from euripedesrocha/mqttcxx_link_fix
Incorrect documentation link
2024-01-18 05:10:23 -03:00
09e68cc0c0 Merge pull request #490 from david-cermak/fix/mdns_eth
fix(mdns): Correction on 6d2c475 MDNS_PREDEF_NETIF_ETH fix
2024-01-17 12:53:14 +01:00
fc59f87c4e fix(mdns): Correction on 6d2c475 MDNS_PREDEF_NETIF_ETH fix 2024-01-17 11:51:17 +01:00
7a2b23909f Merge pull request #489 from espressif/revert-478-component/console_cmd_iperf
Revert "feat(console): Added component with iperf command"
2024-01-17 10:24:17 +01:00
d3bf773445 Revert "feat(console): Added component with iperf command" 2024-01-17 20:20:30 +11:00
1c29af9237 Merge pull request #472 from david-cermak/fix/modem_at_corrupt_response
fix(modem): Fixed AT commands to copy strings to prevent overrides
2024-01-17 09:48:24 +01:00
b53981a68c Merge pull request #479 from marekmaskarinec/master
fix(mdns): Fix compile error when MDNS_PREDEF_NETIF_ETH is defined, but ETH_ENABLED is not (IDFGH-11868)
2024-01-17 09:48:05 +01:00
de8ec67a88 Merge pull request #478 from espressif-abhikroy/component/console_cmd_iperf
feat(console): Added component with iperf command
2024-01-17 00:53:56 +11:00
8dac30781c Merge pull request #488 from gabsuren/fix_docs
docs(common): Added package requirements.txt for esp-docs
2024-01-16 16:59:05 +04:00
ba3f06f942 docs(common): Added package requirements.txt for esp-docs 2024-01-16 16:57:58 +04:00
a10f0008fb bump(console): First version [1.0.0]
1.0.0
Features
- Added component with iperf command (93d14087)
2024-01-16 20:27:56 +11:00
741d166034 fix(modem): Fixed AT commands to copy strings to prevent overrides
Previously we used std::string_view, which pointed to the lower-layers
buffer which might have been reused for other asynchronous operations
before processing it, thus causing corruption of replies.

Closes https://github.com/espressif/esp-protocols/issues/463
2024-01-16 09:51:51 +01:00
93d140875f feat(console): Added component with iperf command 2024-01-16 19:18:01 +11:00
eb7699388c bump(websocket): 1.2.1 -> 1.2.2
1.2.2
Bug Fixes
- continuation after FIN in websocket client (#460) (774d1c75e6)
- Re-applie refs to common comps idf_component.yml (9fe44a4504)
2024-01-16 12:17:25 +04:00
585e4b30b2 Merge pull request #483 from espressif-abhikroy/ci/build_app_failure_fix
fix(common): Fixed scripts for build test failure
2024-01-16 09:07:14 +01:00
aa4e9d5795 fix(mqtt_cxx): Incorrect documentation link
- Link in the component README was incorrect.

Fixes #486
2024-01-16 08:41:55 +01:00
6d2c475c20 fix(mdns): Fix compile error when MDNS_PREDEF_NETIF_ETH is defined, but ETH_ENABLED is not (#459)
Signed-off-by: Marek Maškarinec <marek@mrms.cz>
2024-01-15 16:50:44 +01:00
47736a2556 fix(common): Fixed scripts for build test failure 2024-01-16 01:59:11 +11:00
adde6df6e8 fix(modem): Documented some tips on failing OTA
Closes https://github.com/espressif/esp-protocols/issues/482
2024-01-15 14:58:03 +01:00
1393764dc5 Merge pull request #477 from david-cermak/feat/modem_at_raw
feat(modem): Added support for at_raw() command
2024-01-15 12:31:18 +01:00
0998f3dd4f fix(modem): Fixed incorrect dial command format
ATD command is used to switch generic device to the data mode
by dialing GPRS_SRC. The generic version of the command was incorrect
`ATD*99##` (containing extra trailing # char, which is accepted by most
devices). Command has been fixed, but the extra #'s been kept for devices
that we cannot test (to be fixed in IDFGH-9470).

Closes https://github.com/espressif/esp-protocols/issues/433
2024-01-09 12:48:16 +01:00
577de67c0e fix(modem): Fixed documentation and example on creating custom device
Updates docs and examples per recent changes:
* Modem example does no longer demonstrate how to overwrite an AT method
* PPPoS client example now contains much simpler custom module
definition

Closes https://github.com/espressif/esp-protocols/issues/452
2024-01-09 10:26:39 +01:00
ae38110d84 feat(modem): Added support for at_raw() command
at_raw() sends raw string and supports custom pass/fail phrases for the
API to return OK/FAIL. It also returns raw, unprocessed string as
output.
This allows sending custom requests, such as:
* dce->at_raw("", resp, "RDY", "...", 20000) -- waiting for reset
* esp_modem_at_raw(dce, "+++", resp, "DISCONNECTED", "ERROR", 5000) --
exits PPP mode

Closes https://github.com/espressif/esp-protocols/issues/471
Partially addresses https://github.com/espressif/esp-protocols/issues/468
2024-01-09 08:20:10 +01:00
5ab699d6f4 Merge pull request #354 from david-cermak/feat/modem_iperf_test
feat(modem): Add iperf test of PPP netif
2024-01-08 16:48:02 +01:00
976e98d6ff feat(modem): Added iperf test for PPP netifs 2024-01-08 16:22:22 +01:00
18b2ae103a Merge pull request #476 from david-cermak/fix/ci_examples
fix(examples): Fixed esp_netif_next() warning with IDFv5.3
2024-01-08 12:48:30 +01:00
52a34c21d0 fix(examples): Fixed esp_netif_next() warning with IDFv5.3 2024-01-08 08:02:20 +01:00
9df86641f3 Merge pull request #475 from gabsuren/bump_mdns__v1.2.3
bump(mdns): 1.2.2 -> 1.2.3
2024-01-05 14:22:25 +04:00
75af01670e bump(mdns): 1.2.2 -> 1.2.3
1.2.3
Bug Fixes
- fixed CI issues for host and afl tests (4be5efc84e)
- fix copy delegated host addr (19fb36000c)
- enable CONFIG_ESP_WIFI_ENABLED if CONFIG_SOC_WIFI_SUPPORTED is also enabled (for ESP-IDF <= 5.1) (d20a718320)
- remove protocol_examples_common (1ee9dae6bf)
- move the example into a subdirectory (d28232b9f8)
- reference protocol_examples_common from IDF (c83b76ea8f)
2024-01-05 14:00:34 +04:00
bf79e2f734 Merge pull request #465 from espressif-abhikroy/component/ifconfig_bug_fix
fix(console): Fixed ifconfig command for PPP interface and IPv4 only
2024-01-05 19:36:46 +11:00
a22c9e8da9 bump(console): 1.0.0 -> 1.0.1
1.0.1
Bug Fixes
- Fixed ifconfig command for PPP interface and IPv4 only (8548dabb)
2024-01-05 19:27:16 +11:00
8548dabbd6 fix(console): Fixed ifconfig command for PPP interface and IPv4 only 2024-01-05 19:23:52 +11:00
d2e4370d30 Merge pull request #469 from DejinChen/component/mdns_host_address_fix
fix(mdns): fix copy delegated host addr (IDFGH-11829)
2024-01-04 21:13:58 +04:00
e4c353760c Merge pull request #473 from gabsuren/mdns_fix_kconfig
fix(mdns): fixed CI issues for host and afl tests
2024-01-04 21:12:46 +04:00
4be5efc84e fix(mdns): fixed CI issues for host and afl tests 2024-01-04 17:46:18 +04:00
7451ec2a75 Merge pull request #363 from david-cermak/feat/modem_test_ota
feat(modem): Add OTA test to exercise modem layers
2024-01-03 21:27:36 +01:00
19fb36000c fix(mdns): fix copy delegated host addr 2024-01-03 17:12:41 +08:00
7d0eb5c48c Merge pull request #467 from espressif-abhikroy/component/console_cmd_wifi
fix(console): Fixed license file for console_cmd_wifi
2024-01-03 18:59:32 +11:00
a0a58e60b5 bump(console): 1.0.0 -> 1.0.1
1.0.1
Features
- Console for runtime wifi configuration (194d1179)
Bug Fixes
- Fixed license file for console_cmd_wifi (8285e973)
2023-12-23 21:19:53 +11:00
8285e9730f fix(console): Fixed license file for console_cmd_wifi 2023-12-23 21:19:30 +11:00
cf845cc23e Merge pull request #453 from espressif-abhikroy/component/console_cmd_wifi
feat(console): Console for runtime wifi configuration
2023-12-22 23:54:09 +11:00
a532e76ec1 bump(console): First version [1.0.0]
1.0.0
Features
- Console for runtime wifi configuration (194d1179)
2023-12-22 23:48:44 +11:00
194d1179bf feat(console): Console for runtime wifi configuration 2023-12-22 23:44:16 +11:00
aadda22961 Merge pull request #461 from 0xFEEDC0DE64/fix-websocket-client-fin
fix(websocket): continuation after FIN in websocket client (#460) (IDFGH-11761)
2023-12-22 14:45:51 +04:00
a6845f4cdd Merge pull request #456 from david-cermak/fix/modem_pdp_context_timeout
fix(modem): Fixed set_pdp_context() command timeout
2023-12-21 07:21:31 +01:00
d88cd6123b fix(modem): Removed CI jobs for IDF v4.2 2023-12-19 17:01:51 +01:00
10bb738911 fix(modem): Added CI jobs to build all tests for all targets 2023-12-19 17:01:51 +01:00
edc3e7251f fix(modem): Fixed OAT test to verify server cert and CN 2023-12-19 17:01:30 +01:00
774d1c75e6 fix: continuation after FIN in websocket client (#460) 2023-12-19 17:00:17 +01:00
f2223dd719 feat(modem): Added test that performs OTA to exercise modem layers 2023-12-19 16:55:34 +01:00
1d80cbc179 fix(modem): Fixed set_pdp_context() command timeout
Closes https://github.com/espressif/esp-protocols/issues/455
2023-12-19 16:43:59 +01:00
82c2cf8936 Merge pull request #451 from david-cermak/bump/modem_1.0.5
bump(modem): 1.0.4 -> 1.0.5
2023-12-06 09:54:37 +01:00
997cff5338 bump(modem): 1.0.4 -> 1.0.5
1.0.5
Major changes
- Added support for implementing user defined modules in standard C-API (Support for custom modules with C-API)
Bug Fixes
- Added test injecting unexpected replies (b220d1e, #426)
- Fixed inconsistent state on data after OK (bf99f28, #426)
- TLS example: Added restore session support in mbedtls-wrap (79d38e5)
- Fixed examples to show netif on ppp-changed event (a70b197)
- remove unused GNU Make based buildsystem files (d6b6f63)
- specify override_path in example manifest files (5b78da4)
- Added test-cases that exercise mode transitions (aff571d)
- Fixed mode transitions between any state and UNDEF (93cb2ca, #320)
- Fixed API docs within doxygen comments (020b407)
- Support for custom modules with C-API (0254d50)
- Fix CRLF issue with esp_modem_c_api.cpp (2661b4d)
Updated
- ci(common): Created reusable action for host and coverage tests (9ad04de)
2023-12-06 09:22:39 +01:00
ed8bbf2666 Merge pull request #447 from david-cermak/fix/modem_command_state_consistent_test
fix(modem): Added test injecting unexpected replies
2023-12-06 09:20:27 +01:00
b220d1ee5e fix(modem): Added test injecting unexpected replies
This test exhibits an issue of getting unexpected data just after
completing a command. The DTE rewrites the state to TIMEOUT, but the
signal has been already triggered, thus results in an unexpected state.

Related to Closes https://github.com/espressif/esp-protocols/issues/426
2023-12-06 09:09:47 +01:00
671190bc6d Merge pull request #446 from david-cermak/fix/modem_command_state_consistency
fix(modem): Fixed inconsistent state on data after OK
2023-12-06 09:06:23 +01:00
1bf6843b3e Merge pull request #332 from espressif-abhikroy/components/console_cmd_ifconfig
Added component: Console for runtime network interface configuration
2023-11-30 08:23:15 +01:00
b78569860d Merge pull request #438 from espressif-abhikroy/components/console_cmd_ping
bump(console): First version [1.0.0]
2023-11-30 08:19:43 +01:00
27f955abfa bump(console): First version [1.0.0]
1.0.0
Features
- Added ping command to console component (7babdeb9)
2023-11-30 18:14:59 +11:00
bf99f287bc fix(modem): Fixed inconsistent state on data after OK
If we received response in two chunks and the first one completes the
command (result=OK or FAIL), and the second chunk pre-empts command processing,
then we performed processing again rewritting the result back to
TIMEOUT. This would invalidate the command, but also causes an
exception: ESP_MODEM_THROW_IF_ERROR(ESP_ERR_INVALID_STATE)
Fixed by checking if the processing already finished in process_line().

Closes https://github.com/espressif/esp-protocols/issues/426
2023-11-29 20:24:53 +01:00
909e8d9494 Merge pull request #441 from david-cermak/fix/apply_examples_idf_component_refs
fix(common): Re-applied refs to common comps idf_component.yml
2023-11-28 17:44:13 +01:00
0c6bb4bedc Merge pull request #360 from david-cermak/fix/modem_example_mbedtls_wrapper
fix(modem): TLS example: Add restore session support in mbedtls wrapper
2023-11-28 17:12:10 +01:00
afbca5343e Merge pull request #416 from david-cermak/fix/modem_undef_mode
fix(modem): Fixed mode transitions between any state and UNDEF
2023-11-28 17:11:43 +01:00
3f49583761 Merge pull request #443 from gabsuren/websocket_1.2.1
bump(websocket): 1.2.0 -> 1.2.1
2023-11-28 17:20:14 +04:00
c6448c3bd1 bump(websocket): 1.2.0 -> 1.2.1
1.2.1
Bug Fixes
- consider failure if return value of `esp_websocket_client_send_with_exact_opcode` less than 0 (f523b4d)
- fix of return value for `esp_websocket_client_send_with_opcode` API (ba33588)
2023-11-28 16:42:55 +04:00
f523b4dc84 fix(websocket): consider failure if return value of esp_websocket_client_send_with_exact_opcode less than 0 2023-11-28 16:40:10 +04:00
6fda4c134e Merge pull request #442 from gabsuren/fix/websocket_return_opcode
fix(websocket): fix of return value for `esp_websocket_client_send_with_opcode` API (IDF-8745)
2023-11-28 15:51:27 +04:00
ba33588008 fix(websocket): fix of return value for esp_websocket_client_send_with_opcode API 2023-11-28 12:37:31 +04:00
9fe44a4504 fix(common): Re-applie refs to common comps idf_component.yml
This reverts commit 74fc228cdf.
This reverts commit b176d3abbb.
This reverts commit f9e0281a04.
2023-11-28 09:32:52 +01:00
9758379921 Merge pull request #440 from david-cermak/fix/revert_example_common_comp_refs
Temporarily revert IDF component references in examples
2023-11-28 08:15:28 +01:00
f52a38ba34 bump(websocket): 1.1.0 -> 1.2.0
1.2.0
Features
- Added new API `esp_websocket_client_append_header` (39e9725)
- Added new APIs to support fragmented messages transmission (fae80e2)
Bug Fixes
- Reference common component from IDF (74fc228)
- Revert referencing protocol_examples_common from IDF (b176d3a)
- reference protocol_examples_common from IDF (025ede1)
- specify override_path in example manifests (d5e7898)
- Return status code correctly on esp_websocket_client_send_with_opcode (ac8f1de)
- Fix pytest exclusion, gitignore, and changelog checks (2696221)
2023-11-28 07:42:49 +01:00
74fc228cdf fix(common): Reference common component from IDF 2023-11-28 07:42:28 +01:00
b176d3abbb fix(websocket): Revert referencing protocol_examples_common from IDF
This reverts commit 025ede12e8.
2023-11-28 07:23:32 +01:00
f9e0281a04 fix(asio): Revert referencing protocol_examples_common from IDF
This reverts commit 09abb18be6.
2023-11-28 07:21:56 +01:00
76a4a9f97a bump(console): First version [1.0.0]
1.0.0
Features
- Console for runtime network interface configuration and monitoring (8bab1420)
2023-11-24 22:12:33 +11:00
8bab1420d3 feat(console): Console for runtime network interface configuration and monitoring 2023-11-24 20:07:33 +11:00
9c555b90d4 Merge pull request #385 from espressif-abhikroy/components/console_cmd_ping
feat(console): Added ping command to console component
2023-11-23 19:58:33 +11:00
79d38e54f2 fix(modem): TLS example: Added restore session support in mbedtls-wrap
Reusable component in modem_tcp_client example implements a simple
mbedtls wrapper. This update add support for mbedtls deinit() and for
saving and restoring TLS session.
2023-11-23 09:56:01 +01:00
7babdeb970 feat(console): Added ping command to console component 2023-11-23 01:17:43 +11:00
7faa97450b Merge pull request #434 from david-cermak/bugfix/modem_examples_print_netif
fix(modem): Fixed examples to show netif on ppp-changed event
2023-11-16 17:52:03 +01:00
cc40cc1f84 Merge pull request #413 from david-cermak/feat/new_modem_cmd
Added support for implementing user defined modules in standard C-API
2023-11-16 17:51:28 +01:00
a70b197d02 fix(modem): Fixed examples to show netif on ppp-changed event 2023-11-16 10:10:02 +01:00
f50edd40b5 Merge pull request #425 from euripedesrocha/asio_contributing_guide
bump(asio): 1.28.0~0 -> 1.28.2~0
2023-11-14 14:50:47 +01:00
28f1e430fd bump(asio): 1.28.0~0 -> 1.28.2~0
1.28.2~0

Bug Fixes
- reference protocol_examples_common from IDF ([09abb18](https://github.com/espressif/esp-protocols/commit/09abb18))
- specify override_path in example manifest files ([1d8923c](https://github.com/espressif/esp-protocols/commit/1d8923c))

Updated
- docs(asio): Updates asio docs ([ce9337d](https://github.com/espressif/esp-protocols/commit/ce9337d))
2023-11-14 13:48:12 +01:00
93f5f7bf58 Merge pull request #427 from gabsuren/fix/mdns_config
mdns: enable CONFIG_ESP_WIFI_ENABLED if CONFIG_SOC_WIFI_SUPPORTED is also enabled (for ESP-IDF <= 5.1)
2023-11-14 16:24:10 +04:00
d20a718320 fix(mdns): enable CONFIG_ESP_WIFI_ENABLED if CONFIG_SOC_WIFI_SUPPORTED is also enabled (for ESP-IDF <= 5.1) 2023-11-14 14:48:42 +04:00
5396de42d0 docs(modem): Added documentation about mode transitions
Closes https://github.com/espressif/esp-protocols/issues/318
2023-11-07 12:35:06 +01:00
aff571df2c fix(modem): Added test-cases that exercise mode transitions 2023-11-07 12:35:06 +01:00
93cb2caadb fix(modem): Fixed mode transitions between any state and UNDEF
Closes https://github.com/espressif/esp-protocols/issues/320
2023-11-07 12:35:00 +01:00
020b407472 fix(modem): Fixed API docs within doxygen comments 2023-11-02 15:46:38 +01:00
0254d50128 fix(modem): Support for custom modules with C-API
MAJOR CHANGE: Added support for implementing user defined modules in standard C-API
2023-11-02 13:35:07 +01:00
2661b4d28c fix(modem): Fix CRLF issue with esp_modem_c_api.cpp 2023-11-02 13:35:07 +01:00
127 changed files with 5590 additions and 586 deletions

View File

@ -0,0 +1,32 @@
name: "console_cmd_ifconfig: build-tests"
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
build_console_cmd_ifconfig:
if: contains(github.event.pull_request.labels.*.name, 'console') || github.event_name == 'push'
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.0"]
idf_target: ["esp32"]
test: [ { app: ifconfig-basic, path: "components/console_cmd_ifconfig/examples"}]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
with:
submodules: recursive
- name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
shell: bash
working-directory: ${{matrix.test.path}}
run: |
${IDF_PATH}/install.sh --enable-pytest
. ${IDF_PATH}/export.sh
python ../../../ci/build_apps.py ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app

View File

@ -0,0 +1,32 @@
name: "console_cmd_ping: build-tests"
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
build_console_cmd_ping:
if: contains(github.event.pull_request.labels.*.name, 'console') || github.event_name == 'push'
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.0"]
idf_target: ["esp32"]
test: [ { app: ping-basic, path: "components/console_cmd_ping/examples" }]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
with:
submodules: recursive
- name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
shell: bash
working-directory: ${{matrix.test.path}}
run: |
${IDF_PATH}/install.sh --enable-pytest
. ${IDF_PATH}/export.sh
python ../../../ci/build_apps.py ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app

View File

@ -0,0 +1,32 @@
name: "console_cmd_wifi: build-tests"
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
build_console_cmd_wifi:
if: contains(github.event.pull_request.labels.*.name, 'console') || github.event_name == 'push'
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.0"]
idf_target: ["esp32"]
test: [ { app: wifi-basic, path: "components/console_cmd_wifi/examples" }]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
with:
submodules: recursive
- name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
shell: bash
working-directory: ${{matrix.test.path}}
run: |
${IDF_PATH}/install.sh --enable-pytest
. ${IDF_PATH}/export.sh
python ../../../ci/build_apps.py ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app

View File

@ -15,7 +15,7 @@ jobs:
matrix:
idf_ver: ["latest", "release-v5.0"]
idf_target: ["esp32"]
test: [ { app: example, path: "components/console_simple_init/examples" }]
test: [ { app: console_basic, path: "components/console_simple_init/examples" }]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
@ -29,4 +29,4 @@ jobs:
run: |
${IDF_PATH}/install.sh --enable-pytest
. ${IDF_PATH}/export.sh
python $IDF_PATH/tools/ci/ci_build_apps.py . --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app
python ../../../ci/build_apps.py ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app

View File

@ -8,25 +8,19 @@ on:
types: [opened, synchronize, reopened, labeled]
jobs:
build_esp_modem:
build_esp_modem_examples:
if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push'
name: Build
name: Build examples
strategy:
matrix:
idf_ver: ["latest", "release-v4.2", "release-v4.3", "release-v4.4", "release-v5.0"]
idf_ver: ["latest", "release-v4.3", "release-v4.4", "release-v5.0"]
example: ["pppos_client", "modem_console", "modem_tcp_client", "ap_to_pppos", "simple_cmux_client"]
exclude:
- idf_ver: "release-v4.2"
example: simple_cmux_client
- idf_ver: "release-v4.2"
example: modem_tcp_client
- idf_ver: "release-v4.3"
example: modem_tcp_client
- idf_ver: "release-v4.4"
example: modem_tcp_client
include:
- idf_ver: "release-v4.2"
skip_config: usb
- idf_ver: "release-v4.3"
skip_config: usb
- idf_ver: "release-v5.0"
@ -50,7 +44,33 @@ jobs:
. ${IDF_PATH}/export.sh
python -m pip install idf-build-apps
cd $GITHUB_WORKSPACE/protocols
python ./ci/build_apps.py components/esp_modem/examples/${{ matrix.example }} -m components/esp_modem/examples/.build-test-rules.yml
python ./ci/build_apps.py components/esp_modem/examples/${{ matrix.example }} -m components/esp_modem/.build-test-rules.yml
build_esp_modem_tests:
if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push'
name: Build tests
strategy:
matrix:
idf_ver: ["release-v5.0", "release-v5.1", "latest"]
test: ["target", "target_ota", "target_iperf"]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
with:
path: protocols
- name: Build ${{ matrix.test }} with IDF-${{ matrix.idf_ver }}
env:
EXPECTED_WARNING: ${{ matrix.warning }}
shell: bash
run: |
. ${IDF_PATH}/export.sh
python -m pip install idf-build-apps
cd $GITHUB_WORKSPACE/protocols
python ./ci/build_apps.py components/esp_modem/test/${{ matrix.test }} -m components/esp_modem/.build-test-rules.yml
host_test_esp_modem:
if: contains(github.event.pull_request.labels.*.name, 'modem') || github.event_name == 'push'

View File

@ -15,7 +15,7 @@ jobs:
matrix:
idf_ver: ["latest", "release-v5.0"]
idf_target: ["esp32"]
test: [ { app: example, path: "components/esp_mqtt_cxx/examples" }]
test: [ { app: mqtt-basic, path: "components/esp_mqtt_cxx/examples" }]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
@ -29,4 +29,4 @@ jobs:
run: |
${IDF_PATH}/install.sh --enable-pytest
. ${IDF_PATH}/export.sh
python $IDF_PATH/tools/ci/ci_build_apps.py . --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app
python ../../../ci/build_apps.py ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app

View File

@ -48,7 +48,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get -y install doxygen clang python3-pip
python -m pip install breathe recommonmark esp-docs==1.4.1
python -m pip install -r docs/requirements.txt
for comp in `ls components`; do
if [[ -d $GITHUB_WORKSPACE/docs/${comp} ]]; then
cd $GITHUB_WORKSPACE/docs/${comp}
@ -94,5 +94,8 @@ jobs:
components/esp_websocket_client;
components/mdns;
components/console_simple_init;
components/console_cmd_ping;
components/console_cmd_ifconfig;
components/console_cmd_wifi;
namespace: "espressif"
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}

View File

@ -29,9 +29,8 @@ e.g.
## Creating a new component
Steps:
1. Add a file named .cz.yaml to the root of the component.
Once the commit containing a new component is ready follow the steps below:
1. Add a file named .cz.yaml to the root of the component and add it to your commit.
The template for .cz.yaml should look like this:
```
@ -50,6 +49,8 @@ commitizen:
Replace [component], [version] and [scope] with the specific component name, version and scope you are working with. This command will help you bump the version of the component with the provided details.
Note: It is crucial to adhere to the above steps when introducing a new component. Never merge the code for a new component without first implementing a bump commit.
## Release process
When releasing a new component version we have to:

View File

@ -37,3 +37,15 @@ Please refer to instructions in [ESP-IDF](https://github.com/espressif/esp-idf)
### console_simple_init
* Brief introduction [README](components/console_simple_init/README.md)
### console_cmd_ping
* Brief introduction [README](components/console_cmd_ping/README.md)
### console_cmd_ifconfig
* Brief introduction [README](components/console_cmd_ifconfig/README.md)
### console_cmd_wifi
* Brief introduction [README](components/console_cmd_wifi/README.md)

View File

@ -10,6 +10,8 @@ import sys
from idf_build_apps import build_apps, find_apps, setup_logging
from idf_build_apps.constants import SUPPORTED_TARGETS
from packaging import version
from pkg_resources import get_distribution
if __name__ == '__main__':
parser = argparse.ArgumentParser(
@ -17,6 +19,12 @@ if __name__ == '__main__':
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument('paths', nargs='+', help='Paths to the apps to build.')
parser.add_argument(
'-v',
'--verbose',
action='count',
help='Increase the LOGGER level of the script. Can be specified multiple times.',
)
parser.add_argument(
'-t',
'--target',
@ -28,6 +36,8 @@ if __name__ == '__main__':
parser.add_argument('-d', '--delete', action='store_true', help='Delete build artifacts')
parser.add_argument('-c', '--recursive', action='store_true', help='Build recursively')
parser.add_argument('-l', '--linux', action='store_true', help='Include linux build (dont check warnings)')
parser.add_argument('--preserve-all', action='store_true', help='Preserve the binaries for all apps when specified.')
parser.add_argument('--pytest-apps', action='store_true', help='Only build apps required by pytest scripts.')
args = parser.parse_args()
IDF_PATH = os.environ['IDF_PATH']
@ -41,20 +51,36 @@ if __name__ == '__main__':
SUPPORTED_TARGETS.append('linux')
ignore_warning = 'warning: ' # Ignore all common warnings on linux builds
setup_logging(2)
apps = find_apps(
args.paths,
recursive=args.recursive,
target=args.target,
build_dir='build_@t_@w',
config_rules_str=args.rules,
build_log_path='build_log.txt',
size_json_path='size.json' if not args.linux else None,
check_warnings=True,
preserve=not args.delete,
manifest_files=args.manifests,
default_build_targets=SUPPORTED_TARGETS,
manifest_rootpath='.',
)
if version.parse(get_distribution('idf_build_apps').version) >= version.parse('2.0.0'):
apps = find_apps(
args.paths,
recursive=args.recursive,
target=args.target,
build_dir='build_@t_@w',
config_rules_str=args.rules,
build_log_filename='build_log.txt',
size_json_filename='size.json' if not args.linux else None,
check_warnings=True,
preserve=not args.delete,
manifest_files=args.manifests,
default_build_targets=SUPPORTED_TARGETS,
manifest_rootpath='.',
)
else:
apps = find_apps(
args.paths,
recursive=args.recursive,
target=args.target,
build_dir='build_@t_@w',
config_rules_str=args.rules,
build_log_path='build_log.txt',
size_json_path='size.json' if not args.linux else None,
check_warnings=True,
preserve=not args.delete,
manifest_files=args.manifests,
default_build_targets=SUPPORTED_TARGETS,
manifest_rootpath='.',
)
for app in apps:
print(app)

View File

@ -1,5 +1,16 @@
# Changelog
## [1.28.2~0](https://github.com/espressif/esp-protocols/commits/asio-1.28.2_0)
### Bug Fixes
- reference protocol_examples_common from IDF ([09abb18](https://github.com/espressif/esp-protocols/commit/09abb18))
- specify override_path in example manifest files ([1d8923c](https://github.com/espressif/esp-protocols/commit/1d8923c))
### Updated
- docs(asio): Updates asio docs ([ce9337d](https://github.com/espressif/esp-protocols/commit/ce9337d))
## [1.28.0~0](https://github.com/espressif/esp-protocols/commits/asio-1.28.0~0)
### Features

View File

@ -0,0 +1,40 @@
# Information for Contributors
Contributions in the form of pull requests, issue reports, and feature requests are welcome!
## Updating ASIO
ASIO is managed as a submodule, to updated the version first the [espressif](github.com/espressif/asio) ASIO fork must be updated.
## Release process
When releasing a new component version we have to:
* Update the submodule reference
* Update the version number
* Update the changelog
And the automation process takes care of the last steps:
* Create the version tag in this repository
* Deploy the component to component registry
* Update the documentation
This process needs to be manually handled for ASIO component since commitizen doesn't accept the versioning schema used.
* Increment manually the version in the [manifest file](idf_component.yml)
* Export environment variables for changelog generation:
- CZ_PRE_CURRENT_TAG_VERSION
- CZ_PRE_NEW_TAG_VERSION
- CZ_PRE_NEW_VERSION
* Run `python ../../ci/changelog.py asio` from this directory to generate the change log
* Check the updated `CHANGELOG.md`
* Commit the changes with the adequated message format.
```
bump(asio): $current_version -> $new_version
$Changelog for the version
```
* Create a PR
Once the PR is merged, the CI job tags the merge commit, creates a new release, builds and deploys documentation and the new component to the component registry

View File

@ -1,4 +1,4 @@
version: "1.28.0~0"
version: "1.28.2~0"
description: Cross-platform C++ library for network and I/O programming
url: https://github.com/espressif/esp-protocols/tree/master/components/asio
issues: https://github.com/espressif/esp-protocols/issues

View File

@ -0,0 +1,8 @@
---
commitizen:
bump_message: 'bump(console): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py console_cmd_ifconfig
tag_format: console_cmd_ifconfig-v$version
version: 1.0.1
version_files:
- idf_component.yml

View File

@ -0,0 +1,13 @@
# Changelog
## [1.0.1](https://github.com/espressif/esp-protocols/commits/console_cmd_ifconfig-v1.0.1)
### Bug Fixes
- Fixed ifconfig command for PPP interface and IPv4 only ([8548dabb](https://github.com/espressif/esp-protocols/commit/8548dabb))
## [1.0.0](https://github.com/espressif/esp-protocols/commits/console_cmd_ifconfig-v1.0.0)
### Features
- Console for runtime network interface configuration and monitoring ([8bab1420](https://github.com/espressif/esp-protocols/commit/8bab1420))

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "console_ifconfig.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_netif console esp_eth
WHOLE_ARCHIVE)

View File

@ -0,0 +1,201 @@
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.

View File

@ -0,0 +1,113 @@
# Console command ifconfig
The component offers a console with a command that enables runtime network interface configuration and monitoring for any example project.
## API
### Steps to enable console in an example code:
1. Add this component to your project using ```idf.py add-dependency``` command.
2. In the main file of the example, add the following line:
```c
#include "console_ifconfig.h"
```
3. Ensure esp-netif and NVS flash is initialized and default event loop is created in your app_main():
```c
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_err_t ret = nvs_flash_init(); //Initialize NVS
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
```
4. In your app_main() function, add the following line as the last line:
```c
ESP_ERROR_CHECK(console_cmd_init()); // Initialize console
// Register all plugin command added to your project
ESP_ERROR_CHECK(console_cmd_all_register());
// To register only ifconfig command skip calling console_cmd_all_register()
ESP_ERROR_CHECK(console_cmd_ifconfig_register());
ESP_ERROR_CHECK(console_cmd_start()); // Start console
```
### Adding a plugin command or component:
To add a plugin command or any component from IDF component manager into your project, simply include an entry within the `idf_component.yml` file.
For more details refer [IDF Component Manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html)
## Suported command:
### Ifconfig:
```
ifconfig help: Prints the help text for all ifconfig commands
ifconfig netif create/destroy <ethernet handle id>/<iface>: Create or destroy a network interface with the specified ethernet handle or interface name
ifconfig eth init/deinit/show: Initialize, deinitialize and display a list of available ethernet handle
ifconfig: Display a list of all esp_netif interfaces along with their information
ifconfig <iface>: Provide the details of the named interface
ifconfig <iface> default: Set the specified interface as the default interface
ifconfig <iface> ip6: Enable IPv6 on the specified interface
ifconfig <iface> up: Enable the specified interface
ifconfig <iface> down: Disable the specified interface
ifconfig <iface> link <up/down>: Enable or disable the link of the specified interface
ifconfig <iface> napt <enable/disable>: Enable or disable NAPT on the specified interface.
ifconfig <iface> ip <ipv4 addr>: Set the IPv4 address of the specified interface
ifconfig <iface> mask <ipv4 addr>: Set the subnet mask of the specified interface
ifconfig <iface> gw <ipv4 addr>: Set the default gateway of the specified interface
ifconfig <iface> staticip: Enables static ip
ifconfig <iface> dhcp server <enable/disable>: Enable or disable the DHCP server.(Note: DHCP server is not supported yet)
ifconfig <iface> dhcp client <enable/disable>: Enable or disable the DHCP client.
Note: Disabling the DHCP server and client enables the use of static IP configuration.
```
## Usage:
### Creating an ethernet interface
```
esp> ifconfig eth init
Internal(IP101): pins: 23,18, Id: 0
esp> ifconfig netif create 0
```
### Removing an interface and deinitializing ethernet
```
esp> ifconfig netif destroy en1
I (8351266) ethernet_init: Ethernet(IP101[23,18]) Link Down
I (8351266) ethernet_init: Ethernet(IP101[23,18]) Stopped
esp> ifconfig eth deinit
```
### Set default interface
```
esp> ifconfig en1 default
```
### Enable NAPT on an interface
```
esp> ifconfig en1 napt enable
I (8467116) console_ifconfig: Setting napt enable on en1
```
### Enable DHCP client on an interface
```
esp> ifconfig en1 dhcp client enable
```
### Enable static IP on an interface
```
esp> ifconfig en1 dhcp client disable
```
### Set static IP address
```
esp> ifconfig en1 ip 192.168.5.2
I (111466) console_ifconfig: Setting ip: 192.168.5.2
esp> ifconfig en1 mask 255.255.255.0
I (130946) console_ifconfig: Setting mask: 255.255.255.0
esp> ifconfig en1 gw 192.168.5.1
I (143576) console_ifconfig: Setting gw: 192.168.5.1
I (143576) esp_netif_handlers: eth ip: 192.168.5.2, mask: 255.255.255.0, gw: 192.168.5.1
```

View File

@ -0,0 +1,682 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "esp_netif.h"
#include "esp_eth.h"
#include "esp_console.h"
#include "esp_event.h"
#include "argtable3/argtable3.h"
#include "esp_log.h"
#include "esp_netif_net_stack.h"
#if CONFIG_LWIP_IPV6
#include "lwip/ip6.h"
#endif
#include "lwip/opt.h"
#include "ethernet_init.h"
#include "console_ifconfig.h"
#if IP_NAPT
#include "lwip/lwip_napt.h"
#endif
/**
* Static registration of this plugin is achieved by defining the plugin description
* structure and placing it into .console_cmd_desc section.
* The name of the section and its placement is determined by linker.lf file in 'plugins' component.
*/
static const console_cmd_plugin_desc_t __attribute__((section(".console_cmd_desc"), used)) PLUGIN = {
.name = "console_cmd_ifconfig",
.plugin_regd_fn = &console_cmd_ifconfig_register
};
typedef struct netif_op_t {
char *name;
esp_err_t (*operation)(struct netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
int arg_cnt;
int start_index;
char *help;
int netif_flag;
} netif_op_t;
static esp_err_t ifcfg_help_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
static esp_err_t ifcfg_print_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
static esp_err_t ifcfg_lwip_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
static esp_err_t ifcfg_basic_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
static esp_err_t ifcfg_ip_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
static esp_err_t ifcfg_napt_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
static esp_err_t ifcfg_addr_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
static esp_err_t ifcfg_netif_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
static esp_err_t ifcfg_eth_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif);
static const char *TAG = "console_ifconfig";
netif_op_t cmd_list[] = {
{.name = "help", .operation = ifcfg_help_op, .arg_cnt = 2, .start_index = 1, .netif_flag = false, .help = "ifconfig help: Prints the help text for all ifconfig commands"},
{.name = "netif", .operation = ifcfg_netif_op, .arg_cnt = 4, .start_index = 1, .netif_flag = false, .help = "ifconfig netif create/destroy <ethernet handle id>/<iface>: Create or destroy a network interface with the specified ethernet handle or interface name"},
{.name = "eth", .operation = ifcfg_eth_op, .arg_cnt = 3, .start_index = 1, .netif_flag = false, .help = "ifconfig eth init/deinit/show: Initialize, deinitialize and display a list of available ethernet handle"},
{.name = "ifconfig", .operation = ifcfg_print_op, .arg_cnt = 1, .start_index = 0, .netif_flag = false, .help = "ifconfig: Display a list of all esp_netif interfaces along with their information"},
{.name = "ifconfig", .operation = ifcfg_print_op, .arg_cnt = 2, .start_index = 0, .netif_flag = true, .help = "ifconfig <iface>: Provide the details of the named interface"},
{.name = "default", .operation = ifcfg_basic_op, .arg_cnt = 3, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> default: Set the specified interface as the default interface"},
#if CONFIG_LWIP_IPV6
{.name = "ip6", .operation = ifcfg_basic_op, .arg_cnt = 3, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> ip6: Enable IPv6 on the specified interface"},
#endif
{.name = "up", .operation = ifcfg_lwip_op, .arg_cnt = 3, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> up: Enable the specified interface"},
{.name = "down", .operation = ifcfg_lwip_op, .arg_cnt = 3, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> down: Disable the specified interface"},
{.name = "link", .operation = ifcfg_lwip_op, .arg_cnt = 4, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> link <up/down>: Enable or disable the link of the specified interface"},
{.name = "napt", .operation = ifcfg_napt_op, .arg_cnt = 4, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> napt <enable/disable>: Enable or disable NAPT on the specified interface."},
{.name = "ip", .operation = ifcfg_ip_op, .arg_cnt = 4, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> ip <ipv4 addr>: Set the IPv4 address of the specified interface"},
{.name = "mask", .operation = ifcfg_ip_op, .arg_cnt = 4, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> mask <ipv4 addr>: Set the subnet mask of the specified interface"},
{.name = "gw", .operation = ifcfg_ip_op, .arg_cnt = 4, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> gw <ipv4 addr>: Set the default gateway of the specified interface"},
{.name = "staticip", .operation = ifcfg_addr_op, .arg_cnt = 3, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> staticip: Enables static ip"},
{.name = "dhcp", .operation = ifcfg_addr_op, .arg_cnt = 5, .start_index = 2, .netif_flag = true, .help = "ifconfig <iface> dhcp server <enable/disable>: Enable or disable the DHCP server.(Note: DHCP server is not supported yet)\n ifconfig <iface> dhcp client <enable/disable>: Enable or disable the DHCP client.\nNote: Disabling the DHCP server and client enables the use of static IP configuration."},
};
static esp_err_t ifcfg_help_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif)
{
int cmd_count = sizeof(cmd_list) / sizeof(cmd_list[0]);
for (int i = 0; i < cmd_count; i++) {
if ((cmd_list[i].help != NULL) && (strlen(cmd_list[i].help) != 0)) {
printf(" %s\n", cmd_list[i].help);
}
}
return ESP_OK;
}
static esp_netif_t *get_esp_netif_from_ifname(char *if_name)
{
esp_netif_t *esp_netif = NULL;
esp_err_t ret = ESP_FAIL;
char interface[10];
/* Get interface details and obtain the global IPv6 address */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
while ((esp_netif = esp_netif_next_unsafe(esp_netif)) != NULL) {
#else
while ((esp_netif = esp_netif_next(esp_netif)) != NULL) {
#endif
ret = esp_netif_get_netif_impl_name(esp_netif, interface);
if ((ESP_FAIL == ret) || (NULL == esp_netif)) {
ESP_LOGE(TAG, "No interface available");
return NULL;
}
if (!strcmp(interface, if_name)) {
return esp_netif;
}
}
return NULL;
}
static esp_err_t ifcfg_basic_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif)
{
/* Set Default */
if (!strcmp("default", argv[self->start_index])) {
esp_netif_set_default_netif(esp_netif);
return ESP_OK;
}
#if CONFIG_LWIP_IPV6
/* Enable IPv6 on this interface */
if (!strcmp("ip6", argv[self->start_index])) {
ESP_ERROR_CHECK(esp_netif_create_ip6_linklocal(esp_netif));
return ESP_OK;
}
#endif
return ESP_FAIL;
}
static esp_err_t ifcfg_lwip_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif)
{
struct netif *lwip_netif = esp_netif_get_netif_impl(esp_netif);
if (NULL == lwip_netif) {
ESP_LOGE(TAG, "lwip interface %s not available", argv[1]);
return ESP_OK;
}
/* Enable/Disable Interface */
if (!strcmp("up", argv[self->start_index])) {
netif_set_up(lwip_netif);
return ESP_OK;
}
if (!strcmp("down", argv[self->start_index])) {
netif_set_down(lwip_netif);
return ESP_OK;
}
/* Enable/Disable link */
if (!strcmp("link", argv[self->start_index])) {
if (!strcmp("up", argv[self->start_index + 1])) {
netif_set_link_up(lwip_netif);
}
if (!strcmp("down", argv[self->start_index + 1])) {
netif_set_down(lwip_netif);
netif_set_link_down(lwip_netif);
}
return ESP_OK;
}
return ESP_FAIL;
}
static esp_err_t ifcfg_ip_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif)
{
esp_netif_ip_info_t ip_info = {0};
esp_netif_dhcpc_stop(esp_netif);
esp_netif_get_ip_info(esp_netif, &ip_info);
if (!strcmp("ip", argv[self->start_index])) {
ESP_LOGI(TAG, "Setting ip: %s", argv[self->start_index + 1]);
inet_aton(argv[self->start_index + 1], &ip_info.ip.addr);
esp_netif_set_ip_info(esp_netif, &ip_info);
return ESP_OK;
} else if (!strcmp("mask", argv[self->start_index])) {
ESP_LOGI(TAG, "Setting mask: %s", argv[self->start_index + 1]);
inet_aton(argv[self->start_index + 1], &ip_info.netmask.addr);
esp_netif_set_ip_info(esp_netif, &ip_info);
return ESP_OK;
} else if (!strcmp("gw", argv[self->start_index])) {
ESP_LOGI(TAG, "Setting gw: %s", argv[self->start_index + 1]);
inet_aton(argv[self->start_index + 1], &ip_info.gw.addr);
esp_netif_set_ip_info(esp_netif, &ip_info);
return ESP_OK;
}
return ESP_FAIL;
}
#if IP_NAPT
static esp_err_t set_napt(char *if_name, bool state)
{
esp_netif_t *esp_netif = NULL;
esp_err_t ret = ESP_FAIL;
char interface[10];
/* Get interface details and own global ipv6 address */
for (int i = 0; i < esp_netif_get_nr_of_ifs(); ++i) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
esp_netif = esp_netif_next_unsafe(esp_netif);
#else
esp_netif = esp_netif_next(esp_netif);
#endif
ret = esp_netif_get_netif_impl_name(esp_netif, interface);
if ((ESP_FAIL == ret) || (NULL == esp_netif)) {
ESP_LOGE(TAG, "No interface available");
return ESP_FAIL;
}
if (!strcmp(interface, if_name)) {
return esp_netif_napt_enable(esp_netif);
}
}
return ESP_FAIL;
}
#endif
static esp_err_t ifcfg_napt_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif)
{
#if IP_NAPT
if (!strcmp("napt", argv[self->start_index])) {
ESP_LOGI(TAG, "Setting napt %s on %s", argv[self->start_index + 1], argv[1]);
if (!strcmp(argv[self->start_index + 1], "enable")) {
return set_napt(argv[1], true);
} else if (!strcmp(argv[self->start_index + 1], "disable")) {
return set_napt(argv[1], false);
} else {
ESP_LOGI(TAG, "Invalid argument: %s", argv[self->start_index + 1]);
}
return ESP_FAIL;
}
#endif
ESP_LOGE(TAG, "NAPT not enabled in menuconfig");
return ESP_OK;
}
static esp_err_t ifcfg_addr_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif)
{
if (!strcmp("staticip", argv[self->start_index])) {
esp_netif_dhcpc_stop(esp_netif);
//esp_netif_dhcps_stop(esp_netif);
return ESP_OK;
} else if (!strcmp("server", argv[self->start_index + 1])) { // Server
if (!strcmp("enable", argv[self->start_index + 2])) {
ESP_LOGW(TAG, "DHCP Server configuration is not supported yet."); // TBD
//esp_netif_dhcps_start(esp_netif);
return ESP_OK;
} else if (!strcmp("disable", argv[self->start_index + 2])) {
ESP_LOGW(TAG, "DHCP Server configuration is not supported yet."); // TBD
//esp_netif_dhcps_stop(esp_netif);
return ESP_OK;
}
ESP_LOGE(TAG, "Invalid argument");
return ESP_FAIL;
} else if (!strcmp("client", argv[self->start_index + 1])) { // Client
if (!strcmp("enable", argv[self->start_index + 2])) {
esp_netif_dhcpc_start(esp_netif);
return ESP_OK;
} else if (!strcmp("disable", argv[self->start_index + 2])) {
esp_netif_dhcpc_stop(esp_netif);
return ESP_OK;
}
ESP_LOGE(TAG, "Invalid argument");
return ESP_FAIL;
}
return ESP_FAIL;
}
static void print_iface_details(esp_netif_t *esp_netif)
{
esp_netif_ip_info_t ip_info;
uint8_t mac[NETIF_MAX_HWADDR_LEN];
char interface[10];
#if CONFIG_LWIP_IPV6
int ip6_addrs_count = 0;
esp_ip6_addr_t ip6[LWIP_IPV6_NUM_ADDRESSES];
#endif
esp_err_t ret = ESP_FAIL;
esp_netif_dhcp_status_t status;
struct netif *lwip_netif = esp_netif_get_netif_impl(esp_netif);
/* Print Interface Name and Number */
ret = esp_netif_get_netif_impl_name(esp_netif, interface);
if ((ESP_FAIL == ret) || (NULL == esp_netif)) {
ESP_LOGE(TAG, "No interface available");
return;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
if (esp_netif_get_default_netif() == esp_netif) {
ESP_LOGI(TAG, "Interface Name: %s (DEF)", interface);
} else {
ESP_LOGI(TAG, "Interface Name: %s", interface);
}
#else
ESP_LOGI(TAG, "Interface Name: %s", interface);
#endif
if (lwip_netif != NULL) {
ESP_LOGI(TAG, "Interface Number: %d", lwip_netif->num);
}
/* Print MAC address */
esp_netif_get_mac(esp_netif, mac);
ESP_LOGI(TAG, "MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1],
mac[2], mac[3], mac[4], mac[5]);
/* Print DHCP status */
if (ESP_OK == esp_netif_dhcps_get_status(esp_netif, &status)) {
ESP_LOGI(TAG, "DHCP Server Status: %s", (status == ESP_NETIF_DHCP_STARTED) || (status == ESP_NETIF_DHCP_STOPPED) ? "enabled" : "disabled");
} else if ((ESP_OK == esp_netif_dhcpc_get_status(esp_netif, &status))) {
if (ESP_NETIF_DHCP_STOPPED == status) {
ESP_LOGI(TAG, "Static IP");
} else {
ESP_LOGI(TAG, "DHCP Client Status: %s", status ? "enabled" : "disabled");
}
}
/* Print IP Info */
esp_netif_get_ip_info(esp_netif, &ip_info);
ESP_LOGI(TAG, "IP: " IPSTR ", MASK: " IPSTR ", GW: " IPSTR, IP2STR(&(ip_info.ip)), IP2STR(&(ip_info.netmask)), IP2STR(&(ip_info.gw)));
#if IP_NAPT
/* Print NAPT status*/
if (lwip_netif != NULL) {
ESP_LOGI(TAG, "NAPT: %s", lwip_netif->napt ? "enabled" : "disabled");
}
#endif
#if CONFIG_LWIP_IPV6
/* Print IPv6 Address */
ip6_addrs_count = esp_netif_get_all_ip6(esp_netif, ip6);
for (int j = 0; j < ip6_addrs_count; ++j) {
ESP_LOGI(TAG, "IPv6 address: " IPV6STR, IPV62STR(ip6[j]));
}
#endif
/* Print Interface and Link Status*/
ESP_LOGI(TAG, "Interface Status: %s", esp_netif_is_netif_up(esp_netif) ? "UP" : "DOWN");
if (lwip_netif != NULL) {
ESP_LOGI(TAG, "Link Status: %s", netif_is_link_up(lwip_netif) ? "UP" : "DOWN");
}
ESP_LOGI(TAG, "");
}
static esp_err_t ifcfg_print_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif)
{
/* Print interface details */
if (2 == argc) {
print_iface_details(esp_netif);
return ESP_OK;
}
/* Get interface details and own global ipv6 address of all interfaces */
for (int i = 0; i < esp_netif_get_nr_of_ifs(); ++i) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
esp_netif = esp_netif_next_unsafe(esp_netif);
#else
esp_netif = esp_netif_next(esp_netif);
#endif
print_iface_details(esp_netif);
}
return ESP_OK;
}
/* Maximum number of interface that can be added */
#define MAX_ETH_NETIF_COUNT (10)
typedef enum {
UNINITIALIZED = 0,
ETH_INITIALIZED = 1,
NETIF_CREATED = 2,
NETIF_DESTROYED = 3,
ETH_DEINITIALIZED = 4
} iface_state;
typedef struct {
esp_netif_t *esp_netif;
esp_eth_handle_t *eth_handle;
esp_eth_netif_glue_handle_t eth_glue;
iface_state state;
} iface_desc;
static iface_desc iface_list[MAX_ETH_NETIF_COUNT];
static uint8_t netif_count;
static uint8_t eth_init_flag = false;
static uint8_t eth_port_cnt_g = 0;
static esp_err_t get_netif_config(uint16_t id, esp_netif_config_t *eth_cfg_o)
{
/* Create new default instance of esp-netif for Ethernet */
char *if_key;
if (asprintf(&if_key, "IFC_ETH%d", id) == -1) {
return ESP_FAIL;
}
esp_netif_inherent_config_t *esp_eth_base_config = malloc(sizeof(esp_netif_inherent_config_t));
if (NULL == esp_eth_base_config) {
return ESP_FAIL;
}
*esp_eth_base_config = (esp_netif_inherent_config_t)ESP_NETIF_INHERENT_DEFAULT_ETH();
esp_eth_base_config->if_key = if_key;
eth_cfg_o->base = esp_eth_base_config;
eth_cfg_o->driver = NULL;
eth_cfg_o->stack = ESP_NETIF_NETSTACK_DEFAULT_ETH;
return ESP_OK;
}
static void free_config(esp_netif_config_t *eth_cfg)
{
if ((NULL != eth_cfg) && (NULL != eth_cfg->base)) {
free((void *)(eth_cfg->base->if_key));
free((void *)(eth_cfg->base));
}
}
static esp_err_t ifcfg_netif_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif)
{
int eth_handle_id = atoi(argv[self->start_index + 2]);
if (!strcmp(argv[self->start_index + 1], "create")) {
/* Validate ethernet handle */
if ((eth_handle_id + 1 > eth_port_cnt_g) || (eth_handle_id < 0)) {
ESP_LOGE(TAG, "Invalid ethernet handle: %s", argv[self->start_index + 2]);
return ESP_FAIL;
}
esp_netif_config_t eth_cfg;
ESP_ERROR_CHECK(get_netif_config(eth_handle_id, &eth_cfg));
for (int i = 0; i < MAX_ETH_NETIF_COUNT; i++) {
if (iface_list[i].state == ETH_INITIALIZED) {
esp_netif = esp_netif_new(&eth_cfg);
if (esp_netif == NULL) {
ESP_LOGE(TAG, "Interface with key %s already exists", argv[self->start_index + 2]);
return ESP_FAIL;
}
iface_list[i].eth_glue = esp_eth_new_netif_glue(iface_list[i].eth_handle);
if (iface_list[i].eth_glue == NULL) {
ESP_LOGE(TAG, "%s: eth_glue is NULL", __func__);
esp_netif_destroy(esp_netif);
return ESP_FAIL;
}
iface_list[i].esp_netif = esp_netif;
ESP_ERROR_CHECK(esp_netif_attach(iface_list[i].esp_netif, iface_list[i].eth_glue));
// start Ethernet driver state machine
ESP_ERROR_CHECK(esp_eth_start(iface_list[i].eth_handle));
free_config(&eth_cfg);
iface_list[i].state = NETIF_CREATED;
netif_count++;
break;
}
}
return ESP_OK;
} else if (!strcmp(argv[self->start_index + 1], "destroy")) {
esp_netif = get_esp_netif_from_ifname(argv[self->start_index + 2]);
if (NULL == esp_netif) {
ESP_LOGE(TAG, "interface %s not available", argv[1]);
return ESP_FAIL;
}
for (int i = 0; i < MAX_ETH_NETIF_COUNT; i++) {
if (esp_netif == iface_list[i].esp_netif) {
if (iface_list[i].state == NETIF_CREATED) {
esp_eth_stop(iface_list[i].eth_handle);
esp_eth_del_netif_glue(iface_list[i].eth_glue);
esp_netif_destroy(iface_list[i].esp_netif);
iface_list[i].state = NETIF_DESTROYED;
netif_count--;
return ESP_OK;
} else {
ESP_LOGE(TAG, "Netif is not in created state");
return ESP_FAIL;
}
break;
}
}
ESP_LOGE(TAG, "Something is very wrong. Unauthorized Interface.");
return ESP_FAIL;
}
return ESP_FAIL;
}
static void print_eth_info(eth_dev_info_t eth_info, int id)
{
if (eth_info.type == ETH_DEV_TYPE_INTERNAL_ETH) {
printf("Internal(%s): pins: %2d,%2d, Id: %d\n", eth_info.name, eth_info.pin.eth_internal_mdc, eth_info.pin.eth_internal_mdio, id);
} else if (eth_info.type == ETH_DEV_TYPE_SPI) {
printf(" SPI(%s): pins: %2d,%2d, Id: %d\n", eth_info.name, eth_info.pin.eth_spi_cs, eth_info.pin.eth_spi_int, id);
} else {
printf("ethernet handle id(ETH_DEV_TYPE_UNKNOWN): %d\n", id);
}
}
static esp_err_t ifcfg_eth_op(netif_op_t *self, int argc, char *argv[], esp_netif_t *esp_netif)
{
static esp_eth_handle_t *eth_handle_g = NULL;
eth_dev_info_t eth_info;
if (!strcmp(argv[self->start_index + 1], "init")) {
/* Check if ethernet is initialized */
if (eth_init_flag == false) {
// Initialize Ethernet driver
if (ethernet_init_all(&eth_handle_g, &eth_port_cnt_g) != ESP_OK) {
ESP_LOGE(TAG, "Unable to initialize ethernet");
return ESP_FAIL;
}
eth_init_flag = true;
for (int i = 0; i < eth_port_cnt_g; i++) {
for (int j = 0; j < MAX_ETH_NETIF_COUNT; j++) {
if (iface_list[j].state == UNINITIALIZED) {
iface_list[j].eth_handle = eth_handle_g[i];
iface_list[j].state = ETH_INITIALIZED;
break;
}
}
}
if (eth_port_cnt_g > MAX_ETH_NETIF_COUNT) {
ESP_LOGW(TAG, "Not all ethernet ports can be assigned a network interface.\nPlease reconfigure MAX_ETH_NETIF_COUNT to a higher value.");
}
} else {
ESP_LOGW(TAG, "Ethernet already initialized");
}
/* Display available ethernet handles */
for (int i = 0; i < eth_port_cnt_g; i++) {
eth_info = ethernet_init_get_dev_info(iface_list[i].eth_handle);
print_eth_info(eth_info, i);
}
} else if (!strcmp(argv[self->start_index + 1], "show")) {
/* Check if ethernet is initialized */
if (eth_init_flag == false) {
// Initialize Ethernet driver
ESP_LOGE(TAG, "Ethernet is not initialized.");
return ESP_OK;
}
/* Display available ethernet handles */
for (int i = 0; i < eth_port_cnt_g; i++) {
eth_info = ethernet_init_get_dev_info(iface_list[i].eth_handle);
print_eth_info(eth_info, i);
}
} else if (!strcmp(argv[self->start_index + 1], "deinit")) {
/* Check if ethernet is initialized */
if (eth_init_flag == false) {
// Initialize Ethernet driver
ESP_LOGE(TAG, "Ethernet is not initialized.");
return ESP_OK;
}
/* Stop and Deinit ethernet here */
ethernet_deinit_all(eth_handle_g);
eth_port_cnt_g = 0;
eth_init_flag = false;
} else {
return ESP_FAIL;
}
return ESP_OK;
}
/* handle 'ifconfig' command */
static int do_cmd_ifconfig(int argc, char **argv)
{
esp_netif_t *esp_netif = NULL;
int cmd_count = sizeof(cmd_list) / sizeof(cmd_list[0]);
netif_op_t cmd;
for (int i = 0; i < cmd_count; i++) {
cmd = cmd_list[i];
if (argc < cmd.start_index + 1) {
continue;
}
if (!strcmp(cmd.name, argv[cmd.start_index])) {
/* Get interface for eligible commands */
if (cmd.netif_flag == true) {
esp_netif = get_esp_netif_from_ifname(argv[1]);
if (NULL == esp_netif) {
ESP_LOGE(TAG, "interface %s not available", argv[1]);
return 0;
}
}
if (cmd.arg_cnt == argc) {
if (cmd.operation != NULL) {
if (cmd.operation(&cmd, argc, argv, esp_netif) != ESP_OK) {
ESP_LOGE(TAG, "Usage:\n%s", cmd.help);
return 0;
}
}
return 0;
}
}
}
ESP_LOGE(TAG, "Command not available");
return 1;
}
/**
* @brief Registers the ifconfig command.
*
* @return
* - esp_err_t
*/
esp_err_t console_cmd_ifconfig_register(void)
{
esp_err_t ret;
esp_console_cmd_t command = {
.command = "ifconfig",
.help = "Command for network interface configuration and monitoring\nFor more info run 'ifconfig help'",
.func = &do_cmd_ifconfig
};
ret = esp_console_cmd_register(&command);
if (ret) {
ESP_LOGE(TAG, "Unable to register ifconfig");
}
return ret;
}

View File

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "console_simple_init.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Registers the ifconfig command.
*
* @return
* - esp_err_t
*/
esp_err_t console_cmd_ifconfig_register(void);
#ifdef __cplusplus
}
#endif

View File

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

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "ifconfig-basic.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
console_cmd_ifconfig:
version: "*"
override_path: '../../../'

View File

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "esp_netif.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "console_ifconfig.h"
void app_main(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_err_t ret = nvs_flash_init(); //Initialize NVS
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize console REPL
ESP_ERROR_CHECK(console_cmd_init());
ESP_ERROR_CHECK(console_cmd_ifconfig_register());
// start console REPL
ESP_ERROR_CHECK(console_cmd_start());
}

View File

@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
# -*- coding: utf-8 -*-
import pytest
@pytest.mark.esp32
def test_examples_ifconfig_command(dut):
dut.expect('esp>', timeout=30)
dut.write('ifconfig eth init')
dut.expect(r'Internal\(IP101\): pins:', timeout=30)
dut.write('ifconfig netif create 0')
dut.expect(r'ethernet_init: Ethernet\(IP101\[23,18\]\) Link Up', timeout=30)
dut.write('ifconfig')
dut.expect('console_ifconfig: Interface Name: en1', timeout=5)
dut.write('ifconfig netif destroy en1')
dut.expect('esp>', timeout=5)
dut.write('ifconfig eth deinit')
dut.expect('esp>', timeout=5)
pass

View File

@ -0,0 +1,12 @@
version: 1.0.1
url: https://github.com/espressif/esp-protocols/tree/master/components/console_cmd_ifconfig
description: The component offers a console that enables runtime network interface configuration and monitoring.
dependencies:
idf:
version: '>=5.0'
espressif/console_simple_init:
version: '>=1.1.0'
override_path: '../console_simple_init'
public: true
espressif/ethernet_init:
version: '>=0.0.7'

View File

@ -0,0 +1,8 @@
---
commitizen:
bump_message: 'bump(console): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py console_cmd_ping
tag_format: console_cmd_ping-v$version
version: 1.0.0
version_files:
- idf_component.yml

View File

@ -0,0 +1,7 @@
# Changelog
## [1.0.0](https://github.com/espressif/esp-protocols/commits/console_cmd_ping-v1.0.0)
### Features
- Added ping command to console component ([7babdeb9](https://github.com/espressif/esp-protocols/commit/7babdeb9))

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "console_ping.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_netif console
WHOLE_ARCHIVE)

View File

@ -0,0 +1,201 @@
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.

View File

@ -0,0 +1,55 @@
# Console command ping
The component provides a console where the 'ping' command can be executed.
## API
### Steps to enable console in an example code:
1. Add this component to your project using ```idf.py add-dependency``` command.
2. In the main file of the example, add the following line:
```c
#include "console_ping.h"
```
3. Ensure esp-netif and NVS flash is initialized and default event loop is created in your app_main():
```c
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_err_t ret = nvs_flash_init(); //Initialize NVS
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
```
4. In your app_main() function, add the following line as the last line:
```c
ESP_ERROR_CHECK(console_cmd_init()); // Initialize console
// Register all plugin command added to your project
ESP_ERROR_CHECK(console_cmd_all_register());
// To register only ifconfig command skip calling console_cmd_all_register()
ESP_ERROR_CHECK(console_cmd_ping_register());
ESP_ERROR_CHECK(console_cmd_start()); // Start console
```
### Adding a plugin command or component:
To add a plugin command or any component from IDF component manager into your project, simply include an entry within the `idf_component.yml` file.
For more details refer [IDF Component Manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html)
## Suported command:
### ping:
```
ping [-W <t>] [-i <t>] [-s <n>] [-c <n>] [-Q <n>] [-T <n>] <host>
send ICMP ECHO_REQUEST to network hosts
-W, --timeout=<t> Time to wait for a response, in seconds
-i, --interval=<t> Wait interval seconds between sending each packet
-s, --size=<n> Specify the number of data bytes to be sent
-c, --count=<n> Stop after sending count packets
-Q, --tos=<n> Set Type of Service related bits in IP datagrams
-T, --ttl=<n> Set Time to Live related bits in IP datagrams
<host> Host address
```

View File

@ -0,0 +1,215 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "esp_console.h"
#include "esp_event.h"
#include "esp_log.h"
#include "argtable3/argtable3.h"
#include "ping/ping_sock.h"
#include "console_ping.h"
static const char *TAG = "console_ping";
SemaphoreHandle_t sync_semaphore;
/**
* Static registration of this plugin is achieved by defining the plugin description
* structure and placing it into .console_cmd_desc section.
* The name of the section and its placement is determined by linker.lf file in 'plugins' component.
*/
static const console_cmd_plugin_desc_t __attribute__((section(".console_cmd_desc"), used)) PLUGIN = {
.name = "console_cmd_ping",
.plugin_regd_fn = &console_cmd_ping_register
};
static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args)
{
uint8_t ttl;
uint16_t seqno;
uint32_t elapsed_time, recv_len;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
printf("%" PRIu32 " bytes from %s icmp_seq=%" PRIu16 " ttl=%" PRIu16 " time=%" PRIu32 " ms\n",
recv_len, ipaddr_ntoa((ip_addr_t *)&target_addr), seqno, ttl, elapsed_time);
}
static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args)
{
uint16_t seqno;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
printf("From %s icmp_seq=%d timeout\n", ipaddr_ntoa((ip_addr_t *)&target_addr), seqno);
}
static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
{
ip_addr_t target_addr;
uint32_t transmitted;
uint32_t received;
uint32_t total_time_ms;
uint32_t loss;
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
if (transmitted > 0) {
loss = (uint32_t)((1 - ((float)received) / transmitted) * 100);
} else {
loss = 0;
}
if (IP_IS_V4(&target_addr)) {
printf("\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr)));
} else {
printf("\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
}
printf("%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n",
transmitted, received, loss, total_time_ms);
// delete the ping sessions, so that we clean up all resources and can create a new ping session
ESP_ERROR_CHECK(esp_ping_delete_session(hdl));
/* Give the semaphore as ping task is done */
xSemaphoreGive(sync_semaphore);
}
static struct {
struct arg_dbl *timeout;
struct arg_dbl *interval;
struct arg_int *data_size;
struct arg_int *count;
struct arg_int *tos;
struct arg_int *ttl;
struct arg_str *host;
struct arg_end *end;
} ping_args;
static int do_ping_cmd(int argc, char **argv)
{
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
int nerrors = arg_parse(argc, argv, (void **)&ping_args);
if (nerrors != 0) {
arg_print_errors(stderr, ping_args.end, argv[0]);
return 1;
}
if (ping_args.timeout->count > 0) {
config.timeout_ms = (uint32_t)(ping_args.timeout->dval[0] * 1000);
}
if (ping_args.interval->count > 0) {
config.interval_ms = (uint32_t)(ping_args.interval->dval[0] * 1000);
}
if (ping_args.data_size->count > 0) {
config.data_size = (uint32_t)(ping_args.data_size->ival[0]);
}
if (ping_args.count->count > 0) {
config.count = (uint32_t)(ping_args.count->ival[0]);
}
if (ping_args.tos->count > 0) {
config.tos = (uint32_t)(ping_args.tos->ival[0]);
}
if (ping_args.ttl->count > 0) {
config.ttl = (uint32_t)(ping_args.ttl->ival[0]);
}
// parse IP address
struct sockaddr_in6 sock_addr6;
ip_addr_t target_addr = {0};
if (inet_pton(AF_INET6, ping_args.host->sval[0], &sock_addr6.sin6_addr) == 1) {
/* convert ip6 string to ip6 address */
ipaddr_aton(ping_args.host->sval[0], &target_addr);
} else {
struct addrinfo hint = {0};
struct addrinfo *res = NULL;
/* convert ip4 string or hostname to ip4 or ip6 address */
if (getaddrinfo(ping_args.host->sval[0], NULL, &hint, &res) != 0) {
printf("ping: unknown host %s\n", ping_args.host->sval[0]);
return 1;
}
if (res->ai_family == AF_INET) {
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
} else {
struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr;
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
}
freeaddrinfo(res);
}
config.target_addr = target_addr;
/* set callback functions */
esp_ping_callbacks_t cbs = {
.cb_args = NULL,
.on_ping_success = cmd_ping_on_ping_success,
.on_ping_timeout = cmd_ping_on_ping_timeout,
.on_ping_end = cmd_ping_on_ping_end
};
esp_ping_handle_t ping;
ESP_ERROR_CHECK(esp_ping_new_session(&config, &cbs, &ping));
ESP_ERROR_CHECK(esp_ping_start(ping));
/* Wait till the ping task is done */
if (xSemaphoreTake(sync_semaphore, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Error in xSemaphoreTake\n");
}
return 0;
}
/**
* @brief Registers the ping command.
*
* @return
* - esp_err_t
*/
esp_err_t console_cmd_ping_register(void)
{
esp_err_t ret;
sync_semaphore = xSemaphoreCreateBinary();
ping_args.timeout = arg_dbl0("W", "timeout", "<t>", "Time to wait for a response, in seconds");
ping_args.interval = arg_dbl0("i", "interval", "<t>", "Wait interval seconds between sending each packet");
ping_args.data_size = arg_int0("s", "size", "<n>", "Specify the number of data bytes to be sent");
ping_args.count = arg_int0("c", "count", "<n>", "Stop after sending count packets");
ping_args.tos = arg_int0("Q", "tos", "<n>", "Set Type of Service related bits in IP datagrams");
ping_args.ttl = arg_int0("T", "ttl", "<n>", "Set Time to Live related bits in IP datagrams");
ping_args.host = arg_str1(NULL, NULL, "<host>", "Host address");
ping_args.end = arg_end(1);
const esp_console_cmd_t ping_cmd = {
.command = "ping",
.help = "send ICMP ECHO_REQUEST to network hosts",
.hint = NULL,
.func = &do_ping_cmd,
.argtable = &ping_args
};
ret = esp_console_cmd_register(&ping_cmd);
if (ret) {
ESP_LOGE(TAG, "Unable to register ping");
}
return ret;
}

View File

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "console_simple_init.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Registers the ping command.
*
* @return
* - esp_err_t
*/
esp_err_t console_cmd_ping_register(void);
#ifdef __cplusplus
}
#endif

View File

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

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "ping-basic.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
console_cmd_ping:
version: "*"
override_path: '../../../'

View File

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "esp_netif.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "console_ping.h"
void app_main(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_err_t ret = nvs_flash_init(); //Initialize NVS
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize console REPL
ESP_ERROR_CHECK(console_cmd_init());
// Register ping command
ESP_ERROR_CHECK(console_cmd_ping_register());
// start console REPL
ESP_ERROR_CHECK(console_cmd_start());
}

View File

@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
# -*- coding: utf-8 -*-
import pytest
@pytest.mark.esp32
def test_examples_ping_command(dut):
dut.expect('esp>', timeout=30)
dut.write('ping 127.0.0.1')
dut.expect('5 packets transmitted, 5 received, 0% packet loss, time 0ms', timeout=30)
pass

View File

@ -0,0 +1,10 @@
version: 1.0.0
url: https://github.com/espressif/esp-protocols/tree/master/components/console_cmd_ping
description: The component provides a console where the 'ping' command can be executed.
dependencies:
idf:
version: '>=5.0'
espressif/console_simple_init:
version: '>=1.1.0'
override_path: '../console_simple_init'
public: true

View File

@ -0,0 +1,8 @@
---
commitizen:
bump_message: 'bump(console): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py console_cmd_wifi
tag_format: console_cmd_wifi-v$version
version: 1.0.1
version_files:
- idf_component.yml

View File

@ -0,0 +1,17 @@
# Changelog
## [1.0.1](https://github.com/espressif/esp-protocols/commits/console_cmd_wifi-v1.0.1)
### Features
- Console for runtime wifi configuration ([194d1179](https://github.com/espressif/esp-protocols/commit/194d1179))
### Bug Fixes
- Fixed license file for console_cmd_wifi ([8285e973](https://github.com/espressif/esp-protocols/commit/8285e973))
## [1.0.0](https://github.com/espressif/esp-protocols/commits/console_cmd_wifi-v1.0.0)
### Features
- Console for runtime wifi configuration ([194d1179](https://github.com/espressif/esp-protocols/commit/194d1179))

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "console_wifi.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_netif console esp_wifi
WHOLE_ARCHIVE)

View File

@ -0,0 +1,201 @@
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.

View File

@ -0,0 +1,44 @@
# Console command wifi
The component offers a console with a command that enables runtime wifi configuration for any example project.
## API
### Steps to enable console in an example code:
1. Add this component to your project using ```idf.py add-dependency``` command.
2. In the main file of the example, add the following line:
```c
#include "console_wifi.h"
```
3. Ensure esp-netif and NVS flash is initialized and default event loop is created in your app_main():
```c
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_err_t ret = nvs_flash_init(); //Initialize NVS
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
```
4. In your app_main() function, add the following line as the last line:
```c
ESP_ERROR_CHECK(console_cmd_init()); // Initialize console
// Register all plugin command added to your project
ESP_ERROR_CHECK(console_cmd_all_register());
// To register only wifi command skip calling console_cmd_all_register()
ESP_ERROR_CHECK(console_cmd_wifi_register());
ESP_ERROR_CHECK(console_cmd_start()); // Start console
```
## Suported command:
### wifi:
* ```wifi help```: Prints the help text for all wifi commands
* ```wifi show network```: Scans and displays upto 10 available wifi networks.
* ```wifi show sta```: Shows the details of wifi station.
* ```wifi sta join <network ssid> <password>```: Station joins the given wifi network.
* ```wifi sta join <network ssid>```: Station joins the given unsecured wifi network.
* ```wifi sta leave```: Station leaves the wifi network.

View File

@ -0,0 +1,276 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_netif.h"
#include "esp_console.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif_net_stack.h"
#include "lwip/ip6.h"
#include "lwip/opt.h"
#include "esp_wifi.h"
#include "console_wifi.h"
#define DEFAULT_SCAN_LIST_SIZE 10
/**
* Static registration of this plugin is achieved by defining the plugin description
* structure and placing it into .console_cmd_desc section.
* The name of the section and its placement is determined by linker.lf file in 'plugins' component.
*/
static const console_cmd_plugin_desc_t __attribute__((section(".console_cmd_desc"), used)) PLUGIN = {
.name = "console_cmd_wifi",
.plugin_regd_fn = &console_cmd_wifi_register
};
typedef struct wifi_op_t {
char *name;
esp_err_t (*operation)(struct wifi_op_t *self, int argc, char *argv[]);
int arg_cnt;
int start_index;
char *help;
} wifi_op_t;
static esp_err_t wifi_help_op(wifi_op_t *self, int argc, char *argv[]);
static esp_err_t wifi_show_op(wifi_op_t *self, int argc, char *argv[]);
static esp_err_t wifi_sta_join_op(wifi_op_t *self, int argc, char *argv[]);
static esp_err_t wifi_sta_leave_op(wifi_op_t *self, int argc, char *argv[]);
static const char *TAG = "console_wifi";
#define JOIN_TIMEOUT_MS (10000)
static EventGroupHandle_t wifi_event_group;
static const int STA_STARTED_BIT = BIT0;
static const int CONNECTED_BIT = BIT1;
static wifi_op_t cmd_list[] = {
{.name = "help", .operation = wifi_help_op, .arg_cnt = 2, .start_index = 1, .help = "wifi help: Prints the help text for all wifi commands"},
{.name = "show", .operation = wifi_show_op, .arg_cnt = 3, .start_index = 1, .help = "wifi show network/sta: Scans and displays all available wifi APs./ Shows the details of wifi station."},
{.name = "join", .operation = wifi_sta_join_op, .arg_cnt = 4, .start_index = 2, .help = "wifi sta join <network ssid> <password>: Station joins the given wifi network."},
{.name = "join", .operation = wifi_sta_join_op, .arg_cnt = 5, .start_index = 2, .help = "wifi sta join <network ssid>: Station joins the given unsecured wifi network."},
{.name = "leave", .operation = wifi_sta_leave_op, .arg_cnt = 3, .start_index = 2, .help = "wifi sta leave: Station leaves the wifi network."},
};
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
xEventGroupSetBits(wifi_event_group, STA_STARTED_BIT);
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
}
}
static esp_err_t wifi_help_op(wifi_op_t *self, int argc, char *argv[])
{
int cmd_count = sizeof(cmd_list) / sizeof(cmd_list[0]);
for (int i = 0; i < cmd_count; i++) {
if ((cmd_list[i].help != NULL) && (strlen(cmd_list[i].help) != 0)) {
printf(" %s\n", cmd_list[i].help);
}
}
return ESP_OK;
}
uint8_t wifi_connection_status = 0;
void wifi_init(void)
{
static bool init_flag = false;
if (init_flag) {
return;
}
wifi_event_group = xEventGroupCreate();
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_START, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_start());
int bits = xEventGroupWaitBits(wifi_event_group, STA_STARTED_BIT,
pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS);
if ((bits & STA_STARTED_BIT) == 0) {
ESP_LOGE(TAG, "Error: Wifi Connection timed out");
return;
}
init_flag = true;
}
/* Initialize Wi-Fi as sta and set scan method */
static void wifi_scan(void)
{
uint16_t number = DEFAULT_SCAN_LIST_SIZE;
wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE];
uint16_t ap_count = 0;
memset(ap_info, 0, sizeof(ap_info));
wifi_init();
esp_wifi_scan_start(NULL, true);
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
ESP_LOGI(TAG, "Showing Wifi networks");
ESP_LOGI(TAG, "*********************");
for (int i = 0; i < number; i++) {
ESP_LOGI(TAG, "RSSI: %d\tChannel: %d\tSSID: %s", ap_info[i].rssi, ap_info[i].primary, ap_info[i].ssid);
}
ESP_LOGI(TAG, "Total APs scanned = %u, actual AP number ap_info holds = %u", ap_count, number);
}
static esp_err_t wifi_show_op(wifi_op_t *self, int argc, char *argv[])
{
if (!strcmp("network", argv[self->start_index + 1])) {
wifi_scan();
return ESP_OK;
}
if (!strcmp("sta", argv[self->start_index + 1])) {
{
wifi_config_t wifi_config = { 0 };
wifi_init();
ESP_ERROR_CHECK(esp_wifi_get_config(WIFI_IF_STA, &wifi_config));
ESP_LOGI(TAG, "Showing Joind AP details:");
ESP_LOGI(TAG, "*************************");
ESP_LOGI(TAG, "SSID: %s", wifi_config.sta.ssid);
ESP_LOGI(TAG, "Password: %s", wifi_config.sta.password);
ESP_LOGI(TAG, "Channel: %d", wifi_config.sta.channel);
ESP_LOGI(TAG, "bssid: %02x:%02x:%02x:%02x:%02x:%02x", wifi_config.sta.bssid[0],
wifi_config.sta.bssid[1], wifi_config.sta.bssid[2], wifi_config.sta.bssid[3],
wifi_config.sta.bssid[4], wifi_config.sta.bssid[5]);
}
return ESP_OK;
}
return ESP_OK;
}
static esp_err_t wifi_sta_join_op(wifi_op_t *self, int argc, char *argv[])
{
wifi_config_t wifi_config = { 0 };
if (strcmp("sta", argv[self->start_index - 1])) {
ESP_LOGE(TAG, "Error: Invalid command\n");
ESP_LOGE(TAG, "%s\n", self->help);
return ESP_FAIL;
}
strlcpy((char *) wifi_config.sta.ssid, argv[self->start_index + 1], sizeof(wifi_config.sta.ssid));
if (self->arg_cnt == 5) {
strlcpy((char *) wifi_config.sta.password, argv[self->start_index + 2], sizeof(wifi_config.sta.password));
}
wifi_init();
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
esp_wifi_connect();
int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS);
if ((bits & CONNECTED_BIT) == 0) {
ESP_LOGE(TAG, "Error: Wifi Connection timed out");
return ESP_OK;
}
return ESP_OK;
}
static esp_err_t wifi_sta_leave_op(wifi_op_t *self, int argc, char *argv[])
{
wifi_config_t wifi_config = { 0 };
if (strcmp("sta", argv[self->start_index - 1])) {
ESP_LOGE(TAG, "Error: Invalid command\n");
ESP_LOGE(TAG, "%s\n", self->help);
return ESP_FAIL;
}
esp_wifi_disconnect();
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
return ESP_OK;
}
/* handle 'wifi' command */
static esp_err_t do_cmd_wifi(int argc, char **argv)
{
int cmd_count = sizeof(cmd_list) / sizeof(cmd_list[0]);
wifi_op_t cmd;
for (int i = 0; i < cmd_count; i++) {
cmd = cmd_list[i];
if (argc < cmd.start_index + 1) {
continue;
}
if (!strcmp(cmd.name, argv[cmd.start_index])) {
/* Get interface for eligible commands */
if (cmd.arg_cnt == argc) {
if (cmd.operation != NULL) {
if (cmd.operation(&cmd, argc, argv) != ESP_OK) {
ESP_LOGE(TAG, "Usage:\n%s", cmd.help);
return 0;
}
}
return ESP_OK;
}
}
}
ESP_LOGE(TAG, "Command not available");
return ESP_OK;
}
/**
* @brief Registers the wifi command.
*
* @return
* - esp_err_t
*/
esp_err_t console_cmd_wifi_register(void)
{
esp_err_t ret;
esp_console_cmd_t command = {
.command = "wifi",
.help = "Command for wifi configuration and monitoring\n For more info run 'wifi help'",
.func = &do_cmd_wifi
};
ret = esp_console_cmd_register(&command);
if (ret) {
ESP_LOGE(TAG, "Unable to register wifi");
}
return ret;
}

View File

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "console_simple_init.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Registers the wifi command.
*
* @return
* - esp_err_t
*/
esp_err_t console_cmd_wifi_register(void);
#ifdef __cplusplus
}
#endif

View File

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

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "wifi-basic.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
console_cmd_wifi:
version: "*"
override_path: '../../../'

View File

@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "esp_netif.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "console_wifi.h"
void app_main(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_err_t ret = nvs_flash_init(); //Initialize NVS
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize console REPL
ESP_ERROR_CHECK(console_cmd_init());
ESP_ERROR_CHECK(console_cmd_wifi_register());
// start console REPL
ESP_ERROR_CHECK(console_cmd_start());
}

View File

@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
# -*- coding: utf-8 -*-
import pytest
@pytest.mark.esp32
def test_examples_ifconfig_command(dut):
dut.expect('esp>', timeout=30)
dut.write('wifi show network')
dut.expect(r'Showing Wifi networks', timeout=30)
dut.expect('esp>', timeout=30)

View File

@ -0,0 +1,10 @@
version: 1.0.1
url: https://github.com/espressif/esp-protocols/tree/master/components/console_cmd_wifi
description: The component offers a console that enables runtime wifi configuration and monitoring.
dependencies:
idf:
version: '>=5.0'
espressif/console_simple_init:
version: '>=1.1.0'
override_path: '../console_simple_init'
public: true

View File

@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(modem): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py esp_modem
tag_format: modem-v$version
version: 1.0.4
version: 1.0.5
version_files:
- idf_component.yml

View File

@ -1,5 +1,29 @@
# Changelog
## [1.0.5](https://github.com/espressif/esp-protocols/commits/modem-v1.0.5)
### Major changes
- Added support for implementing user defined modules in standard C-API ([Support for custom modules with C-API](https://github.com/espressif/esp-protocols/commit/0254d50))
### Bug Fixes
- Added test injecting unexpected replies ([b220d1e](https://github.com/espressif/esp-protocols/commit/b220d1e), [#426](https://github.com/espressif/esp-protocols/issues/426))
- Fixed inconsistent state on data after OK ([bf99f28](https://github.com/espressif/esp-protocols/commit/bf99f28), [#426](https://github.com/espressif/esp-protocols/issues/426))
- TLS example: Added restore session support in mbedtls-wrap ([79d38e5](https://github.com/espressif/esp-protocols/commit/79d38e5))
- Fixed examples to show netif on ppp-changed event ([a70b197](https://github.com/espressif/esp-protocols/commit/a70b197))
- remove unused GNU Make based buildsystem files ([d6b6f63](https://github.com/espressif/esp-protocols/commit/d6b6f63))
- specify override_path in example manifest files ([5b78da4](https://github.com/espressif/esp-protocols/commit/5b78da4))
- Added test-cases that exercise mode transitions ([aff571d](https://github.com/espressif/esp-protocols/commit/aff571d))
- Fixed mode transitions between any state and UNDEF ([93cb2ca](https://github.com/espressif/esp-protocols/commit/93cb2ca), [#320](https://github.com/espressif/esp-protocols/issues/320))
- Fixed API docs within doxygen comments ([020b407](https://github.com/espressif/esp-protocols/commit/020b407))
- Support for custom modules with C-API ([0254d50](https://github.com/espressif/esp-protocols/commit/0254d50))
- Fix CRLF issue with esp_modem_c_api.cpp ([2661b4d](https://github.com/espressif/esp-protocols/commit/2661b4d))
### Updated
- ci(common): Created reusable action for host and coverage tests ([9ad04de](https://github.com/espressif/esp-protocols/commit/9ad04de))
## [1.0.4](https://github.com/espressif/esp-protocols/commits/modem-v1.0.4)
### Bug Fixes

View File

@ -42,6 +42,9 @@ set_target_properties(${COMPONENT_LIB} PROPERTIES
CXX_EXTENSIONS ON
)
if(CONFIG_ESP_MODEM_ADD_CUSTOM_MODULE)
idf_component_optional_requires(PUBLIC main)
endif()
if(${target} STREQUAL "linux")
# This is needed for ESP_LOGx() macros, as integer formats differ on ESP32(..) and x64

View File

@ -45,4 +45,22 @@ menu "esp-modem"
to make the protocol more robust on noisy environments or when underlying
transport gets corrupted often (for example by Rx buffer overflows)
config ESP_MODEM_ADD_CUSTOM_MODULE
bool "Add support for custom module in C-API"
default n
help
If enabled, we adapt the C-API to create a DCE from a user defined class
config ESP_MODEM_CUSTOM_MODULE_HEADER
string "Header file name which defines custom DCE creation"
depends on ESP_MODEM_ADD_CUSTOM_MODULE
default "custom_module.hpp"
help
Name of the header file in the main component which implements esp_modem_create_custom_dce()
called from C-API for creating esp_modem_dce object.
This header provides definition of the custom module with some additional and/or updated commands
and API. It also defines creation of DCE based on this custom module, typically calling:
dce_factory::Factory::create_unique_dce_from<CustomModule, DCE*>(dce_config, std::move(dte), netif)
Please refer to the pppos_client example for more details.
endmenu

View File

@ -235,7 +235,9 @@ extern "C" void app_main(void)
if (c->get_count_of(&SetModeArgs::mode)) {
auto mode = c->get_string_of(&SetModeArgs::mode);
modem_mode dev_mode;
if (mode == "CMUX1") {
if (mode == "UNDEF") {
dev_mode = esp_modem::modem_mode::UNDEF;
} else if (mode == "CMUX1") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_MODE;
} else if (mode == "CMUX2") {
dev_mode = esp_modem::modem_mode::CMUX_MANUAL_EXIT;

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -17,26 +17,20 @@
#include "cxx_include/esp_modem_dce_module.hpp"
/**
* @brief Definition of a custom modem which inherits from the GenericModule, uses all its methods
* and could override any of them. Here, for demonstration purposes only, we redefine just `get_module_name()`
* @brief Definition of a custom DCE uses GenericModule and all its methods
* but could override command processing. Here, for demonstration purposes only,
* we "inject" URC handler to the actual command processing.
* This is possible since we inherit from `CommandableIf` and redefine `command()` method.
* Then we're able to use declare all common methods from the command library
* to be processed using "our" `command()` method (with custom URC handler).
*/
class MyShinyModem: public esp_modem::GenericModule {
using GenericModule::GenericModule;
public:
esp_modem::command_result get_module_name(std::string &name) override
{
name = "Custom Shiny Module";
return esp_modem::command_result::OK;
}
};
namespace Shiny {
using namespace esp_modem;
class DCE : public esp_modem::DCE_T<MyShinyModem>, public CommandableIf {
class DCE : public esp_modem::DCE_T<GenericModule>, public CommandableIf {
public:
using DCE_T<MyShinyModem>::DCE_T;
using DCE_T<GenericModule>::DCE_T;
command_result
command(const std::string &cmd, got_line_cb got_line, uint32_t time_ms) override
@ -97,7 +91,7 @@ public:
std::shared_ptr<esp_modem::DTE> dte,
esp_netif_t *netif)
{
return build_generic_DCE<MyShinyModem, DCE, std::unique_ptr<DCE>>(config, std::move(dte), netif);
return build_generic_DCE<GenericModule, DCE, std::unique_ptr<DCE>>(config, std::move(dte), netif);
}
};

View File

@ -6,6 +6,7 @@
#pragma once
#include <utility>
#include <memory>
#include <span>
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
@ -22,11 +23,13 @@ public:
Tls();
virtual ~Tls();
bool init(is_server server, do_verify verify);
bool deinit();
int handshake();
int write(const unsigned char *buf, size_t len);
int read(unsigned char *buf, size_t len);
[[nodiscard]] bool set_own_cert(const_buf crt, const_buf key);
[[nodiscard]] bool set_ca_cert(const_buf crt);
bool set_hostname(const char *name);
virtual int send(const unsigned char *buf, size_t len) = 0;
virtual int recv(unsigned char *buf, size_t len) = 0;
size_t get_available_bytes();
@ -41,6 +44,11 @@ protected:
mbedtls_entropy_context entropy_{};
virtual void delay() {}
bool set_session();
bool get_session();
void reset_session();
bool is_session_loaded();
private:
static void print_error(const char *function, int error_code);
static int bio_write(void *ctx, const unsigned char *buf, size_t len);
@ -48,5 +56,21 @@ private:
int mbedtls_pk_parse_key( mbedtls_pk_context *ctx,
const unsigned char *key, size_t keylen,
const unsigned char *pwd, size_t pwdlen);
struct unique_session {
unique_session()
{
::mbedtls_ssl_session_init(&s);
}
~unique_session()
{
::mbedtls_ssl_session_free(&s);
}
mbedtls_ssl_session *ptr()
{
return &s;
}
mbedtls_ssl_session s;
};
std::unique_ptr<unique_session> session_;
};

View File

@ -35,6 +35,16 @@ bool Tls::init(is_server server, do_verify verify)
return true;
}
bool Tls::deinit()
{
::mbedtls_ssl_config_free(&conf_);
::mbedtls_ssl_free(&ssl_);
::mbedtls_pk_free(&pk_key_);
::mbedtls_x509_crt_free(&public_cert_);
::mbedtls_x509_crt_free(&ca_cert_);
return true;
}
void Tls::print_error(const char *function, int error_code)
{
static char error_buf[100];
@ -106,6 +116,16 @@ bool Tls::set_ca_cert(const_buf crt)
return true;
}
bool Tls::set_hostname(const char *name)
{
int ret = mbedtls_ssl_set_hostname(&ssl_, name);
if (ret < 0) {
print_error("mbedtls_ssl_set_hostname", ret);
return false;
}
return true;
}
Tls::Tls()
{
mbedtls_x509_crt_init(&public_cert_);
@ -132,3 +152,39 @@ Tls::~Tls()
::mbedtls_x509_crt_free(&public_cert_);
::mbedtls_x509_crt_free(&ca_cert_);
}
bool Tls::get_session()
{
if (session_ == nullptr) {
session_ = std::make_unique<unique_session>();
}
int ret = ::mbedtls_ssl_get_session(&ssl_, session_->ptr());
if (ret != 0) {
print_error("mbedtls_ssl_get_session() failed", ret);
return false;
}
return true;
}
bool Tls::set_session()
{
if (session_ == nullptr) {
printf("session hasn't been initialized");
return false;
}
int ret = mbedtls_ssl_set_session(&ssl_, session_->ptr());
if (ret != 0) {
print_error("mbedtls_ssl_set_session() failed", ret);
return false;
}
return true;
}
void Tls::reset_session()
{
session_.reset(nullptr);
}
bool Tls::is_session_loaded()
{
return session_ != nullptr;
}

View File

@ -48,6 +48,13 @@ menu "Example Configuration"
bool "A7670"
help
A7670X is Multi-Band LTE-FDD/LTE-TDD/GSM/GPRS/EDGE module.
config EXAMPLE_MODEM_DEVICE_CUSTOM
select ESP_MODEM_ADD_CUSTOM_MODULE
bool "Custom device"
help
This demonstrates use of a custom device in C-API.
endchoice
config EXAMPLE_MODEM_PPP_APN

View File

@ -0,0 +1,79 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "cxx_include/esp_modem_api.hpp"
#include "cxx_include/esp_modem_command_library_utils.hpp"
/**
* @brief Definition of a custom module based on some already defined module
* Here we use GenericModule, but you can use any kind of device
* that is closer to your CustomModule.
*/
class SIM7600_WITH_TIME: public GenericModule {
/**
* @brief Need to reuse the constructors of our ancestor
*/
using GenericModule::GenericModule;
public:
/**
* @brief New command that is not defined in the GenericModule
*/
command_result get_time(std::string &time)
{
return esp_modem::dce_commands::generic_get_string(dte.get(), "AT+CCLK?\r", time);
}
/**
* @brief This command is already defined in the GenericModule
*
* Here we just modify get_signal_quality() to return zeroes
* for demonstration purpose only, since it's called within this example
*/
command_result get_signal_quality(int &rssi, int &ber) override
{
rssi = ber = 0;
return esp_modem::command_result::OK;
}
};
/**
* @brief esp_modem_create_custom_dce() needs to be defined, as it is called in C-API wrapper when creating esp_modem_dce
*
* This uses public factory function for creating a common DCE with our CustomModule. Creating raw DCE pointer is only needed
* for the C-API wrapper; C++API users would create DCE (any kind of smart pointer) directly with
* Factory::create_unique_dce_from<CustomModule>(dce_config, std::move(dte), netif);
*/
DCE *esp_modem_create_custom_dce(const esp_modem_dce_config_t *dce_config, std::shared_ptr<DTE> dte, esp_netif_t *netif)
{
return dce_factory::Factory::create_unique_dce_from<SIM7600_WITH_TIME, DCE *>(dce_config, std::move(dte), netif);
}
/**
* @brief This API is only needed for extending standard C-API, since we added get_time() method to our CustomModule
*
* @note This header is included from esp_modem_c_api.cpp, so it could use ESP_MODEM_C_API_STR_MAX macro
* indicating maximum C-API string size
*
* @note In order to access the newly added API get_time(), we have to static_cast<> the GenericModule from DCE
* to our CustomModule.
* Alternatively we could use the modem Factory to build our specific DCE_T<CustomModule>, but in that case
* we couldn't use our C-API wrappers which expect DCE type, DCE_T<GenericModule> with lib commands (this alternative
* is cleaner, but more suitable for C++ users)
*/
extern "C" esp_err_t esp_modem_get_time(esp_modem_dce_t *dce_wrap, char *p_time)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string time{ESP_MODEM_C_API_STR_MAX};
auto ret = command_response_to_esp_err(static_cast<SIM7600_WITH_TIME *>(dce_wrap->dce->get_module())->get_time(time));
if (ret == ESP_OK && !time.empty()) {
strlcpy(p_time, time.c_str(), ESP_MODEM_C_API_STR_MAX);
}
return ret;
}

View File

@ -37,6 +37,10 @@ static const int CONNECT_BIT = BIT0;
static const int GOT_DATA_BIT = BIT2;
static const int USB_DISCONNECTED_BIT = BIT3; // Used only with USB DTE but we define it unconditionally, to avoid too many #ifdefs in the code
#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM
esp_err_t esp_modem_get_time(esp_modem_dce_t *dce_wrap, char *p_time);
#endif
#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
#include "esp_modem_usb_c_api.h"
#include "esp_modem_usb_config.h"
@ -105,8 +109,8 @@ static void on_ppp_changed(void *arg, esp_event_base_t event_base,
ESP_LOGI(TAG, "PPP state changed event %" PRIu32, event_id);
if (event_id == NETIF_PPP_ERRORUSER) {
/* User interrupted event from esp-netif */
esp_netif_t *netif = event_data;
ESP_LOGI(TAG, "User interrupted event from netif:%p", netif);
esp_netif_t **p_netif = event_data;
ESP_LOGI(TAG, "User interrupted event from netif:%p", *p_netif);
}
}
@ -192,6 +196,9 @@ void app_main(void)
#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);
#elif CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM == 1
ESP_LOGI(TAG, "Initializing esp_modem with custom module...");
esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_CUSTOM, &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);
@ -258,6 +265,18 @@ void app_main(void)
}
ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d", rssi, ber);
#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM
{
char time[64];
err = esp_modem_get_time(dce, time);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_modem_get_time failed with %d %s", err, esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "esp_modem_get_time: %s", time);
}
#endif
#if CONFIG_EXAMPLE_SEND_MSG
if (esp_modem_sms_txt_mode(dce, true) != ESP_OK || esp_modem_sms_character_set(dce) != ESP_OK) {
ESP_LOGE(TAG, "Setting text mode or GSM character set failed");

View File

@ -1,4 +1,4 @@
version: "1.0.4"
version: "1.0.5"
description: Library for communicating with cellular modems in command and data modes
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem
issues: https://github.com/espressif/esp-protocols/issues

View File

@ -54,7 +54,8 @@ 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);
command_result set_data_mode_alt(CommandableIf *t);
command_result set_pdp_context(CommandableIf *t, PdpContext &pdp, uint32_t timeout_ms);
/**
* @}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -25,18 +25,16 @@ command_result generic_command(CommandableIf *t, const std::string &command,
* @brief Utility command to send command and return reply (after DCE says OK)
* @param t Anything that is "command-able"
* @param command Command to issue
* @param output String to return
* @param timeout_ms
* @param output String to return (could be either std::string& or std::string_view&)
* @param timeout_ms Command timeout in ms
* @return Generic command return type (OK, FAIL, TIMEOUT)
*/
command_result generic_get_string(CommandableIf *t, const std::string &command, std::string &output, uint32_t timeout_ms = 500);
template <typename T> command_result generic_get_string(CommandableIf *t, const std::string &command, T &output, uint32_t timeout_ms = 500);
/**
* @brief Generic command that passes on "OK" and fails on "ERROR"
* @param t Anything that is "command-able"
* @param command Command to issue
* @param timeout
* @param timeout_ms Command timeout in ms
* @return Generic command return type (OK, FAIL, TIMEOUT)
*/

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -12,6 +12,7 @@
* @brief DCE modem factory
*/
#define esp_modem_create_dce_from dce_factory::Factory::create_unique_dce_from
namespace esp_modem::dce_factory {
@ -236,12 +237,12 @@ public:
return nullptr;
}
template <typename T_Module>
static std::unique_ptr<DCE> create_unique_dce_from(const esp_modem::dce_config *config,
std::shared_ptr<esp_modem::DTE> dte,
esp_netif_t *netif)
template <typename T_Module, typename Ptr = std::unique_ptr<DCE>>
static Ptr create_unique_dce_from(const esp_modem::dce_config *config,
std::shared_ptr<esp_modem::DTE> dte,
esp_netif_t *netif)
{
return build_generic_DCE<T_Module, DCE, std::unique_ptr<DCE>>(config, std::move(dte), netif);
return build_generic_DCE<T_Module, DCE, Ptr>(config, std::move(dte), netif);
}
private:

View File

@ -144,6 +144,8 @@ class SIM7070: public GenericModule {
using GenericModule::GenericModule;
public:
command_result power_down() override;
command_result set_data_mode() override;
};
/**
@ -162,7 +164,6 @@ class SIM800: public GenericModule {
using GenericModule::GenericModule;
public:
command_result power_down() override;
command_result set_data_mode() override;
};
/**
@ -170,6 +171,8 @@ public:
*/
class BG96: public GenericModule {
using GenericModule::GenericModule;
public:
command_result set_pdp_context(PdpContext &pdp) override;
};
/**

View File

@ -76,6 +76,7 @@ public:
* @param command Command to be sent
* @param got_line callback if a line received
* @param time_ms timeout in milliseconds
* @param separator Character treated as a line separator, typically '\n'
* @return OK, FAIL or TIMEOUT
*/
virtual command_result command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator) = 0;

View File

@ -53,6 +53,7 @@ typedef enum esp_modem_dce_device {
ESP_MODEM_DCE_SIM7000,
ESP_MODEM_DCE_BG96,
ESP_MODEM_DCE_SIM800,
ESP_MODEM_DCE_CUSTOM
} esp_modem_dce_device_t;
/**

View File

@ -25,8 +25,8 @@ ESP_MODEM_DECLARE_DCE_COMMAND(sync, command_result, 0) \
\
/**
* @brief Reads the operator name
* @param[out] operator name
* @param[out] access technology
* @param[out] name operator name
* @param[out] act access technology
* @return OK, FAIL or TIMEOUT
*/ \
ESP_MODEM_DECLARE_DCE_COMMAND(get_operator_name, command_result, 2, STRING_OUT(p1, name), INT_OUT(p2, act)) \
@ -44,9 +44,21 @@ ESP_MODEM_DECLARE_DCE_COMMAND(store_profile, command_result, 0) \
*/\
ESP_MODEM_DECLARE_DCE_COMMAND(set_pin, command_result, 1, STRING_IN(p1, pin)) \
\
/**
* @brief Execute the supplied AT command in raw mode (doesn't append '\r' to command, returns everything)
* @param[in] cmd String command that's send to DTE
* @param[out] out Raw output from DTE
* @param[in] pass Pattern in response for the API to return OK
* @param[in] fail Pattern in response for the API to return FAIL
* @param[in] cmd String command that's send to DTE
* @param[in] timeout AT command timeout in milliseconds
* @return OK, FAIL or TIMEOUT
*/\
ESP_MODEM_DECLARE_DCE_COMMAND(at_raw, command_result, 5, STRING_IN(p1, cmd), STRING_OUT(p2, out), STRING_IN(p3, pass), STRING_IN(p4, fail), INT_IN(p5, timeout)) \
\
/**
* @brief Execute the supplied AT command
* @param[in] at AT command
* @param[in] cmd AT command
* @param[out] out Command output string
* @param[in] timeout AT command timeout in milliseconds
* @return OK, FAIL or TIMEOUT
@ -252,6 +264,8 @@ ESP_MODEM_DECLARE_DCE_COMMAND(set_preferred_mode, command_result, 1, INT_IN(p1,
/**
* @brief Set network bands for CAT-M or NB-IoT
* @param[in] mode CAT-M or NB-IoT
* @param[in] bands bitmap in hex representing bands
* @param[in] size size of teh bands bitmap
* @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)) \

View File

@ -31,7 +31,7 @@
*/
struct esp_modem_vfs_uart_creator {
const char *dev_name; /*!< VFS device name, e.g. /dev/uart/n */
const struct esp_modem_uart_term_config uart; /*!< UART driver init struct */
struct esp_modem_uart_term_config uart; /*!< UART driver init struct */
};
/**

View File

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

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -51,7 +51,35 @@ command_result generic_command(CommandableIf *t, const std::string &command,
return generic_command(t, command, pass, fail, timeout_ms);
}
static command_result generic_get_string(CommandableIf *t, const std::string &command, std::string_view &output, uint32_t timeout_ms = 500)
/*
* Purpose of this namespace is to provide different means of assigning the result to a string-like parameter.
* By default we assign strings, which comes with an allocation. Alternatively we could take `std::span`
* with user's buffer and directly copy the result, thus avoiding allocations (this is not used as of now)
*/
namespace str_copy {
bool set(std::string &dest, std::string_view &src)
{
dest = src;
return true;
}
/* This is an example of using std::span output in generic_get_string()
bool set(std::span<char> &dest, std::string_view &src)
{
if (dest.size() >= src.size()) {
std::copy(src.begin(), src.end(), dest.data());
dest = dest.subspan(0, src.size());
return true;
}
ESP_LOGE(TAG, "Cannot set result of size %d (to span of size %d)", dest.size(), src.size());
return false;
}
*/
} // str_copy
template <typename T> command_result generic_get_string(CommandableIf *t, const std::string &command, T &output, uint32_t timeout_ms)
{
ESP_LOGV(TAG, "%s", __func__ );
return t->command(command, [&](uint8_t *data, size_t len) {
@ -70,7 +98,9 @@ static command_result generic_get_string(CommandableIf *t, const std::string &co
} else if (token.find("ERROR") != std::string::npos) {
return command_result::FAIL;
} else if (token.size() > 2) {
output = token;
if (!str_copy::set(output, token)) {
return command_result::FAIL;
}
}
response = response.substr(pos + 1);
}
@ -78,18 +108,6 @@ static command_result generic_get_string(CommandableIf *t, const std::string &co
}, timeout_ms);
}
command_result generic_get_string(CommandableIf *t, const std::string &command, std::string &output, uint32_t timeout_ms)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
auto ret = generic_get_string(t, command, out, timeout_ms);
if (ret == command_result::OK) {
output = out;
}
return ret;
}
command_result generic_command_common(CommandableIf *t, const std::string &command, uint32_t timeout_ms)
{
ESP_LOGV(TAG, "%s", __func__ );
@ -153,7 +171,7 @@ command_result hang_up(CommandableIf *t)
command_result get_battery_status(CommandableIf *t, int &voltage, int &bcs, int &bcl)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
std::string out;
auto ret = generic_get_string(t, "AT+CBC\r", out);
if (ret != command_result::OK) {
return ret;
@ -189,7 +207,7 @@ command_result get_battery_status(CommandableIf *t, int &voltage, int &bcs, int
command_result get_battery_status_sim7xxx(CommandableIf *t, int &voltage, int &bcs, int &bcl)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
std::string out;
auto ret = generic_get_string(t, "AT+CBC\r", out);
if (ret != command_result::OK) {
return ret;
@ -224,7 +242,7 @@ command_result set_flow_control(CommandableIf *t, int dce_flow, int dte_flow)
command_result get_operator_name(CommandableIf *t, std::string &operator_name, int &act)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
std::string out;
auto ret = generic_get_string(t, "AT+COPS?\r", out, 75000);
if (ret != command_result::OK) {
return ret;
@ -261,24 +279,29 @@ command_result set_echo(CommandableIf *t, bool on)
return generic_command_common(t, "ATE0\r");
}
command_result set_pdp_context(CommandableIf *t, PdpContext &pdp)
command_result set_pdp_context(CommandableIf *t, PdpContext &pdp, uint32_t timeout_ms)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string pdp_command = "AT+CGDCONT=" + std::to_string(pdp.context_id) +
",\"" + pdp.protocol_type + "\",\"" + pdp.apn + "\"\r";
return generic_command_common(t, pdp_command, 150000);
return generic_command_common(t, pdp_command, timeout_ms);
}
command_result set_pdp_context(CommandableIf *t, PdpContext &pdp)
{
return set_pdp_context(t, pdp, 1000);
}
command_result set_data_mode(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 set_data_mode_sim8xx(CommandableIf *t)
command_result set_data_mode_alt(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)
@ -356,7 +379,7 @@ command_result set_cmux(CommandableIf *t)
command_result read_pin(CommandableIf *t, bool &pin_ok)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
std::string out;
auto ret = generic_get_string(t, "AT+CPIN?\r", out);
if (ret != command_result::OK) {
return ret;
@ -389,10 +412,26 @@ command_result at(CommandableIf *t, const std::string &cmd, std::string &out, in
return generic_get_string(t, at_command, out, timeout);
}
command_result at_raw(CommandableIf *t, const std::string &cmd, std::string &out, const std::string &pass, const std::string &fail, int timeout = 500)
{
ESP_LOGV(TAG, "%s", __func__ );
return t->command(cmd, [&](uint8_t *data, size_t len) {
out.assign(reinterpret_cast<char *>(data), len);
if (out.find(pass) != std::string::npos) {
return command_result::OK;
} else if (out.find(fail) != std::string::npos) {
return command_result::FAIL;
}
return command_result::TIMEOUT;
}, timeout);
}
command_result get_signal_quality(CommandableIf *t, int &rssi, int &ber)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
std::string out;
auto ret = generic_get_string(t, "AT+CSQ\r", out);
if (ret != command_result::OK) {
return ret;
@ -430,7 +469,7 @@ command_result set_network_attachment_state(CommandableIf *t, int state)
command_result get_network_attachment_state(CommandableIf *t, int &state)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
std::string out;
auto ret = generic_get_string(t, "AT+CGATT?\r", out);
if (ret != command_result::OK) {
return ret;
@ -457,7 +496,7 @@ command_result set_radio_state(CommandableIf *t, int state)
command_result get_radio_state(CommandableIf *t, int &state)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
std::string out;
auto ret = generic_get_string(t, "AT+CFUN?\r", out);
if (ret != command_result::OK) {
return ret;
@ -522,7 +561,7 @@ command_result set_network_bands_sim76xx(CommandableIf *t, const std::string &mo
command_result get_network_system_mode(CommandableIf *t, int &mode)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
std::string out;
auto ret = generic_get_string(t, "AT+CNSMOD?\r", out);
if (ret != command_result::OK) {
return ret;
@ -550,7 +589,7 @@ command_result set_gnss_power_mode(CommandableIf *t, int mode)
command_result get_gnss_power_mode(CommandableIf *t, int &mode)
{
ESP_LOGV(TAG, "%s", __func__ );
std::string_view out;
std::string out;
auto ret = generic_get_string(t, "AT+CGNSPWR?\r", out);
if (ret != command_result::OK) {
return ret;

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -96,6 +96,11 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
{
switch (m) {
case modem_mode::UNDEF:
if (!dte->set_mode(m)) {
return false;
}
mode = m;
return true;
case modem_mode::DUAL_MODE: // Only DTE can be in Dual mode
break;
case modem_mode::COMMAND_MODE:
@ -151,7 +156,7 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
mode = modem_mode::CMUX_MANUAL_MODE;
return true;
case modem_mode::CMUX_MANUAL_EXIT:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
if (mode != modem_mode::CMUX_MANUAL_MODE && mode != modem_mode::UNDEF) {
return false;
}
if (!dte->set_mode(m)) {
@ -160,7 +165,7 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
mode = modem_mode::COMMAND_MODE;
return true;
case modem_mode::CMUX_MANUAL_SWAP:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
if (mode != modem_mode::CMUX_MANUAL_MODE && mode != modem_mode::UNDEF) {
return false;
}
if (!dte->set_mode(m)) {
@ -168,12 +173,12 @@ bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m
}
return true;
case modem_mode::CMUX_MANUAL_DATA:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
if (mode != modem_mode::CMUX_MANUAL_MODE && mode != modem_mode::UNDEF) {
return false;
}
return transitions::enter_data(*dte, *device, netif);
case modem_mode::CMUX_MANUAL_COMMAND:
if (mode != modem_mode::CMUX_MANUAL_MODE) {
if (mode != modem_mode::CMUX_MANUAL_MODE && mode != modem_mode::UNDEF) {
return false;
}
return transitions::exit_data(*dte, *device, netif);

View File

@ -151,10 +151,14 @@ command_result DTE::command(const std::string &cmd, got_line_cb got_line, uint32
bool DTE::exit_cmux()
{
if (!cmux_term) {
return false;
}
if (!cmux_term->deinit()) {
return false;
}
exit_cmux_internal();
cmux_term.reset();
return true;
}
@ -174,6 +178,10 @@ void DTE::exit_cmux_internal()
bool DTE::setup_cmux()
{
if (cmux_term) {
ESP_LOGE("esp_modem_dte", "Cannot setup_cmux(), cmux_term already exists");
return false;
}
cmux_term = std::make_shared<CMux>(primary_term, std::move(buffer));
if (cmux_term == nullptr) {
return false;
@ -198,6 +206,11 @@ bool DTE::setup_cmux()
bool DTE::set_mode(modem_mode m)
{
// transitions (any) -> UNDEF
if (m == modem_mode::UNDEF) {
mode = m;
return true;
}
// transitions (COMMAND|UNDEF) -> CMUX
if (m == modem_mode::CMUX_MODE) {
if (mode == modem_mode::UNDEF || mode == modem_mode::COMMAND_MODE) {
@ -246,7 +259,7 @@ bool DTE::set_mode(modem_mode m)
return false;
}
// manual CMUX transitions: Exit CMUX
if (m == modem_mode::CMUX_MANUAL_EXIT && mode == modem_mode::CMUX_MANUAL_MODE) {
if (m == modem_mode::CMUX_MANUAL_EXIT && (mode == modem_mode::CMUX_MANUAL_MODE || mode == modem_mode::UNDEF)) {
if (exit_cmux()) {
mode = modem_mode::COMMAND_MODE;
return true;
@ -255,7 +268,7 @@ bool DTE::set_mode(modem_mode m)
return false;
}
// manual CMUX transitions: Swap terminals
if (m == modem_mode::CMUX_MANUAL_SWAP && mode == modem_mode::CMUX_MANUAL_MODE) {
if (m == modem_mode::CMUX_MANUAL_SWAP && (mode == modem_mode::CMUX_MANUAL_MODE || mode == modem_mode::UNDEF)) {
secondary_term.swap(primary_term);
set_command_callbacks();
return true;
@ -334,6 +347,9 @@ void DTE::on_read(got_line_cb on_read_cb)
bool DTE::command_cb::process_line(uint8_t *data, size_t consumed, size_t len)
{
if (result != command_result::TIMEOUT) {
return false; // this line has been processed already (got OK or FAIL previously)
}
if (memchr(data + consumed, separator, len)) {
result = got_line(data, consumed + len);
if (result == command_result::OK || result == command_result::FAIL) {

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -67,6 +67,11 @@ command_result SIM7070::power_down()
return dce_commands::power_down_sim70xx(dte.get());
}
command_result SIM7070::set_data_mode()
{
return dce_commands::set_data_mode_alt(dte.get());
}
command_result SIM7000::power_down()
{
return dce_commands::power_down_sim70xx(dte.get());
@ -77,9 +82,9 @@ command_result SIM800::power_down()
return dce_commands::power_down_sim8xx(dte.get());
}
command_result SIM800::set_data_mode()
command_result BG96::set_pdp_context(esp_modem::PdpContext &pdp)
{
return dce_commands::set_data_mode_sim8xx(dte.get());
return dce_commands::set_pdp_context(dte.get(), pdp, 300);
}
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -36,7 +36,7 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
} else if (command == "ATO\r") {
response = "ERROR\r\n";
} else if (command.find("ATD") != std::string::npos) {
response = "CONNECT\r\n";
response = "CONNECT\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) {
@ -74,6 +74,9 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
}
if (len > 2 && data[0] == 0xf9) { // Simple CMUX responder
// turn the request into a reply -> implements CMUX loopback
// Note: This simple CMUX responder only updates CMUX headers and replaces payload.
// It means that all responses (that we test) must be shorter or equal to the requests
// For example ATD (dial command): sizeof("ATD*99#") >= sizeof("CONNECT");
if (data[2] == 0x3f || data[2] == 0x53) { // SABM command
data[2] = 0x73;
} else if (data[2] == 0xef) { // Generic request

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -33,6 +33,20 @@ TEST_CASE("DTE command races", "[esp_modem]")
// this command should either timeout or finish successfully
CHECK((ret == command_result::TIMEOUT || ret == command_result::OK));
}
// Now we test the same, but with some garbage after the expected data and inject the reply in chunks by 3 bytes
uint8_t resp2[] = {'O', 'K', '\n', '1', '2', '\n'};
for (int i = 0; i < 1000; ++i) {
loopback->inject(&resp2[0], sizeof(resp2), 3, /* 1ms before injecting reply */0, 0);
auto ret = dce->command("check\n", [&](uint8_t *data, size_t len) {
if (len > 0 && data[0] == 'O') { // expected reply only when it starts with '0'
return command_result::OK;
}
return esp_modem::command_result::TIMEOUT;
}, 1);
// this command should either timeout or finish successfully
CHECK((ret == command_result::TIMEOUT || ret == command_result::OK));
}
}
TEST_CASE("Test polymorphic delete for custom device/dte", "[esp_modem]")
@ -247,3 +261,88 @@ TEST_CASE("Test CMUX protocol by injecting payloads", "[esp_modem]")
CHECK(ret == command_result::OK);
}
}
TEST_CASE("Command and Data mode transitions", "[esp_modem][transitions]")
{
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);
// UNDEF -> CMD (OK)
uint8_t resp[] = "DISCONNECTED\n";
loopback->inject(&resp[0], sizeof(resp), sizeof(resp), /* 10ms before injecting reply */100, 0);
loopback->write(nullptr, 0); /* this triggers sending the injected response */
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
loopback->inject(nullptr, 0, 0, 0, 0); /* reset injection, use synchronous replies now */
// CMD -> CMD (Fail)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == false);
// Forcing transition to CMD (via UNDEF)
// CMD -> UNDEF (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::UNDEF) == true);
// UNDEF -> CMD (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
// CMD -> DATA (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::DATA_MODE) == true);
// DATA -> CMD (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
}
TEST_CASE("CMUX mode transitions", "[esp_modem][transitions]")
{
auto term = std::make_unique<LoopbackTerm>();
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);
// UNDEF -> CMUX (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MODE) == true);
// CMUX -> DATA (Fail)
CHECK(dce->set_mode(esp_modem::modem_mode::DATA_MODE) == false);
// CMUX back -> CMD (OK)
CHECK(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE) == true);
}
TEST_CASE("CMUX manual mode transitions", "[esp_modem][transitions]")
{
auto term = std::make_unique<LoopbackTerm>();
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);
// Happy flow transitions of Manual CMUX transitions
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA) == true);
// Cannot test CMUX_MANUAL_DATA -> CMUX_MANUAL_COMMAND with our mocked terminal for now
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT) == true);
// Check some out of order manual transitions, most of them are allowed,
// but some fail as modem layers report issues with specific steps
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP) == false); // cannot go directly to SWAP
CHECK(dce->set_mode(esp_modem::modem_mode::UNDEF) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP) == true); // can go via UNDEF
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT) == false); // EXIT is allowed, but CMUX terms don't exist
CHECK(dce->set_mode(esp_modem::modem_mode::UNDEF) == true);
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE) == true); // Enter CMUX (via UNDEF)
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA) == true); // Go directly to DATA mode
CHECK(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT) == true); // Exit CMUX
CHECK(dce->set_mode(esp_modem::modem_mode::UNDEF) == true); // Succeeds from any state
}

View File

@ -64,8 +64,8 @@ static void on_ppp_changed(void *arg, esp_event_base_t event_base,
ESP_LOGI(TAG, "PPP state changed event %" PRId32, event_id);
if (event_id == NETIF_PPP_ERRORUSER) {
/* User interrupted event from esp-netif */
esp_netif_t *netif = static_cast<esp_netif_t *>(event_data);
ESP_LOGI(TAG, "User interrupted event from netif:%p", netif);
auto p_netif = static_cast<esp_netif_t **>(event_data);
ESP_LOGI(TAG, "User interrupted event from netif:%p", *p_netif);
xEventGroupSetBits(event_group, 2);
}
}

View File

@ -0,0 +1,10 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components
$ENV{IDF_PATH}/examples/common_components/iperf
"../..")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(pppd_test)

View File

@ -0,0 +1,48 @@
# Target test for measuring TCP/UDP network performance
## Overview
The aim of this test is to run `iperf` application to measure network throughput of the esp_modem library.
### Configure PPP server
This test uses a network DCE device, which only needs a PPP server. To run the PPP server, use this command:
```
sudo pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 ms-dns 8.8.8.8 modem local noauth debug nocrtscts nodetach +ipv6
```
### Running using pytest
For checking the performance, you only need to execute the pytest, which will measure the network throughput automatically and report the resultant values. For running the pytest, you need to:
* install IDF pytest packages by running: `./install.sh --enable-pytest`
* add IDF internal packages to python path: `export PYTHONPATH=${DIF_PATH}/tools/ci/python_packages/`
* run the pytest as **superuser**
It's useful to note that when running the test multiple times, you can use `pytest --skip-autoflash y` so the pytest wouldn't have to always reprogram the DUT.
### Performance summary
Here's an example of the resultant summary logged by the pytest
```
2023-11-29 18:28:25 INFO [Performance][tcp_tx_throughput]: 0.75 Mbps
2023-11-29 18:28:25 INFO [Performance][tcp_rx_throughput]: 0.70 Mbps
2023-11-29 18:28:25 INFO [Performance][udp_tx_throughput]: 0.73 Mbps
2023-11-29 18:28:25 INFO [Performance][udp_rx_throughput]: 0.70 Mbps
```
### Running the iperf manually
Execute `idf.py flash monitor` in one terminal and after connecting to the PPP server (after getting an IP address), you can use standard `iperf` commands.
In another terminal, you need to execute the iperf counterpart.
For example running for checking UDP performance, and running server on ESP32, please run:
* iperf -u -s -i 3 (in ESP32 terminal)
* iperf -u -c SERVER_IP -t 60 -i 3 (on the host side)
Note that command `pppd info` will print actual IP addresses:
```
iperf> pppd info
ppp:
IP: 192.168.11.2
MASK: 255.255.255.255
GW: 192.168.11.3
```

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "NetworkDCE.cpp"
"cmd_pppclient.c"
"pppd_iperf_main.c")

View File

@ -0,0 +1,96 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "cxx_include/esp_modem_dte.hpp"
#include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp"
#include "cxx_include/esp_modem_dce_factory.hpp"
#include <memory>
#include <utility>
using namespace esp_modem;
using namespace esp_modem::dce_factory;
class NetModule;
typedef DCE_T<NetModule> NetDCE;
/**
* @brief Custom factory which can build and create a DCE using a custom module
*/
class NetDCE_Factory: public Factory {
public:
template <typename T, typename ...Args>
static DCE_T<T> *create(const config *cfg, Args &&... args)
{
return build_generic_DCE<T>(cfg, std::forward<Args>(args)...);
}
};
/**
* @brief This is a null-module, doesn't define any AT commands, just passes everything to pppd
*/
class NetModule: public ModuleIf {
public:
explicit NetModule(std::shared_ptr<DTE> dte, const esp_modem_dce_config *cfg):
dte(std::move(dte)) {}
bool setup_data_mode() override
{
return true;
}
bool set_mode(modem_mode mode) override
{
return true;
}
static esp_err_t init(esp_netif_t *netif)
{
// configure
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
dte_config.uart_config.baud_rate = 921600; // check also 460800
esp_modem_dce_config dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("");
// create DTE and minimal network DCE
auto uart_dte = create_uart_dte(&dte_config);
dce = NetDCE_Factory::create<NetModule>(&dce_config, uart_dte, netif);
return dce == nullptr ? ESP_FAIL : ESP_OK;
}
static void deinit()
{
delete dce;
}
static void start()
{
dce->set_data();
}
static void stop()
{
dce->exit_data();
}
private:
static NetDCE *dce;
std::shared_ptr<DTE> dte;
};
NetDCE *NetModule::dce = nullptr;
extern "C" esp_err_t modem_init_network(esp_netif_t *netif)
{
return NetModule::init(netif);
}
extern "C" esp_err_t modem_start_network()
{
NetModule::start();
return ESP_OK;
}
extern "C" void modem_stop_network()
{
NetModule::stop();
}

View File

@ -0,0 +1,289 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "sys/socket.h" // for INADDR_ANY
#include "esp_netif.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_netif_ppp.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_console.h"
#include "esp_event.h"
#include "esp_bit_defs.h"
#include "argtable3/argtable3.h"
#include "iperf.h"
#include "sdkconfig.h"
#include "network_dce.h"
static const char *TAG = "pppd_test";
static EventGroupHandle_t event_group = NULL;
static esp_netif_t *s_ppp_netif;
static const int GOTIP_BIT = BIT0;
static void on_modem_event(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == IP_EVENT) {
ESP_LOGD(TAG, "IP event! %" PRIu32, event_id);
if (event_id == IP_EVENT_PPP_GOT_IP) {
esp_netif_dns_info_t dns_info;
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
esp_netif_t *netif = event->esp_netif;
ESP_LOGI(TAG, "Modem Connect to PPP Server");
ESP_LOGI(TAG, "~~~~~~~~~~~~~~");
ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask));
ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw));
esp_netif_get_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns_info);
ESP_LOGI(TAG, "Name Server1: " IPSTR, IP2STR(&dns_info.ip.u_addr.ip4));
esp_netif_get_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns_info);
ESP_LOGI(TAG, "Name Server2: " IPSTR, IP2STR(&dns_info.ip.u_addr.ip4));
ESP_LOGI(TAG, "~~~~~~~~~~~~~~");
ESP_LOGI(TAG, "GOT ip event!!!");
xEventGroupSetBits(event_group, GOTIP_BIT);
} else if (event_id == IP_EVENT_PPP_LOST_IP) {
ESP_LOGI(TAG, "Modem Disconnect from PPP Server");
} else if (event_id == IP_EVENT_GOT_IP6) {
ESP_LOGI(TAG, "GOT IPv6 event!");
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
ESP_LOGI(TAG, "Got IPv6 address " IPV6STR, IPV62STR(event->ip6_info.ip));
}
}
}
static void on_ppp_changed(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
ESP_LOGI(TAG, "PPP state changed event %" PRId32, event_id);
if (event_id == NETIF_PPP_ERRORUSER) {
/* User interrupted event from esp-netif */
esp_netif_t *netif = (esp_netif_t *)event_data;
ESP_LOGI(TAG, "User interrupted event from netif:%p", netif);
xEventGroupSetBits(event_group, 2);
}
}
/* "pppd" command */
static struct {
struct arg_str *control;
struct arg_end *end;
} pppd_control_args;
static int pppd_cmd_control(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&pppd_control_args);
if (nerrors != 0) {
arg_print_errors(stderr, pppd_control_args.end, argv[0]);
return 1;
}
if (!strncmp(pppd_control_args.control->sval[0], "info", 4)) {
esp_netif_ip_info_t ip;
printf("%s:\n", esp_netif_get_desc(s_ppp_netif));
esp_netif_get_ip_info(s_ppp_netif, &ip);
printf(" IP: " IPSTR "\r\n", IP2STR(&ip.ip));
printf(" MASK: " IPSTR "\r\n", IP2STR(&ip.netmask));
printf(" GW: " IPSTR "\r\n", IP2STR(&ip.gw));
}
return 0;
}
/* "iperf" command */
static struct {
struct arg_str *ip;
struct arg_lit *server;
struct arg_lit *udp;
struct arg_lit *version;
struct arg_int *port;
struct arg_int *length;
struct arg_int *interval;
struct arg_int *time;
struct arg_int *bw_limit;
struct arg_lit *abort;
struct arg_end *end;
} iperf_args;
static int ppp_cmd_iperf(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&iperf_args);
iperf_cfg_t cfg;
if (nerrors != 0) {
arg_print_errors(stderr, iperf_args.end, argv[0]);
return 0;
}
memset(&cfg, 0, sizeof(cfg));
// ethernet iperf only support IPV4 address
cfg.type = IPERF_IP_TYPE_IPV4;
/* iperf -a */
if (iperf_args.abort->count != 0) {
iperf_stop();
return 0;
}
if (((iperf_args.ip->count == 0) && (iperf_args.server->count == 0)) ||
((iperf_args.ip->count != 0) && (iperf_args.server->count != 0))) {
ESP_LOGE(__func__, "Wrong mode! ESP32 should run in client or server mode");
return 0;
}
/* iperf -s */
if (iperf_args.ip->count == 0) {
cfg.flag |= IPERF_FLAG_SERVER;
}
/* iperf -c SERVER_ADDRESS */
else {
cfg.destination_ip4 = esp_ip4addr_aton(iperf_args.ip->sval[0]);
cfg.flag |= IPERF_FLAG_CLIENT;
}
if (iperf_args.length->count == 0) {
cfg.len_send_buf = 0;
} else {
cfg.len_send_buf = iperf_args.length->ival[0];
}
/* wait for ip, could blocked here */
xEventGroupWaitBits(event_group, GOTIP_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
cfg.source_ip4 = INADDR_ANY;
/* iperf -u */
if (iperf_args.udp->count == 0) {
cfg.flag |= IPERF_FLAG_TCP;
} else {
cfg.flag |= IPERF_FLAG_UDP;
}
/* iperf -p */
if (iperf_args.port->count == 0) {
cfg.sport = IPERF_DEFAULT_PORT;
cfg.dport = IPERF_DEFAULT_PORT;
} else {
if (cfg.flag & IPERF_FLAG_SERVER) {
cfg.sport = iperf_args.port->ival[0];
cfg.dport = IPERF_DEFAULT_PORT;
} else {
cfg.sport = IPERF_DEFAULT_PORT;
cfg.dport = iperf_args.port->ival[0];
}
}
/* iperf -i */
if (iperf_args.interval->count == 0) {
cfg.interval = IPERF_DEFAULT_INTERVAL;
} else {
cfg.interval = iperf_args.interval->ival[0];
if (cfg.interval <= 0) {
cfg.interval = IPERF_DEFAULT_INTERVAL;
}
}
/* iperf -t */
if (iperf_args.time->count == 0) {
cfg.time = IPERF_DEFAULT_TIME;
} else {
cfg.time = iperf_args.time->ival[0];
if (cfg.time <= cfg.interval) {
cfg.time = cfg.interval;
}
}
/* iperf -b */
if (iperf_args.bw_limit->count == 0) {
cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT;
} else {
cfg.bw_lim = iperf_args.bw_limit->ival[0];
if (cfg.bw_lim <= 0) {
cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT;
}
}
printf("mode=%s-%s sip=" IPSTR ":%" PRIu16 ", dip=%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".%" PRIu32 ":%" PRIu16 ", interval=%" PRIu32 ", time=%" PRIu32 "\r\n",
cfg.flag & IPERF_FLAG_TCP ? "tcp" : "udp",
cfg.flag & IPERF_FLAG_SERVER ? "server" : "client",
(uint16_t) cfg.source_ip4 & 0xFF,
(uint16_t) (cfg.source_ip4 >> 8) & 0xFF,
(uint16_t) (cfg.source_ip4 >> 16) & 0xFF,
(uint16_t) (cfg.source_ip4 >> 24) & 0xFF,
cfg.sport,
cfg.destination_ip4 & 0xFF, (cfg.destination_ip4 >> 8) & 0xFF,
(cfg.destination_ip4 >> 16) & 0xFF, (cfg.destination_ip4 >> 24) & 0xFF, cfg.dport,
cfg.interval, cfg.time);
iperf_start(&cfg);
return 0;
}
void register_pppd(void)
{
event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
esp_netif_config_t ppp_netif_config = ESP_NETIF_DEFAULT_PPP();
s_ppp_netif = esp_netif_new(&ppp_netif_config);
assert(s_ppp_netif);
esp_netif_ppp_config_t ppp_config = { true, true };
esp_netif_ppp_set_params(s_ppp_netif, &ppp_config);
ESP_ERROR_CHECK(modem_init_network(s_ppp_netif));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_modem_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_changed, NULL));
modem_start_network();
pppd_control_args.control = arg_str1(NULL, NULL, "<info>", "Get info of pppd");
pppd_control_args.end = arg_end(1);
const esp_console_cmd_t cmd = {
.command = "pppd",
.help = "PPP interface IO control",
.hint = NULL,
.func = pppd_cmd_control,
.argtable = &pppd_control_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
iperf_args.ip = arg_str0("c", "client", "<ip>",
"run in client mode, connecting to <host>");
iperf_args.server = arg_lit0("s", "server", "run in server mode");
iperf_args.udp = arg_lit0("u", "udp", "use UDP rather than TCP");
iperf_args.version = arg_lit0("V", "ipv6_domain", "use IPV6 address rather than IPV4");
iperf_args.port = arg_int0("p", "port", "<port>",
"server port to listen on/connect to");
iperf_args.length = arg_int0("l", "len", "<length>", "set read/write buffer size");
iperf_args.interval = arg_int0("i", "interval", "<interval>",
"seconds between periodic bandwidth reports");
iperf_args.time = arg_int0("t", "time", "<time>", "time in seconds to transmit for (default 10 secs)");
iperf_args.bw_limit = arg_int0("b", "bandwidth", "<bandwidth>", "bandwidth to send at in Mbits/sec");
iperf_args.abort = arg_lit0("a", "abort", "abort running iperf");
iperf_args.end = arg_end(1);
const esp_console_cmd_t iperf_cmd = {
.command = "iperf",
.help = "iperf command",
.hint = NULL,
.func = &ppp_cmd_iperf,
.argtable = &iperf_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&iperf_cmd));
}

View File

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "esp_err.h"
#include "esp_netif.h"
/**
* @brief Initialize PPP network (with a generic PPP server)
* @param netif PPP netif
* @return ESP_OK on success
*/
esp_err_t modem_init_network(esp_netif_t *netif);
/**
* @brief Start PPP network
*/
void modem_start_network();
/**
* @brief Stop PPP network
*/
void modem_stop_network();

View File

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_console.h"
#include "cmd_system.h"
void register_pppd(void);
void app_main(void)
{
esp_console_repl_t *repl = NULL;
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
repl_config.prompt = "iperf>";
// init console REPL environment
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
/* Register commands */
register_system_common();
register_pppd();
printf("\n =======================================================\n");
printf(" | Steps to Test PPP Client Bandwidth |\n");
printf(" | |\n");
printf(" | 1. Enter 'help', check all supported commands |\n");
printf(" | 2. Start PPP server on host system |\n");
printf(" | - pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 modem local noauth debug nocrtscts nodetach +ipv6\n");
printf(" | 3. Wait ESP32 to get IP from PPP server |\n");
printf(" | 4. Enter 'pppd info' (optional) |\n");
printf(" | 5. Server: 'iperf -u -s -i 3' |\n");
printf(" | 6. Client: 'iperf -u -c SERVER_IP -t 60 -i 3' |\n");
printf(" | |\n");
printf(" =======================================================\n\n");
// start console REPL
ESP_ERROR_CHECK(esp_console_start_repl(repl));
}

View File

@ -0,0 +1,112 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
"""
Test case for iperf example.
This test case might have problem running on Windows:
- use `sudo killall iperf` to force kill iperf, didn't implement windows version
"""
from __future__ import division, unicode_literals
import logging
import os
import subprocess
import pytest
from common_test_methods import get_host_ip4_by_dest_ip
from idf_iperf_test_util import IperfUtility
from pytest_embedded import Dut
try:
from typing import Any, Callable, Tuple
except ImportError:
# Only used for type annotations
pass
NO_BANDWIDTH_LIMIT = -1 # iperf send bandwidth is not limited
class IperfTestUtilityEth(IperfUtility.IperfTestUtility):
""" iperf test implementation """
def __init__(self, dut: str, config_name: str, pc_nic_ip: str, pc_iperf_log_file: str, test_result:Any=None) -> None:
IperfUtility.IperfTestUtility.__init__(self, dut, config_name, 'None', 'None', pc_nic_ip, pc_iperf_log_file, test_result)
def setup(self) -> Tuple[str,int]:
"""
setup iperf test:
1. kill current iperf process
2. reboot DUT (currently iperf is not very robust, need to reboot DUT)
"""
try:
subprocess.check_output('sudo killall iperf 2>&1 > /dev/null', shell=True)
except subprocess.CalledProcessError:
pass
self.dut.write('restart')
dut_ip = self.dut.expect(r'pppd_test: IP : (\d+\.\d+\.\d+\.\d+)').group(1)
self.dut.expect("Type 'help' to get the list of commands.")
self.dut.expect('iperf>')
rssi = 0
return dut_ip, rssi
@pytest.fixture
def log_performance(record_property: Callable[[str, object], None]) -> Callable[[str, str], None]:
"""
log performance item with pre-defined format to the console
and record it under the ``properties`` tag in the junit report if available.
"""
def real_func(item: str, value: str) -> None:
"""
:param item: performance item name
:param value: performance value
"""
logging.info('[Performance][%s]: %s', item, value)
return real_func
def test_esp_pppd_iperf(
dut: Dut,
log_performance: Callable[[str, object], None],
# check_performance: Callable[[str, float, str], None],
) -> None:
"""
steps: |
1. test TCP tx rx and UDP tx rx throughput
2. compare with the pre-defined pass standard
"""
# 1. preparing
pc_iperf_log_file = os.path.join(dut.logdir, 'pc_iperf_log.md')
# 2. wait for DUT
dut_ip = dut.expect(r'pppd_test: IP : (\d+\.\d+\.\d+\.\d+)').group(1)
pc_nic_ip = get_host_ip4_by_dest_ip(dut_ip)
test_result = {
'tcp_tx': IperfUtility.TestResult('tcp', 'tx', 'ppp_client'),
'tcp_rx': IperfUtility.TestResult('tcp', 'rx', 'ppp_client'),
'udp_tx': IperfUtility.TestResult('udp', 'tx', 'ppp_client'),
'udp_rx': IperfUtility.TestResult('udp', 'rx', 'ppp_client'),
}
test_utility = IperfTestUtilityEth(dut, 'ppp', pc_nic_ip, pc_iperf_log_file, test_result)
# 3. run test for TCP Tx, Rx and UDP Tx, Rx
test_utility.run_test('tcp', 'tx', 0, 5)
test_utility.run_test('tcp', 'rx', 0, 5)
test_utility.run_test('udp', 'tx', 0, 5)
test_utility.run_test('udp', 'rx', 0, 5)
# 4. print out performance details
for throughput_type in test_result:
print('{}_throughput'.format(throughput_type))
print(test_result[throughput_type])
print('---------------------------')
# 5. log performance summary
for throughput_type in test_result:
log_performance('{}_throughput'.format(throughput_type),
'{:.02f} Mbps'.format(test_result[throughput_type].get_best_throughput()))

View File

@ -0,0 +1,3 @@
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096

View File

@ -0,0 +1,8 @@
# 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.8)
set(EXTRA_COMPONENT_DIRS "../.." "../../examples/modem_tcp_client/components")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ota_test)

View File

@ -0,0 +1,27 @@
# Target test running OTA update
## Overview
The aim of this test is to exercise the most commonly failing scenario, running OTA over PPPoS with https.
This project opens a data session, runs basic mqtt operations and initiates OTA update.
It supports the following test configurations:
* Using a real modem device (default config)
* Using VFS device (only to exercise VFS DTE)
* Using network-only DCE (connecting directly to PPP server) -- needs some configuration
### Configuring the PPP server
You need to run these applications on your host machine:
* PPP server
```bash
sudo pppd /dev/ttyUSB1 115200 192.168.11.1:192.168.11.2 ms-dns 8.8.8.8 modem local noauth debug nocrtscts nodetach +ipv6
```
* MQTT broker: Running mosquitto in the default config is enough, configuring the broker's URL to the local PPP address: `config.broker.address.uri = "mqtt://192.168.11.1";`
* HTTP server: Need to support HTTP/1.1 (to support ranges). You can use the script `http_server.py` and configure the OTA endpoint as `"https://192.168.11.1:1234/esp32.bin"`
## Potential issues
When running this test it is expected to experience some buffer overflows or connection interruption.
The modem library should recover from these failure modes and continue and complete OTA update.
These issues are expected, since UART ISR is deliberately not placed into IRAM in the test configuration to exhibit some minor communication glitches.

Binary file not shown.

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS manual_ota.cpp transport_batch_tls.cpp
INCLUDE_DIRS "."
PRIV_REQUIRES extra_tcp_transports esp_http_client app_update)

View File

@ -0,0 +1,317 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "manual_ota.hpp"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_app_format.h"
#include "esp_http_client.h"
#include "esp_partition.h"
#include "esp_transport_tcp.h"
#include "transport_batch_tls.hpp"
static const char *TAG = "manual_ota";
bool manual_ota::begin()
{
if (status != state::UNDEF) {
ESP_LOGE(TAG, "Invalid state for manual_ota::perform");
return false;
}
const esp_partition_t *configured = esp_ota_get_boot_partition();
const esp_partition_t *running = esp_ota_get_running_partition();
if (configured != running) {
ESP_LOGE(TAG, "Configured OTA boot partition at offset 0x%08" PRIx32 ", but running from offset 0x%08" PRIx32, configured->address, running->address);
return false;
}
status = state::INIT;
max_buffer_size_ = size_ * 1024;
if (mode_ == mode::BATCH) {
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT
esp_transport_handle_t tcp = esp_transport_tcp_init();
ssl_ = esp_transport_batch_tls_init(tcp, max_buffer_size_);
http_.config_.transport = ssl_;
if (!esp_transport_batch_set_ca_cert(ssl_, http_.config_.cert_pem, 0)) {
return fail();
}
if (!esp_transport_batch_set_cn(ssl_, common_name_)) {
return fail();
}
#else
ESP_LOGE(TAG, "mode::BATCH Cannot be used without CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT");
return false;
#endif
}
if (!http_.init()) {
return fail();
}
image_length_ = http_.get_image_len();
if (image_length_ <= 0) {
return fail();
}
if (image_length_ > size_) {
if (!http_.set_range(0, max_buffer_size_ - 1)) {
return fail();
}
}
esp_http_client_set_method(http_.handle_, HTTP_METHOD_GET);
partition_ = esp_ota_get_next_update_partition(nullptr);
if (partition_ == nullptr) {
ESP_LOGE(TAG, "Invalid update partition");
return false;
}
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%" PRIx32, partition_->subtype, partition_->address);
file_length_ = 0;
reconnect_attempts_ = 0;
buffer_.resize(max_buffer_size_);
status = state::IMAGE_CHECK;
return true;
}
bool manual_ota::perform()
{
if (status != state::IMAGE_CHECK && status != state::START) {
ESP_LOGE(TAG, "Invalid state for manual_ota::perform");
return false;
}
esp_err_t err = esp_http_client_open(http_.handle_, 0);
if (err != ESP_OK) {
if (image_length_ == file_length_) {
status = state::END;
return false;
}
esp_http_client_close(http_.handle_);
ESP_LOGI(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
if (reconnect_attempts_++ < max_reconnect_attempts_) {
return true; // will retry in the next iteration
}
return fail();
}
esp_http_client_fetch_headers(http_.handle_);
int batch_len = max_buffer_size_;
if (mode_ == mode::BATCH) {
batch_len = esp_transport_batch_tls_pre_read(ssl_, max_buffer_size_, timeout_ * 1000);
if (batch_len < 0) {
ESP_LOGE(TAG, "Error: Failed to pre-read plain text data!");
fail();
return false;
}
}
int data_read = esp_http_client_read(http_.handle_, buffer_.data(), batch_len);
if (data_read < 0) {
ESP_LOGE(TAG, "Error: SSL data read error");
return fail();
} else if (data_read > 0) {
esp_http_client_close(http_.handle_);
if (status == state::IMAGE_CHECK) {
esp_app_desc_t new_app_info;
if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
// check current version with downloading
memcpy(&new_app_info, &buffer_[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version);
esp_app_desc_t running_app_info;
const esp_partition_t *running = esp_ota_get_running_partition();
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
}
const esp_partition_t *last_invalid_app = esp_ota_get_last_invalid_partition();
esp_app_desc_t invalid_app_info;
if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) {
ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version);
}
// check current version with last invalid partition
if (last_invalid_app != NULL) {
if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
ESP_LOGW(TAG, "New version is the same as invalid version.");
ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
ESP_LOGW(TAG, "The firmware has been rolled back to the previous version.");
return fail();
}
}
status = state::START;
err = esp_ota_begin(partition_, OTA_WITH_SEQUENTIAL_WRITES, &update_handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
return fail();
}
ota_begin = true;
ESP_LOGI(TAG, "esp_ota_begin succeeded");
} else {
ESP_LOGE(TAG, "Received chunk doesn't contain app descriptor");
return fail();
}
}
err = esp_ota_write(update_handle_, (const void *)buffer_.data(), data_read);
if (err != ESP_OK) {
return fail();
}
file_length_ += data_read;
ESP_LOGI(TAG, "Written image length %d", file_length_);
if (image_length_ == file_length_) {
status = state::END;
return false;
}
if (!prepare_reconnect()) {
return fail();
}
} else if (data_read == 0) {
if (file_length_ == 0) {
// Try to handle possible HTTP redirections
if (!http_.handle_redirects()) {
return fail();
}
err = esp_http_client_open(http_.handle_, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
return fail();
}
esp_http_client_fetch_headers(http_.handle_);
}
}
return true;
}
bool manual_ota::prepare_reconnect()
{
esp_http_client_set_method(http_.handle_, HTTP_METHOD_GET);
return http_.set_range(file_length_,
(image_length_ - file_length_) > max_buffer_size_ ? (file_length_ + max_buffer_size_ - 1) : 0);
}
bool manual_ota::fail()
{
status = state::FAIL;
return false;
}
bool manual_ota::end()
{
if (status == state::END) {
if (!http_.is_data_complete()) {
ESP_LOGE(TAG, "Error in receiving complete file");
return fail();
}
esp_err_t err = esp_ota_end(update_handle_);
if (err != ESP_OK) {
if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
ESP_LOGE(TAG, "Image validation failed, image is corrupted");
} else {
ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err));
}
return fail();
}
ota_begin = false;
err = esp_ota_set_boot_partition(partition_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
return fail();
}
return true;
}
return false;
}
manual_ota::~manual_ota()
{
if (ota_begin) {
esp_ota_abort(update_handle_);
}
}
bool manual_ota::http_client::handle_redirects()
{
int status_code = esp_http_client_get_status_code(handle_);
ESP_LOGW(TAG, "Status code: %d", status_code);
esp_err_t err = esp_http_client_set_redirection(handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "URL redirection Failed");
return false;
}
err = esp_http_client_open(handle_, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
return false;
}
esp_http_client_fetch_headers(handle_);
return true;
}
bool manual_ota::http_client::set_range(size_t from, size_t to)
{
char *header_val = nullptr;
if (to != 0) {
asprintf(&header_val, "bytes=%d-%d", from, to);
} else {
asprintf(&header_val, "bytes=%d-", from);
}
if (header_val == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for HTTP header");
return false;
}
esp_http_client_set_header(handle_, "Range", header_val);
free(header_val);
return true;
}
bool manual_ota::http_client::is_data_complete()
{
return esp_http_client_is_complete_data_received(handle_);
}
manual_ota::http_client::~http_client()
{
if (handle_) {
esp_http_client_close(handle_);
esp_http_client_cleanup(handle_);
}
}
bool manual_ota::http_client::init()
{
handle_ = esp_http_client_init(&config_);
return handle_ != nullptr;
}
int64_t manual_ota::http_client::get_image_len()
{
esp_http_client_set_method(handle_, HTTP_METHOD_HEAD);
esp_err_t err = esp_http_client_perform(handle_);
if (err == ESP_OK) {
int http_status = esp_http_client_get_status_code(handle_);
if (http_status != HttpStatus_Ok) {
ESP_LOGE(TAG, "Received incorrect http status %d", http_status);
return -1;
}
} else {
ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err);
return -1;
}
int64_t image_length = esp_http_client_get_content_length(handle_);
ESP_LOGI(TAG, "image_length = %lld", image_length);
esp_http_client_close(handle_);
return image_length;
}

View File

@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include <vector>
#include "esp_http_client.h"
#include "esp_partition.h"
#include "esp_transport_tcp.h"
#include "esp_ota_ops.h"
class manual_ota {
public:
/**
* @brief Set the preferred mode
*/
enum class mode {
BATCH, /**< Read data chunk from TCP and pass it to SSL, restore session on reconnection */
NORMAL /**< Use standard partial download, continuously passing data from TCP to mbedTLS */
} mode_ {mode::BATCH};
/**
* @brief Set the OTA batch size in kB
*
* This would allocate two big buffers:
* - one for reading from TCP socket and
* - one for passing to mbedTLS for description
*/
size_t size_{32};
/**
* @brief Set timeout in seconds
*
* This is the network timeout, so if less data than the batch size received
* the timeout (and no EOF) we should proceed with passing the data to mbedtls
*/
int timeout_{2};
/**
* @brief Set common name of the server to verify
*/
const char *common_name_;
/**
* @brief Wrapper around the http client -- Please set the http config
*/
class http_client {
friend class manual_ota;
~http_client();
bool init();
esp_http_client_handle_t handle_{nullptr};
bool handle_redirects();
bool set_range(size_t from, size_t to);
bool is_data_complete();
int64_t get_image_len();
public:
esp_http_client_config_t config_{}; /**< Configure the http connection parameters */
} http_;
/**
* @brief Construct a new manual ota object
*/
explicit manual_ota() {}
~manual_ota();
/**
* @brief Start the manual OTA process
*
* @return true if started successfully
*/
bool begin();
/**
* @brief Performs one read-write OTA iteration
*
* @return true if the process is in progress
* @return false if the process finished, call end() to get OTA result
*/
bool perform();
/**
* @brief Finishes an OTA update
*
* @return true if the OTA update completed successfully
*/
bool end();
private:
enum class state {
UNDEF,
INIT,
IMAGE_CHECK,
START,
END,
FAIL,
};
int64_t image_length_;
size_t file_length_;
size_t max_buffer_size_{size_ * 1024};
const esp_partition_t *partition_{nullptr};
state status{state::UNDEF};
std::vector<char> buffer_{};
int reconnect_attempts_;
const int max_reconnect_attempts_{3};
esp_transport_handle_t ssl_;
esp_ota_handle_t update_handle_{0};
bool ota_begin;
bool prepare_reconnect();
bool fail();
};

View File

@ -0,0 +1,249 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <vector>
#include "esp_log.h"
#include "mbedtls_wrap.hpp"
#include "esp_transport_tcp.h"
#define TAG "batch-tls"
class TlsTransport: public Tls {
public:
explicit TlsTransport(esp_transport_handle_t parent):
Tls(), transport_(parent), last_timeout(0), read_len(0), offset(0) {}
int send(const unsigned char *buf, size_t len) override;
int recv(unsigned char *buf, size_t len) override;
static bool set_func(esp_transport_handle_t tls_transport);
int preread(size_t len, int timeout_ms);
bool prepare_buffer(size_t max_size);
private:
esp_transport_handle_t transport_{};
int connect(const char *host, int port, int timeout_ms);
void delay() override;
struct transport {
static int connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms);
static int read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms);
static int write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms);
static int close(esp_transport_handle_t t);
static int poll_read(esp_transport_handle_t t, int timeout_ms);
static int poll_write(esp_transport_handle_t t, int timeout_ms);
static int destroy(esp_transport_handle_t t);
};
int last_timeout;
std::vector<char> buf;
size_t read_len;
size_t offset;
};
esp_transport_handle_t esp_transport_tls_init(esp_transport_handle_t parent)
{
esp_transport_handle_t transport_handle = esp_transport_init();
auto *tls_context = new TlsTransport(parent);
esp_transport_set_context_data(transport_handle, tls_context);
TlsTransport::set_func(transport_handle);
return transport_handle;
}
int TlsTransport::send(const unsigned char *buf, size_t len)
{
int ret = esp_transport_write(transport_, reinterpret_cast<const char *>(buf), len, 0);
ESP_LOGD(TAG, "writing(len=%d) ret=%d", len, ret);
return ret;
}
int TlsTransport::recv(unsigned char *buffer, size_t len)
{
ESP_LOGD(TAG, "recv(len=%d)", len);
if (read_len != 0) {
if (read_len > len) {
memcpy((char *)buffer, buf.data() + offset, len);
read_len -= len;
offset += len;
ESP_LOGD(TAG, "read %d from batch read_len = %d", len, read_len);
return len;
} else {
int remaining = len = read_len;
if (remaining > 0) {
memcpy((char *)buffer, buf.data() + offset, remaining);
read_len = 0;
offset = 0;
return remaining;
}
read_len = 0;
offset = 0;
return ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN;
}
}
int ret = esp_transport_read(transport_, reinterpret_cast<char *>(buffer), len, last_timeout);
if (ret == ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT) {
return MBEDTLS_ERR_SSL_WANT_READ;
}
return ret == ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN ? 0 : ret;
}
bool TlsTransport::set_func(esp_transport_handle_t tls_transport)
{
return esp_transport_set_func(tls_transport, TlsTransport::transport::connect, TlsTransport::transport::read, TlsTransport::transport::write, TlsTransport::transport::close, TlsTransport::transport::poll_read, TlsTransport::transport::poll_write, TlsTransport::transport::destroy) == ESP_OK;
}
int TlsTransport::connect(const char *host, int port, int timeout_ms)
{
return esp_transport_connect(transport_, host, port, timeout_ms);
}
void TlsTransport::delay()
{
vTaskDelay(pdMS_TO_TICKS(500));
}
int TlsTransport::transport::connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms)
{
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
tls->init(is_server{false}, do_verify{true});
ESP_LOGD(TAG, "TLS-connect");
auto ret = tls->connect(host, port, timeout_ms);
if (ret < 0) {
ESP_LOGI(TAG, "Failed to connect to transport");
return ret;
}
if (tls->is_session_loaded()) {
tls->set_session();
}
ESP_LOGI(TAG, "Before handshake");
ret = tls->handshake();
if (ret < 0) {
ESP_LOGI(TAG, "Failed to handshake");
return ret;
}
if (!tls->get_session()) {
// we're not able to save session, report an error and continue (next connection will be slower)
ESP_LOGW(TAG, "Failed to save session");
}
ESP_LOGI(TAG, "After handshake");
return 0;
}
int TlsTransport::transport::read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms)
{
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
ESP_LOGD(TAG, "available=%d tls->read_len=%d", tls->get_available_bytes(), tls->read_len);
if (tls->get_available_bytes() <= 0 && tls->read_len == 0) {
ESP_LOGD(TAG, "red(len=%d, timeout=%d) tls->read_len=%d", len, timeout_ms, tls->read_len);
tls->last_timeout = timeout_ms;
int poll = esp_transport_poll_read(t, timeout_ms);
if (poll == -1) {
return ERR_TCP_TRANSPORT_CONNECTION_FAILED;
}
if (poll == 0) {
return ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT;
}
}
auto ret = tls->read(reinterpret_cast<unsigned char *>(buffer), len);
if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
ret = ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT;
}
ESP_LOGD(TAG, "red(len=%d, timeout=%d) ret=%d", len, timeout_ms, ret);
return ret;
}
int TlsTransport::transport::write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms)
{
int poll;
if ((poll = esp_transport_poll_write(t, timeout_ms)) <= 0) {
ESP_LOGW(TAG, "Poll timeout or error timeout_ms=%d", timeout_ms);
return poll;
}
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
int ret = tls->write(reinterpret_cast<const unsigned char *>(buffer), len);
ESP_LOGD(TAG, "write ret=%d", ret);
return ret;
}
int TlsTransport::transport::close(esp_transport_handle_t t)
{
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
int ret = esp_transport_close(tls->transport_);
tls->deinit();
return ret;
}
int TlsTransport::transport::poll_read(esp_transport_handle_t t, int timeout_ms)
{
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
return esp_transport_poll_read(tls->transport_, timeout_ms);
}
int TlsTransport::transport::poll_write(esp_transport_handle_t t, int timeout_ms)
{
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
return esp_transport_poll_write(tls->transport_, timeout_ms);
}
int TlsTransport::transport::destroy(esp_transport_handle_t t)
{
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
return esp_transport_destroy(tls->transport_);
}
esp_transport_handle_t esp_transport_batch_tls_init(esp_transport_handle_t parent, const size_t max_buffer_size)
{
esp_transport_handle_t ssl = esp_transport_init();
auto *tls = new TlsTransport(parent);
esp_transport_set_context_data(ssl, tls);
TlsTransport::set_func(ssl);
tls->prepare_buffer(max_buffer_size);
return ssl;
}
bool esp_transport_batch_set_ca_cert(esp_transport_handle_t t, const char *ca_cert, size_t cert_len = 0)
{
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
const_buf cert((const unsigned char *)ca_cert, cert_len ? cert_len : strlen(ca_cert) + 1);
return tls->set_ca_cert(cert);
}
bool esp_transport_batch_set_cn(esp_transport_handle_t t, const char *name)
{
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
return tls->set_hostname(name);
}
int TlsTransport::preread(size_t len, int timeout_ms)
{
while (len != read_len) {
int l = esp_transport_read(transport_, buf.data() + read_len, len - read_len, timeout_ms);
ESP_LOGD(TAG, "need %d read %d already %d", len, l, read_len);
if ((l == ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN || l == ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT ) && read_len > 0) {
return read_len;
}
if (l <= 0) {
read_len = 0;
return read_len;
}
read_len += l;
}
return read_len;
}
bool TlsTransport::prepare_buffer(size_t max_size)
{
buf.resize(max_size);
return true;
}
int esp_transport_batch_tls_pre_read(esp_transport_handle_t t, size_t len, int timeout_ms)
{
auto tls = static_cast<TlsTransport *>(esp_transport_get_context_data(t));
return tls->preread(len, timeout_ms);
}

View File

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
/**
* @brief Creates batch transport
*
* @param parent tcp-transport handle to the parent transport
* @param max_buffer_size maximum size of one batch
* @return created transport handle
*/
esp_transport_handle_t esp_transport_batch_tls_init(esp_transport_handle_t parent, const size_t max_buffer_size);
/**
* @brief Performs batch read operation from the underlying transport
*
* @param t Transport handle
* @param len Batch size
* @param timeout_ms Timeout in ms
* @return true If read from the parent transport completed successfully
*/
bool esp_transport_batch_tls_pre_read(esp_transport_handle_t t, size_t len, int timeout_ms);
/**
* @brief Set the CA Certificate to verify the server
*
* @param ca_cert Pointer to the CA Cert data
* @param cert_len CA Cert data len (set to 0 if null terminated string, i.e. PEM format)
* @return true on success
*/
bool esp_transport_batch_set_ca_cert(esp_transport_handle_t t, const char *ca_cert, size_t cert_len);
/**
* @brief Set comman name
* @param t
* @param ca_cert
* @param cert_len
* @return
*/
bool esp_transport_batch_set_cn(esp_transport_handle_t t, const char *name);

View File

@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import ssl
from http.server import HTTPServer
from RangeHTTPServer import RangeRequestHandler
server_address = ('0.0.0.0', 1234)
httpd = HTTPServer(server_address, RangeRequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket,
server_side=True,
certfile='srv.crt',
keyfile='srv.key',
ssl_version=ssl.PROTOCOL_TLS)
httpd.serve_forever()

Some files were not shown because too many files have changed in this diff Show More