Compare commits

...

48 Commits

Author SHA1 Message Date
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
93d140875f feat(console): Added component with iperf command 2024-01-16 19:18:01 +11: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
47736a2556 fix(common): Fixed scripts for build test failure 2024-01-16 01:59:11 +11: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
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
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
104 changed files with 4575 additions and 81 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_iperf: build-tests"
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
build_console_cmd_iperf:
if: contains(github.event.pull_request.labels.*.name, 'console') || github.event_name == 'push'
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.1", "release-v5.0"]
idf_target: ["esp32"]
test: [ { app: iperf-basic, path: "components/console_cmd_iperf/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_cmd_ping/examples" }]
test: [ { app: ping-basic, path: "components/console_cmd_ping/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

@ -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}
@ -95,5 +95,8 @@ jobs:
components/mdns;
components/console_simple_init;
components/console_cmd_ping;
components/console_cmd_ifconfig;
components/console_cmd_wifi;
components/console_cmd_iperf;
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

@ -41,3 +41,11 @@ Please refer to instructions in [ESP-IDF](https://github.com/espressif/esp-idf)
### 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

@ -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_iperf
tag_format: console_cmd_iperf-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_iperf-v1.0.0)
### Features
- Added component with iperf command ([93d14087](https://github.com/espressif/esp-protocols/commit/93d14087))

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "console_iperf.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,52 @@
# Console command iperf
The component provides a console where the 'iperf' command can be executed 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_iperf.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());
```
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 iperf command skip calling console_cmd_all_register()
ESP_ERROR_CHECK(console_cmd_iperf_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:
### iperf:
```
iperf [-suVa] [-c <ip>] [-p <port>] [-l <length>] [-i <interval>] [-t <time>] [-b <bandwidth>]
Command to measure network performance, through TCP or UDP connections.
-c, --client=<ip> run in client mode, connecting to <host>
-s, --server run in server mode
-u, --udp use UDP rather than TCP
-V, --ipv6_domain use IPV6 address rather than IPV4
-p, --port=<port> server port to listen on/connect to
-l, --len=<length> set read/write buffer size
-i, --interval=<interval> seconds between periodic bandwidth reports
-t, --time=<time> time in seconds to transmit for (default 10 secs)
-b, --bandwidth=<bandwidth> bandwidth to send at in Mbits/sec
-a, --abort abort running iperf
```

View File

@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.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 "sdkconfig.h"
#include "iperf.h"
#include "console_iperf.h"
/* "iperf" command */
/**
* 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_iperf",
.plugin_regd_fn = &console_cmd_iperf_register
};
static const char *TAG = "console_iperf";
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 do_cmd_iperf(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&iperf_args);
/* ethernet iperf only support IPV4 address */
iperf_cfg_t cfg = {.type = IPERF_IP_TYPE_IPV4};
if (nerrors != 0) {
arg_print_errors(stderr, iperf_args.end, argv[0]);
return 0;
}
/* 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];
}
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;
}
/**
* @brief Registers the iperf command.
*
* @return
* - esp_err_t
*/
esp_err_t console_cmd_iperf_register(void)
{
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);
esp_err_t ret;
const esp_console_cmd_t command = {
.command = "iperf",
.help = "Command to measure network performance, through TCP or UDP connections.",
.hint = NULL,
.func = &do_cmd_iperf,
.argtable = &iperf_args
};
ret = esp_console_cmd_register(&command);
if (ret) {
ESP_LOGE(TAG, "Unable to register iperf");
}
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 iperf command.
*
* @return
* - esp_err_t
*/
esp_err_t console_cmd_iperf_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(iperf-basic)

View File

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

View File

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

View File

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "esp_netif.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "console_iperf.h"
void app_main(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Initialize console REPL
ESP_ERROR_CHECK(console_cmd_init());
ESP_ERROR_CHECK(console_cmd_all_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_iperf_command(dut):
dut.expect('esp>', timeout=30)
dut.write('help iperf')
dut.expect('esp>', timeout=5)
pass

View File

@ -0,0 +1,13 @@
version: 1.0.0
url: https://github.com/espressif/esp-protocols/tree/master/components/console_cmd_iperf
description: The component provides a console where the 'iperf' command can be executed.
dependencies:
idf:
version: '>=5.0'
espressif/console_simple_init:
version: '>=1.1.0'
override_path: '../console_simple_init'
public: true
iperf:
version: "*"
path: '${IDF_PATH}/examples/common_components/iperf'

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

@ -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

@ -29,6 +29,7 @@ public:
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();

View File

@ -116,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_);

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

@ -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

@ -44,6 +44,18 @@ 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] cmd AT command

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,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -17,7 +17,7 @@
#include "esp_private/c_api_wrapper.hpp"
#ifndef ESP_MODEM_C_API_STR_MAX
#define ESP_MODEM_C_API_STR_MAX 64
#define ESP_MODEM_C_API_STR_MAX 128
#endif
#ifndef HAVE_STRLCPY
@ -206,6 +206,20 @@ extern "C" esp_err_t esp_modem_get_imsi(esp_modem_dce_t *dce_wrap, char *p_imsi)
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) {

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
*/
@ -261,24 +261,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)
@ -389,6 +394,22 @@ 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__ );

View File

@ -347,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

@ -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]")

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()

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS ota_test.cpp
INCLUDE_DIRS ".")

View File

@ -0,0 +1,60 @@
menu "Test Configuration"
choice TEST_DEVICE
prompt "Choose supported modem device (DCE)"
default TEST_DEVICE_MODEM_GENERIC
help
Select modem device connected to the ESP DTE.
config TEST_DEVICE_MODEM_GENERIC
bool "Common modem device"
help
Generic device that could be used with most common modems (BG96, SIM76xx, A76xx).
config TEST_DEVICE_PPPD_SERVER
bool "PPPD Server"
help
Test device is a pppd service in server mode, running on linux.
endchoice
config TEST_MODEM_APN
string "Modem APN"
depends on TEST_DEVICE_MODEM_GENERIC
default "lpwa.vodafone.com"
help
Set APN (Access Point Name), a logical name to choose data network
config TEST_USE_VFS_TERM
bool "Use VFS terminal"
default n
help
Demonstrate use of VFS as a communication terminal of the DTE.
VFS driver implements non-block reads, writes and selects to communicate with esp-modem,
but this implementation uses UART resource only.
config TEST_OTA_URI
string "URI of the binary"
default "https://192.168.11.1/esp32.bin"
help
HTTPS address of the update binary.
config TEST_OTA_CA_CERT
string "Server certificate"
default "---paste the server side certificate here---"
help
Insert the CA cert of the server side here. copy the base64 text between -----BEGIN CERTIFICATE-----
and -----END CERTIFICATE-----.
config TEST_OTA_CN
string "Server common name"
default "192.168.11.1"
help
Insert the server's common name to be checked within verification of the Server side certificat
config BROKER_URI
string "Broker URL"
default "mqtt://test.mosquitto.org"
help
URL of an mqtt broker which this example connects to.
endmenu

View File

@ -0,0 +1,55 @@
/*
* 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>
class NetModule;
/**
* @brief Custom factory which can build and create a DCE using a custom module
*/
class NetDCE_Factory: public esp_modem::dce_factory::Factory {
public:
template <typename T, typename ...Args>
static auto create(const esp_modem::dce_factory::config *cfg, Args &&... args) -> std::shared_ptr<esp_modem::DCE_T<T>>
{
return build_generic_DCE<T, esp_modem::DCE_T<T>, std::shared_ptr<esp_modem::DCE_T<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 esp_modem::ModuleIf {
public:
explicit NetModule(std::shared_ptr<esp_modem::DTE> dte, const esp_modem_dce_config *cfg):
dte(std::move(dte)) {}
bool setup_data_mode() override
{
return true;
}
bool set_mode(esp_modem::modem_mode mode) override
{
return true;
}
private:
std::shared_ptr<esp_modem::DTE> dte;
};
std::shared_ptr<esp_modem::DCE_T<NetModule>> create(std::shared_ptr<esp_modem::DTE> dte, esp_netif_t *netif)
{
const esp_modem::dce_config config = {};
return NetDCE_Factory::create<NetModule>(&config, dte, netif);
}

View File

@ -0,0 +1,307 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <cstring>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_netif.h"
#include "esp_netif_ppp.h"
#include "esp_log.h"
#include "esp_event.h"
#include "cxx_include/esp_modem_dte.hpp"
#include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp"
#include "esp_vfs_dev.h" // For optional VFS support
#include "vfs_resource/vfs_create.hpp"
#include "network_dce.hpp"
#include "manual_ota.hpp"
#include "mqtt_client.h"
using namespace esp_modem;
static const char *TAG = "ota_test";
// Wrap event handlers to destruct correctly on returning from main
class StatusHandler {
public:
static constexpr auto IP_Event = SignalGroup::bit0;
static constexpr auto MQTT_Connect = SignalGroup::bit1;
static constexpr auto MQTT_Data = SignalGroup::bit2;
StatusHandler()
{
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, this));
}
~StatusHandler()
{
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, on_event);
}
void handle_mqtt(esp_mqtt_client_handle_t client)
{
mqtt = client;
esp_mqtt_client_register_event(client, MQTT_EVENT_ANY, on_event, this);
}
void remove_mqtt()
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
esp_mqtt_client_unregister_event(mqtt, MQTT_EVENT_ANY, on_event);
#endif
mqtt = nullptr;
}
esp_err_t wait_for(decltype(IP_Event) event, int milliseconds)
{
return signal.wait_any(event, milliseconds);
}
ip_event_t get_ip_event_type()
{
return ip_event_type;
}
private:
static void on_event(void *arg, esp_event_base_t base, int32_t event, void *data)
{
auto *handler = static_cast<StatusHandler *>(arg);
if (base == IP_EVENT) {
handler->ip_event(event, data);
} else {
handler->mqtt_event(event, data);
}
}
void ip_event(int32_t id, void *data)
{
if (id == IP_EVENT_PPP_GOT_IP) {
auto *event = (ip_event_got_ip_t *)data;
ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask));
ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw));
signal.set(IP_Event);
} else if (id == IP_EVENT_PPP_LOST_IP) {
signal.set(IP_Event);
}
ip_event_type = static_cast<ip_event_t>(id);
}
void mqtt_event(int32_t event, void *data)
{
if (mqtt && event == MQTT_EVENT_CONNECTED) {
signal.set(MQTT_Connect);
} else if (mqtt && event == MQTT_EVENT_DATA) {
auto event_data = static_cast<esp_mqtt_event_handle_t>(data);
ESP_LOGI(TAG, " TOPIC: %.*s", event_data->topic_len, event_data->topic);
ESP_LOGI(TAG, " DATA: %.*s", event_data->data_len, event_data->data);
signal.set(MQTT_Data);
}
}
esp_modem::SignalGroup signal{};
esp_mqtt_client_handle_t mqtt{};
ip_event_t ip_event_type{};
};
// Wrap MQTT operations to destroy everything on returning from main
struct PublishOnce {
esp_mqtt_client_handle_t mqtt_;
StatusHandler *events_;
PublishOnce(StatusHandler *events)
{
esp_mqtt_client_config_t config = { };
config.broker.address.uri = CONFIG_BROKER_URI;
mqtt_ = esp_mqtt_client_init(&config);
events_ = events;
events->handle_mqtt(mqtt_);
}
bool Connect()
{
return esp_mqtt_client_start(mqtt_) == ESP_OK;
}
bool SubscribePublish()
{
return esp_mqtt_client_subscribe(mqtt_, "/topic/esp-modem", 0) >= 0 &&
esp_mqtt_client_publish(mqtt_, "/topic/esp-modem", "Hello modem", 0, 0, 0) >= 0;
}
~PublishOnce()
{
events_->remove_mqtt();
esp_mqtt_client_destroy(mqtt_);
}
};
// OTA related
static constexpr auto OTA_OK = SignalGroup::bit0;
static constexpr auto OTA_FAILED = SignalGroup::bit1;
void ota_task(void *ctx)
{
static const char *ca_cert_pem = "-----BEGIN CERTIFICATE-----\n" CONFIG_TEST_OTA_CA_CERT "\n-----END CERTIFICATE-----";
auto ota_done = static_cast<esp_modem::SignalGroup *>(ctx);
manual_ota ota;
ota.http_.config_.url = CONFIG_TEST_OTA_URI;
ota.http_.config_.cert_pem = ca_cert_pem;
ota.size_ = 32;
ota.common_name_ = CONFIG_TEST_OTA_CN;
#ifndef CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT
// will have to use NORMAL mode, before custom transport is supported in IDF
ota.mode_ = manual_ota::mode::NORMAL;
#endif
ota.begin();
while (true) {
if (!ota.perform()) {
break;
}
}
auto ret = ota.end();
ota_done->set(ret ? OTA_OK : OTA_FAILED);
vTaskDelete(nullptr);
}
// App related
extern "C" void app_main(void)
{
esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set("ota_test", ESP_LOG_DEBUG);
// Initialize system functions
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
// Initialize DTE
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
#ifdef CONFIG_TEST_USE_VFS_TERM
// To code-cover the vfs layers
struct esp_modem_vfs_uart_creator uart_config = ESP_MODEM_VFS_DEFAULT_UART_CONFIG("/dev/uart/1");
assert(vfs_create_uart(&uart_config, &dte_config.vfs_config) == true);
auto dte = create_vfs_dte(&dte_config);
esp_vfs_dev_uart_use_driver(uart_config.uart.port_num);
#else
auto dte = create_uart_dte(&dte_config);
#endif // CONFIG_TEST_USE_VFS_TERM
assert(dte);
dte->set_error_cb([](terminal_error err) {
ESP_LOGE(TAG, "DTE reported terminal error: %d", static_cast<int>(err));
});
// Initialize PPP netif
esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config);
assert(esp_netif);
// Initialize DCE
#ifdef CONFIG_TEST_DEVICE_PPPD_SERVER
auto dce = create(dte, esp_netif);
#else
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_TEST_MODEM_APN);
auto dce = create_generic_dce(&dce_config, dte, esp_netif);
assert(dce);
#endif
StatusHandler handler;
#ifndef CONFIG_TEST_DEVICE_PPPD_SERVER
if (dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE) &&
dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP) &&
dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA)) {
#else
if (dce->set_mode(esp_modem::modem_mode::DATA_MODE)) {
#endif
ESP_LOGI(TAG, "Modem has correctly entered the desired mode (CMUX/DATA/Manual CMUX)");
} else {
ESP_LOGE(TAG, "Failed to configure multiplexed command mode... exiting");
return;
}
if (!handler.wait_for(StatusHandler::IP_Event, 60000)) {
ESP_LOGE(TAG, "Cannot get IP within specified timeout... exiting");
return;
} else if (handler.get_ip_event_type() == IP_EVENT_PPP_GOT_IP) {
ESP_LOGI(TAG, "Got IP address");
/* When connected to network, subscribe and publish some MQTT data */
PublishOnce publish(&handler);
if (!publish.Connect()) {
ESP_LOGE(TAG, "Failed to connect to mqtt server");
return;
}
if (!handler.wait_for(StatusHandler::MQTT_Connect, 60000)) {
ESP_LOGE(TAG, "Cannot connect to %s within specified timeout... exiting", CONFIG_BROKER_URI);
return;
}
ESP_LOGI(TAG, "Connected");
if (!publish.SubscribePublish()) {
ESP_LOGE(TAG, "Failed to subscribe and publish to mqtt server");
return;
}
if (!handler.wait_for(StatusHandler::MQTT_Data, 60000)) {
ESP_LOGE(TAG, "Didn't receive published data within specified timeout... exiting");
return;
}
ESP_LOGI(TAG, "Received MQTT data");
} else if (handler.get_ip_event_type() == IP_EVENT_PPP_LOST_IP) {
ESP_LOGE(TAG, "PPP client has lost connection... exiting");
return;
}
esp_modem::SignalGroup ota_done{};
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
// now stop the LCP keepalive before performing OTA
esp_netif_ppp_config_t cfg;
ESP_ERROR_CHECK(esp_netif_ppp_get_params(esp_netif, &cfg));
cfg.ppp_lcp_echo_disabled = true;
ESP_ERROR_CHECK(esp_netif_ppp_set_params(esp_netif, &cfg));
#endif
// Run the OTA in a separate task to keep sending commands to the modem at the same time
xTaskCreate(ota_task, "ota_task", 8192, &ota_done, 5, nullptr);
#ifndef CONFIG_TEST_DEVICE_PPPD_SERVER
while (true) {
std::string str;
if (dce->get_imsi(str) == esp_modem::command_result::OK) {
ESP_LOGI(TAG, "Modem IMSI number: %s", str.c_str());
}
if (ota_done.wait_any(OTA_OK | OTA_FAILED, 100)) {
break;
}
}
#else
ota_done.wait_any(OTA_OK | OTA_FAILED, portMAX_DELAY);
#endif // CONFIG_TEST_DEVICE_PPPD_SERVER
#ifndef CONFIG_TEST_DEVICE_PPPD_SERVER
if (dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT)) {
#else
if (dce->set_mode(esp_modem::modem_mode::COMMAND_MODE)) {
#endif
ESP_LOGI(TAG, "Modem CMUX/DATA mode exit");
} else {
ESP_LOGE(TAG, "Failed to configure desired mode... exiting");
return;
}
if (ota_done.is_any(OTA_OK)) {
ESP_LOGI(TAG, "Prepare to restart system!");
esp_restart();
}
}

View File

@ -0,0 +1,2 @@
CONFIG_TEST_DEVICE_PPPD_SERVER=y
CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD=n

View File

@ -0,0 +1,4 @@
CONFIG_TEST_DEVICE_MODEM_GENERIC=y
CONFIG_TEST_OTA_URI="https://raw.githubusercontent.com/espressif/esp-protocols/master/components/esp_modem/test/target_ota/bin/blink.bin"
CONFIG_TEST_OTA_CA_CERT="MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBSU0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6aqXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddng9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuWraKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGBAfr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21reacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNVHR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEBMAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IBAQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3zax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7hqG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbCEXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697EA7sKPPcw7+uvTPyLNhBzPvOk"
CONFIG_TEST_OTA_CN="github.com"

View File

@ -0,0 +1,4 @@
CONFIG_TEST_DEVICE_PPPD_SERVER=y
CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD=y
CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED=y
CONFIG_TEST_USE_VFS_TERM=y

View File

@ -0,0 +1,12 @@
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_TWO_OTA=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y
# This is not supported in IDF yet
# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT=y
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_ENABLE_IPV6=n
CONFIG_LWIP_ENABLE_LCP_ECHO=y
CONFIG_LWIP_LCP_ECHOINTERVAL=1
CONFIG_LWIP_LCP_MAXECHOFAILS=2

View File

@ -551,9 +551,14 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
int need_write = len;
int wlen = 0, widx = 0;
bool contained_fin = opcode & WS_TRANSPORT_OPCODES_FIN;
while (widx < len || opcode) { // allow for sending "current_opcode" only message with len==0
if (need_write > client->buffer_size) {
need_write = client->buffer_size;
opcode = opcode & ~WS_TRANSPORT_OPCODES_FIN;
} else if (contained_fin) {
opcode = opcode | WS_TRANSPORT_OPCODES_FIN;
}
memcpy(client->tx_buffer, data + widx, need_write);
// send with ws specific way and specific opcode

View File

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

View File

@ -1,5 +1,16 @@
# Changelog
## [1.2.3](https://github.com/espressif/esp-protocols/commits/mdns-v1.2.3)
### Bug Fixes
- fixed CI issues for host and afl tests ([4be5efc84e](https://github.com/espressif/esp-protocols/commit/4be5efc84e))
- fix copy delegated host addr ([19fb36000c](https://github.com/espressif/esp-protocols/commit/19fb36000c))
- enable CONFIG_ESP_WIFI_ENABLED if CONFIG_SOC_WIFI_SUPPORTED is also enabled (for ESP-IDF <= 5.1) ([d20a718320](https://github.com/espressif/esp-protocols/commit/d20a718320))
- remove protocol_examples_common ([1ee9dae6bf](https://github.com/espressif/esp-protocols/commit/1ee9dae6bf))
- move the example into a subdirectory ([d28232b9f8](https://github.com/espressif/esp-protocols/commit/d28232b9f8))
- reference protocol_examples_common from IDF ([c83b76ea8f](https://github.com/espressif/esp-protocols/commit/c83b76ea8f))
## [1.2.2](https://github.com/espressif/esp-protocols/commits/mdns-v1.2.2)
### Bug Fixes

View File

@ -1,4 +1,4 @@
version: "1.2.2"
version: "1.2.3"
description: mDNS
url: https://github.com/espressif/esp-protocols/tree/master/components/mdns
dependencies:

View File

@ -5882,6 +5882,7 @@ static mdns_ip_addr_t *_copy_delegated_host_address_list(char *hostname)
if (strcasecmp(host->hostname, hostname) == 0) {
return copy_address_list(host->address_list);
}
host = host->next;
}
return NULL;
}

View File

@ -26,10 +26,9 @@ CFLAGS=-g -Wno-unused-value -Wno-missing-declarations -Wno-pointer-bool-conversi
-I$(COMPONENTS_DIR)/heap/include \
-I$(COMPONENTS_DIR)/log/include \
-I$(COMPONENTS_DIR)/lwip/lwip/src/include \
-I$(COMPONENTS_DIR)/linux/include \
-I$(COMPONENTS_DIR)/lwip/port/esp32/include \
-I$(COMPONENTS_DIR)/lwip/lwip/src/include/lwip/apps \
-I$(COMPONENTS_DIR)/newlib/platform_include \
-I$(COMPONENTS_DIR)/soc/include \
-I$(COMPONENTS_DIR)/soc/include \
-I$(COMPONENTS_DIR)/soc/esp32/include \
-I$(COMPONENTS_DIR)/soc/src/esp32/include \

View File

@ -24,5 +24,17 @@ menu "Example Configuration"
help
If enabled, a portion of MAC address is added to the hostname, this is used
for evaluation of tests in CI
config MDNS_ADD_MAC_TO_HOSTNAME
bool "Add mac suffix to hostname"
default n
help
If enabled, a portion of MAC address is added to the hostname, this is used
for evaluation of tests in CI
config MDNS_PUBLISH_DELEGATE_HOST
bool "Publish a delegated host"
help
Enable publishing a delegated host other than ESP32.
The example will also add a mock service for this host.
endmenu

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