mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-12-23 07:12:32 +01:00
Compare commits
117 Commits
websocket-
...
mdns-v1.9.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bfa00389d | ||
|
|
ace7fca8c6 | ||
|
|
2b2f009a65 | ||
|
|
1444d575f0 | ||
|
|
081eef88cf | ||
|
|
8b0704eaf4 | ||
|
|
4889dd6fcb | ||
|
|
134247d88f | ||
|
|
e772ce673d | ||
|
|
3d4712b905 | ||
|
|
67188fd7b4 | ||
|
|
4d7c6848b2 | ||
|
|
1e83bee4fe | ||
|
|
318bca1657 | ||
|
|
9e1b9cdd20 | ||
|
|
7f424325d8 | ||
|
|
9ef228f247 | ||
|
|
5068f2217e | ||
|
|
1ceb42c5a2 | ||
|
|
e52a5757f1 | ||
|
|
732cd29ec0 | ||
|
|
7a203cf085 | ||
|
|
d665e6f18e | ||
|
|
2e269640c6 | ||
|
|
c078c36361 | ||
|
|
90ddb04e53 | ||
|
|
cee3bdea9d | ||
|
|
fa96de3bd7 | ||
|
|
18f0d02806 | ||
|
|
bfa604b5f6 | ||
|
|
92a31187ff | ||
|
|
0197c994ee | ||
|
|
487a746d14 | ||
|
|
af6bb1b5ee | ||
|
|
f5e62e83e9 | ||
|
|
cad527d2fc | ||
|
|
16cc2dcffb | ||
|
|
d622e41a54 | ||
|
|
ca6e39aac7 | ||
|
|
e45944e143 | ||
|
|
fe657b9737 | ||
|
|
453be4cd79 | ||
|
|
018ba58ec5 | ||
|
|
67c682d911 | ||
|
|
91915ce1c7 | ||
|
|
ae052e5507 | ||
|
|
44524f5de0 | ||
|
|
1ace92c279 | ||
|
|
7a6cf0f9c0 | ||
|
|
54eb002758 | ||
|
|
318e41b3c3 | ||
|
|
6f6237a0cc | ||
|
|
18faeb3dfa | ||
|
|
296123c14e | ||
|
|
4e178f06bd | ||
|
|
5ab7e8327e | ||
|
|
91e7e9fa08 | ||
|
|
ff5d6021be | ||
|
|
2432e41dcb | ||
|
|
870ac91db7 | ||
|
|
94bd5b074a | ||
|
|
db7baaffba | ||
|
|
1ea93a866b | ||
|
|
92e1460721 | ||
|
|
858d38b55f | ||
|
|
6428e68c8e | ||
|
|
94563cdc1f | ||
|
|
e0b8de8f38 | ||
|
|
34b6681576 | ||
|
|
1f7828f629 | ||
|
|
e74db36ebb | ||
|
|
f8d2ed2eed | ||
|
|
8bba3a9734 | ||
|
|
9fbb6e6d0a | ||
|
|
e599cd826b | ||
|
|
e2d36b4fbd | ||
|
|
82a784baf4 | ||
|
|
15140e04c6 | ||
|
|
ce1560acb1 | ||
|
|
e1be830fb7 | ||
|
|
b7cfa31a0b | ||
|
|
845a1e2ef8 | ||
|
|
6ae7a4d2ba | ||
|
|
4d52982a69 | ||
|
|
6318022cda | ||
|
|
13591ade3d | ||
|
|
d09b302b9e | ||
|
|
39e2333adb | ||
|
|
cd57f1bb13 | ||
|
|
497ee2d6d4 | ||
|
|
b2568a3d83 | ||
|
|
58a21e39d0 | ||
|
|
9c7ee07755 | ||
|
|
14d3cb6bd1 | ||
|
|
ccdb45ee94 | ||
|
|
18418c83ff | ||
|
|
ecb7dae502 | ||
|
|
958ff6a584 | ||
|
|
5ea83be7ce | ||
|
|
33a3ec54b6 | ||
|
|
c91578c827 | ||
|
|
653328ba07 | ||
|
|
858f85706d | ||
|
|
f8748e026d | ||
|
|
479122b21d | ||
|
|
e8ce8f4739 | ||
|
|
03df9ae957 | ||
|
|
35fa0b1d42 | ||
|
|
e9d9b3a8bd | ||
|
|
07e8eddcb6 | ||
|
|
78ae25598b | ||
|
|
ffeee3e87a | ||
|
|
6d5411941b | ||
|
|
e71365f835 | ||
|
|
8dbf0e4561 | ||
|
|
63bf70914b | ||
|
|
b7b8c5dbd7 |
3
.github/workflows/esp_dns__build.yml
vendored
3
.github/workflows/esp_dns__build.yml
vendored
@@ -33,6 +33,9 @@ jobs:
|
||||
shell: bash
|
||||
working-directory: ${{matrix.test.path}}
|
||||
run: |
|
||||
if [[ "${{ matrix.idf_ver }}" == "release-v5.3" || "${{ matrix.idf_ver }}" == "release-v5.4" ]]; then
|
||||
export EXPECTED_WARNING="unknown kconfig symbol 'LWIP_USE_ESP_GETADDRINFO'"
|
||||
fi
|
||||
. ${IDF_PATH}/export.sh
|
||||
pip install idf-component-manager idf-build-apps --upgrade
|
||||
python ../../../ci/build_apps.py ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app
|
||||
|
||||
@@ -59,10 +59,11 @@ jobs:
|
||||
- name: Build with IDF-${{ matrix.idf_ver }}
|
||||
shell: bash
|
||||
run: |
|
||||
. ${GITHUB_WORKSPACE}/ci/config_env.sh
|
||||
. ${IDF_PATH}/export.sh
|
||||
python -m pip install idf-build-apps
|
||||
python ./ci/build_apps.py examples/mqtt -l -t linux
|
||||
timeout 5 ./examples/mqtt/build_linux_default/esp_mqtt_demo.elf | tee test.log || true
|
||||
python ./ci/build_apps.py examples/mqtt -l -t linux -r 'sdkconfig.ci'
|
||||
timeout 5 ./examples/mqtt/build_linux/esp_mqtt_demo.elf | tee test.log || true
|
||||
grep 'MQTT_EVENT_DATA' test.log
|
||||
|
||||
run_on_target:
|
||||
|
||||
21
.github/workflows/mdns__build-target-test.yml
vendored
21
.github/workflows/mdns__build-target-test.yml
vendored
@@ -24,6 +24,11 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
if [[ "${{ matrix.idf_ver }}" == "latest" ]]; then
|
||||
export EXPECTED_WARNING="warning: unknown kconfig symbol 'EXAMPLE_ETH_PHY_IP101'"
|
||||
else
|
||||
export EXPECTED_WARNING="warning: unknown kconfig symbol 'EXAMPLE_ETH_PHY_GENERIC'"
|
||||
fi
|
||||
python -m pip install idf-build-apps
|
||||
# Build default configs for all targets
|
||||
python ./ci/build_apps.py components/mdns/${{ matrix.test.path }} -r default -d
|
||||
@@ -71,6 +76,22 @@ jobs:
|
||||
- name: Run ${{ matrix.test.app }} application on ${{ matrix.idf_target }}
|
||||
working-directory: components/mdns/${{ matrix.test.path }}
|
||||
run: |
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
export PATH="$PYENV_ROOT/bin:$PATH"
|
||||
eval "$(pyenv init --path)"
|
||||
eval "$(pyenv init -)"
|
||||
if ! pyenv versions --bare | grep -q '^3\.12\.6$'; then
|
||||
echo "Installing Python 3.12.6..."
|
||||
pyenv install -s 3.12.6
|
||||
fi
|
||||
if ! pyenv virtualenvs --bare | grep -q '^myenv$'; then
|
||||
echo "Creating pyenv virtualenv 'myenv'..."
|
||||
pyenv virtualenv 3.12.6 myenv
|
||||
fi
|
||||
pyenv activate myenv
|
||||
python --version
|
||||
pip install --prefer-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf pytest-custom_exit_code esptool
|
||||
pip install --extra-index-url https://dl.espressif.com/pypi/ -r $GITHUB_WORKSPACE/ci/requirements.txt
|
||||
unzip ci/artifacts.zip -d ci
|
||||
for dir in `ls -d ci/build_*`; do
|
||||
rm -rf build sdkconfig.defaults
|
||||
|
||||
40
.github/workflows/mdns__host-tests.yml
vendored
40
.github/workflows/mdns__host-tests.yml
vendored
@@ -68,3 +68,43 @@ jobs:
|
||||
diff -q $file /tmp/$file || exit 1
|
||||
echo "OK"
|
||||
done
|
||||
|
||||
fuzz_test:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'mdns-fuzz') || github.event_name == 'push'
|
||||
name: Fuzzer tests for mdns lib
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
container: aflplusplus/aflplusplus:v4.34c
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout ESP-IDF
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: espressif/esp-idf
|
||||
path: idf
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Necessary Libs
|
||||
run: |
|
||||
apt-get update -y
|
||||
apt-get install -y libbsd-dev
|
||||
|
||||
- name: Run AFL++
|
||||
shell: bash
|
||||
run: |
|
||||
export IDF_PATH=$GITHUB_WORKSPACE/idf
|
||||
cd components/mdns/tests/test_afl_fuzz_host/
|
||||
make fuzz
|
||||
|
||||
- name: Upload Crash Artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fuzz-crashes
|
||||
path: components/mdns/tests/test_afl_fuzz_host/out/default/crashes.tar.gz
|
||||
if-no-files-found: ignore
|
||||
|
||||
@@ -15,11 +15,6 @@ jobs:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
|
||||
example: ["pppos_client", "modem_console", "modem_tcp_client", "ap_to_pppos", "simple_cmux_client"]
|
||||
include:
|
||||
- idf_ver: "release-v5.0"
|
||||
example: "simple_cmux_client"
|
||||
warning: "Warning: The smallest app partition is nearly full"
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
steps:
|
||||
@@ -30,9 +25,9 @@ jobs:
|
||||
- if: ${{ matrix.skip_config }}
|
||||
run: rm -f $GITHUB_WORKSPACE/protocols/components/esp_modem/examples/${{ matrix.example }}/sdkconfig.ci.${{ matrix.skip_config }}*
|
||||
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }}
|
||||
env:
|
||||
EXPECTED_WARNING: ${{ matrix.warning }}
|
||||
shell: bash
|
||||
env:
|
||||
EXPECTED_WARNING: "Warning: The smallest app partition is nearly full\nwarning: unknown kconfig symbol 'ESP32_PANIC_PRINT_HALT'"
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
python -m pip install idf-build-apps
|
||||
|
||||
12
.github/workflows/modem__target-test.yml
vendored
12
.github/workflows/modem__target-test.yml
vendored
@@ -34,18 +34,16 @@ jobs:
|
||||
IDF_TARGET: ${{ matrix.idf_target }}
|
||||
SDKCONFIG: sdkconfig.ci.${{ matrix.test.app }}
|
||||
shell: bash
|
||||
working-directory: ${{ env.TEST_DIR }}
|
||||
run: |
|
||||
. ${GITHUB_WORKSPACE}/ci/config_env.sh
|
||||
. ${IDF_PATH}/export.sh
|
||||
rm -rf sdkconfig build
|
||||
[ -f ${SDKCONFIG} ] && cp ${SDKCONFIG} sdkconfig.defaults
|
||||
idf.py set-target ${{ matrix.idf_target }}
|
||||
idf.py build
|
||||
$GITHUB_WORKSPACE/ci/clean_build_artifacts.sh ${GITHUB_WORKSPACE}/${TEST_DIR}/build
|
||||
python -m pip install idf-build-apps
|
||||
python ./ci/build_apps.py ${{ env.TEST_DIR }} -t ${{ matrix.idf_target }} -r 'sdkconfig.ci.${{ matrix.test.app }}'
|
||||
$GITHUB_WORKSPACE/ci/clean_build_artifacts.sh ${GITHUB_WORKSPACE}/${TEST_DIR}/build_${{ matrix.idf_target }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: modem_target_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.test.app }}
|
||||
path: ${{ env.TEST_DIR }}/build
|
||||
path: ${{ env.TEST_DIR }}/build_${{ matrix.idf_target }}
|
||||
if-no-files-found: error
|
||||
|
||||
target_tests_esp_modem:
|
||||
|
||||
14
.github/workflows/modem_sim__build.yml
vendored
14
.github/workflows/modem_sim__build.yml
vendored
@@ -11,17 +11,21 @@ jobs:
|
||||
build_modem_sim:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'modem_sim') || github.event_name == 'push'
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["release-v5.4"]
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout idf
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: espressif/esp-idf
|
||||
ref: 8ad0d3d8f2faab752635bee36070313c47c07a13
|
||||
path: idf
|
||||
- name: Build ESP-AT with IDF-${{ matrix.idf_ver }}
|
||||
shell: bash
|
||||
run: |
|
||||
export IDF_PATH=$GITHUB_WORKSPACE/idf
|
||||
${IDF_PATH}/install.sh
|
||||
cd common_components/modem_sim
|
||||
./install.sh
|
||||
source export.sh
|
||||
|
||||
5
.github/workflows/mosq__build.yml
vendored
5
.github/workflows/mosq__build.yml
vendored
@@ -16,8 +16,11 @@ jobs:
|
||||
idf_ver: ["latest", "release-v5.5", "release-v5.4", "release-v5.3", "release-v5.2", "release-v5.1"]
|
||||
example: ["broker", "serverless_mqtt"]
|
||||
exclude:
|
||||
# serverless_mqtt is not supported on v5.1 and master (esp-peer dependency)
|
||||
- idf_ver: "release-v5.1"
|
||||
example: "serverless_mqtt" # serverless_mqtt is not supported due to esp-peer
|
||||
example: "serverless_mqtt"
|
||||
- idf_ver: "latest"
|
||||
example: "serverless_mqtt"
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
|
||||
2
.github/workflows/pre_commit_check.yml
vendored
2
.github/workflows/pre_commit_check.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
if ! pre-commit run --from-ref origin/HEAD --to-ref HEAD --hook-stage manual --show-diff-on-failure ; then
|
||||
echo ""
|
||||
echo "::notice::It looks like the commits in this PR have been made without having pre-commit hooks installed."
|
||||
echo "::notice::Please see https://github.com/espressif/esp-protocols/CONTRIBUTING.md for instructions."
|
||||
echo "::notice::Please see https://github.com/espressif/esp-protocols/blob/master/CONTRIBUTING.md for instructions."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -65,14 +65,27 @@ jobs:
|
||||
with:
|
||||
name: websocket_bin_esp32_${{ matrix.idf_ver }}_${{ matrix.test.app }}
|
||||
path: ${{ env.TEST_DIR }}/ci/
|
||||
- name: Install Python packages
|
||||
- name: Run Example Test on target
|
||||
working-directory: ${{ env.TEST_DIR }}
|
||||
env:
|
||||
PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple"
|
||||
run: |
|
||||
pip install --only-binary cryptography --extra-index-url https://dl.espressif.com/pypi/ -r $GITHUB_WORKSPACE/ci/requirements.txt
|
||||
- name: Run Example Test on target
|
||||
working-directory: ${{ env.TEST_DIR }}
|
||||
run: |
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
export PATH="$PYENV_ROOT/bin:$PATH"
|
||||
eval "$(pyenv init --path)"
|
||||
eval "$(pyenv init -)"
|
||||
if ! pyenv versions --bare | grep -q '^3\.12\.6$'; then
|
||||
echo "Installing Python 3.12.6..."
|
||||
pyenv install -s 3.12.6
|
||||
fi
|
||||
if ! pyenv virtualenvs --bare | grep -q '^myenv$'; then
|
||||
echo "Creating pyenv virtualenv 'myenv'..."
|
||||
pyenv virtualenv 3.12.6 myenv
|
||||
fi
|
||||
pyenv activate myenv
|
||||
python --version
|
||||
pip install --prefer-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf pytest-custom_exit_code esptool
|
||||
pip install --extra-index-url https://dl.espressif.com/pypi/ -r $GITHUB_WORKSPACE/ci/requirements.txt
|
||||
unzip ci/artifacts.zip -d ci
|
||||
for dir in `ls -d ci/build_*`; do
|
||||
rm -rf build sdkconfig.defaults
|
||||
|
||||
@@ -25,12 +25,8 @@ repos:
|
||||
(?x)^(
|
||||
.*.py
|
||||
)$
|
||||
- repo: https://github.com/myint/unify
|
||||
rev: v0.5
|
||||
hooks:
|
||||
- id: unify
|
||||
- repo: https://github.com/pre-commit/mirrors-yapf
|
||||
rev: "v0.32.0"
|
||||
- repo: https://github.com/google/yapf
|
||||
rev: "v0.43.0"
|
||||
hooks:
|
||||
- id: yapf
|
||||
args: ['style={based_on_style: google, column_limit: 160, indent_width: 4}']
|
||||
@@ -39,7 +35,7 @@ repos:
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/myint/eradicate/
|
||||
rev: v2.1.0
|
||||
rev: 3.0.0
|
||||
hooks:
|
||||
- id: eradicate
|
||||
- repo: https://github.com/espressif/check-copyright/
|
||||
|
||||
@@ -56,7 +56,7 @@ if __name__ == '__main__':
|
||||
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,
|
||||
size_json_filename=None,
|
||||
check_warnings=True,
|
||||
manifest_files=args.manifests,
|
||||
default_build_targets=SUPPORTED_TARGETS,
|
||||
@@ -65,6 +65,7 @@ if __name__ == '__main__':
|
||||
|
||||
sys.exit(
|
||||
build_apps(apps,
|
||||
verbose=2,
|
||||
dry_run=False,
|
||||
keep_going=False,
|
||||
no_preserve=args.delete,
|
||||
|
||||
7
ci/config_env.sh
Executable file
7
ci/config_env.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# This script is used to set some common variables for the CI pipeline.
|
||||
|
||||
set -e
|
||||
|
||||
# MQTT public broker URI
|
||||
export CI_MQTT_BROKER_URI="test.mosquitto.org"
|
||||
@@ -1 +1,7 @@
|
||||
DeprecationWarning: pkg_resources is deprecated as an API
|
||||
Warning: Deprecated: Option '--flash_size' is deprecated. Use '--flash-size' instead.
|
||||
Warning: Deprecated: Option '--flash_mode' is deprecated. Use '--flash-mode' instead.
|
||||
Warning: Deprecated: Option '--flash_freq' is deprecated. Use '--flash-freq' instead.
|
||||
Warning: Deprecated: Command 'sign_data' is deprecated. Use 'sign-data' instead.
|
||||
Warning: Deprecated: Command 'extract_public_key' is deprecated. Use 'extract-public-key' instead.
|
||||
warning: unknown kconfig symbol 'EXAMPLE_ETH_PHY_IP101'
|
||||
|
||||
@@ -6,3 +6,4 @@ dpkt
|
||||
pytest
|
||||
idf_build_apps
|
||||
netifaces
|
||||
esptool>=5.1.0
|
||||
|
||||
51
common_components/modem_sim/README.md
Normal file
51
common_components/modem_sim/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Modem Simulator Component
|
||||
|
||||
A Wi-Fi modem simulator that extends ESP-AT with PPP server capabilities, turning ESP32 into a fully functional Wi-Fi modem. Perfect for testing AT commands and PPP connections without real hardware dependencies.
|
||||
|
||||
## What it does
|
||||
|
||||
- Extends ESP-AT firmware with PPP server functionality
|
||||
- Provides DATA mode for raw IP communication
|
||||
- Enables existing communication stacks (MQTT, HTTP, custom protocols) to work over Wi-Fi
|
||||
- Ideal for testing ESP-Modem library and CI reliability
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cd common_components/modem_sim
|
||||
./install.sh
|
||||
source export.sh
|
||||
idf.py build
|
||||
```
|
||||
|
||||
## Custom Platform/Module
|
||||
|
||||
```bash
|
||||
./install.sh PLATFORM_ESP32S3 WROOM-32
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The `sdkconfig.defaults` includes:
|
||||
- Wi-Fi and Bluetooth enabled
|
||||
- PPP server support
|
||||
- AT commands for HTTP/MQTT
|
||||
- 4MB flash configuration
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
modem_sim/
|
||||
├── install.sh # Installation script
|
||||
├── export.sh # Environment setup
|
||||
├── sdkconfig.defaults # Default configuration
|
||||
├── pppd_cmd/ # Custom PPP commands
|
||||
└── modem_sim_esp32/ # Generated ESP-AT build
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Testing ESP-Modem library without real hardware
|
||||
- Quick Wi-Fi connectivity for existing communication stacks
|
||||
- CI/CD testing with reliable modem simulation
|
||||
- Development and debugging of AT command implementations
|
||||
@@ -75,3 +75,5 @@ CONFIG_AT_HTTP_COMMAND_SUPPORT=y
|
||||
CONFIG_AT_BLE_COMMAND_SUPPORT=n
|
||||
CONFIG_AT_BLE_HID_COMMAND_SUPPORT=n
|
||||
CONFIG_AT_BLUFI_COMMAND_SUPPORT=n
|
||||
CONFIG_LWIP_IP_FORWARD=y
|
||||
CONFIG_LWIP_IPV4_NAPT=y
|
||||
|
||||
@@ -28,7 +28,7 @@ posix_event::posix_event()
|
||||
}
|
||||
} // namespace asio::detail
|
||||
|
||||
extern "C" int pause (void)
|
||||
extern "C" int pause(void)
|
||||
{
|
||||
while (true) {
|
||||
::sleep(UINT_MAX);
|
||||
|
||||
@@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(console): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py console_cmd_ping
|
||||
tag_format: console_cmd_ping-v$version
|
||||
version: 1.1.0
|
||||
version: 1.2.0
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [1.2.0](https://github.com/espressif/esp-protocols/commits/console_cmd_ping-v1.2.0)
|
||||
|
||||
### Features
|
||||
|
||||
- Add support for interface argument ([90ddb04e](https://github.com/espressif/esp-protocols/commit/90ddb04e))
|
||||
|
||||
## [1.1.0](https://github.com/espressif/esp-protocols/commits/console_cmd_ping-v1.1.0)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -48,7 +48,7 @@ For more details refer [IDF Component Manager](https://docs.espressif.com/projec
|
||||
|
||||
### ping:
|
||||
```
|
||||
ping [-W <t>] [-i <t>] [-s <n>] [-c <n>] [-Q <n>] [-T <n>] <host>
|
||||
ping [-W <t>] [-i <t>] [-s <n>] [-c <n>] [-Q <n>] [-T <n>] [-I <n>] <host>
|
||||
send ICMP ECHO_REQUEST to network hosts
|
||||
-W, --timeout=<t> Time to wait for a response, in seconds
|
||||
-i, --interval=<t> Wait interval seconds between sending each packet
|
||||
@@ -56,6 +56,7 @@ ping [-W <t>] [-i <t>] [-s <n>] [-c <n>] [-Q <n>] [-T <n>] <host>
|
||||
-c, --count=<n> Stop after sending count packets
|
||||
-Q, --tos=<n> Set Type of Service related bits in IP datagrams
|
||||
-T, --ttl=<n> Set Time to Live related bits in IP datagrams
|
||||
-I, --interface=<n> Set Interface number 0=no-interface selected, >0 netif number + 1 (1 is usually 'lo0')
|
||||
<host> Host address
|
||||
|
||||
getaddrinfo [-f <AF>] [-F <FLAGS>]... [-p <port>] <hostname>
|
||||
@@ -121,8 +122,8 @@ getaddrinfo -f AF_INET -F AI_PASSIVE www.example.com
|
||||
ping www.example.com
|
||||
```
|
||||
|
||||
2. To specify additional options, such as timeout, interval, packet size, etc.:
|
||||
2. To specify additional options, such as timeout, interval, packet size, interface, etc.:
|
||||
|
||||
```
|
||||
ping -W 5 -i 1 -s 64 -c 4 -Q 0x10 -T 64 www.example.com
|
||||
ping -W 5 -i 1 -s 64 -c 4 -Q 0x10 -T 64 -I 0 www.example.com
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -99,6 +99,7 @@ static struct {
|
||||
struct arg_int *count;
|
||||
struct arg_int *tos;
|
||||
struct arg_int *ttl;
|
||||
struct arg_int *interface;
|
||||
struct arg_str *host;
|
||||
struct arg_end *end;
|
||||
} ping_args;
|
||||
@@ -137,6 +138,10 @@ static int do_ping_cmd(int argc, char **argv)
|
||||
config.ttl = (uint32_t)(ping_args.ttl->ival[0]);
|
||||
}
|
||||
|
||||
if (ping_args.interface->count > 0) {
|
||||
config.interface = (uint32_t)(ping_args.interface->ival[0]);
|
||||
}
|
||||
|
||||
// parse IP address
|
||||
struct sockaddr_in6 sock_addr6;
|
||||
ip_addr_t target_addr = {0};
|
||||
@@ -155,12 +160,12 @@ static int do_ping_cmd(int argc, char **argv)
|
||||
}
|
||||
if (res->ai_family == AF_INET) {
|
||||
#if CONFIG_LWIP_IPV4
|
||||
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
|
||||
struct in_addr addr4 = ((struct sockaddr_in *)(res->ai_addr))->sin_addr;
|
||||
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
|
||||
#endif
|
||||
} else {
|
||||
#if CONFIG_LWIP_IPV6
|
||||
struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr;
|
||||
struct in6_addr addr6 = ((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr;
|
||||
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
|
||||
#endif
|
||||
}
|
||||
@@ -204,6 +209,7 @@ esp_err_t console_cmd_ping_register(void)
|
||||
ping_args.count = arg_int0("c", "count", "<n>", "Stop after sending count packets");
|
||||
ping_args.tos = arg_int0("Q", "tos", "<n>", "Set Type of Service related bits in IP datagrams");
|
||||
ping_args.ttl = arg_int0("T", "ttl", "<n>", "Set Time to Live related bits in IP datagrams");
|
||||
ping_args.interface = arg_int0("I", "interface", "<n>", "Set Interface number");
|
||||
ping_args.host = arg_str1(NULL, NULL, "<host>", "Host address");
|
||||
ping_args.end = arg_end(1);
|
||||
const esp_console_cmd_t ping_cmd = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: 1.1.0
|
||||
version: 1.2.0
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/console_cmd_ping
|
||||
description: The component provides a console where the 'ping' command can be executed.
|
||||
dependencies:
|
||||
|
||||
@@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(eppp): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py eppp_link
|
||||
tag_format: eppp-v$version
|
||||
version: 1.0.0
|
||||
version: 1.1.3
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
||||
@@ -1,5 +1,46 @@
|
||||
# Changelog
|
||||
|
||||
## [1.1.3](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.3)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix test dependency issue on driver ([1ace92c2](https://github.com/espressif/esp-protocols/commit/1ace92c2))
|
||||
- Fix tun netif to (optionally) return errors ([7a6cf0f9](https://github.com/espressif/esp-protocols/commit/7a6cf0f9))
|
||||
|
||||
## [1.1.2](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.2)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Update uart driver deps per IDF > v5.3 ([92e14607](https://github.com/espressif/esp-protocols/commit/92e14607))
|
||||
|
||||
## [1.1.1](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.1)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix getting context for channel API ([94563cdc](https://github.com/espressif/esp-protocols/commit/94563cdc))
|
||||
- Cover more combinations in build tests ([e0b8de8f](https://github.com/espressif/esp-protocols/commit/e0b8de8f))
|
||||
|
||||
## [1.1.0](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.0)
|
||||
|
||||
### Features
|
||||
|
||||
- Add support for UART flow control ([cd57f1bb](https://github.com/espressif/esp-protocols/commit/cd57f1bb), [#870](https://github.com/espressif/esp-protocols/issues/870))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix SPI transport to allow already init GPIO ISR ([497ee2d6](https://github.com/espressif/esp-protocols/commit/497ee2d6), [#868](https://github.com/espressif/esp-protocols/issues/868))
|
||||
- Fix stack-overflow in ping task for TUN netif ([b2568a3d](https://github.com/espressif/esp-protocols/commit/b2568a3d), [#867](https://github.com/espressif/esp-protocols/issues/867))
|
||||
|
||||
### Updated
|
||||
|
||||
- ci(common): Update test component dir for IDFv6.0 ([18418c83](https://github.com/espressif/esp-protocols/commit/18418c83))
|
||||
|
||||
## [1.0.1](https://github.com/espressif/esp-protocols/commits/eppp-v1.0.1)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Support for IPv4-only mode ([653328ba](https://github.com/espressif/esp-protocols/commit/653328ba), [#864](https://github.com/espressif/esp-protocols/issues/864))
|
||||
|
||||
## [1.0.0](https://github.com/espressif/esp-protocols/commits/eppp-v1.0.0)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3")
|
||||
set(driver_deps esp_driver_gpio esp_driver_spi)
|
||||
set(driver_deps esp_driver_gpio esp_driver_spi esp_driver_uart esp_driver_sdio)
|
||||
else()
|
||||
set(driver_deps driver)
|
||||
endif()
|
||||
|
||||
@@ -17,9 +17,17 @@
|
||||
#include "esp_check.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
#if defined(CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS)
|
||||
typedef esp_err_t esp_netif_recv_ret_t;
|
||||
#define ESP_NETIF_OPTIONAL_RETURN_CODE(expr) expr
|
||||
#else
|
||||
typedef void esp_netif_recv_ret_t;
|
||||
#define ESP_NETIF_OPTIONAL_RETURN_CODE(expr)
|
||||
#endif // CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS
|
||||
|
||||
static const char *TAG = "eppp_tun_netif";
|
||||
|
||||
static void tun_input(void *h, void *buffer, unsigned int len, void *eb)
|
||||
static esp_netif_recv_ret_t tun_input(void *h, void *buffer, unsigned int len, void *eb)
|
||||
{
|
||||
__attribute__((unused)) esp_err_t ret = ESP_OK;
|
||||
struct netif *netif = h;
|
||||
@@ -31,11 +39,12 @@ static void tun_input(void *h, void *buffer, unsigned int len, void *eb)
|
||||
ESP_GOTO_ON_FALSE(pbuf_remove_header(p, SIZEOF_ETH_HDR) == 0, ESP_FAIL, err, TAG, "pbuf_remove_header failed");
|
||||
memcpy(p->payload, buffer, len);
|
||||
ESP_GOTO_ON_FALSE(netif->input(p, netif) == ERR_OK, ESP_FAIL, err, TAG, "failed to input packet to lwip");
|
||||
return;
|
||||
return ESP_NETIF_OPTIONAL_RETURN_CODE(ESP_OK);
|
||||
err:
|
||||
if (p) {
|
||||
pbuf_free(p);
|
||||
}
|
||||
return ESP_NETIF_OPTIONAL_RETURN_CODE(ret);
|
||||
}
|
||||
|
||||
static err_t tun_output(struct netif *netif, struct pbuf *p)
|
||||
@@ -65,12 +74,13 @@ static err_t tun_output_v4(struct netif *netif, struct pbuf *p, const ip4_addr_t
|
||||
LWIP_UNUSED_ARG(ipaddr);
|
||||
return tun_output(netif, p);
|
||||
}
|
||||
#if LWIP_IPV6
|
||||
static err_t tun_output_v6(struct netif *netif, struct pbuf *p, const ip6_addr_t *ipaddr)
|
||||
{
|
||||
LWIP_UNUSED_ARG(ipaddr);
|
||||
return tun_output(netif, p);
|
||||
}
|
||||
|
||||
#endif
|
||||
static err_t tun_init(struct netif *netif)
|
||||
{
|
||||
if (netif == NULL) {
|
||||
@@ -151,9 +161,12 @@ static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
|
||||
}
|
||||
if (IP_IS_V4(&target_addr)) {
|
||||
ESP_LOGD(TAG, "\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr)));
|
||||
} else {
|
||||
}
|
||||
#if LWIP_IPV6
|
||||
else {
|
||||
ESP_LOGD(TAG, "\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
|
||||
}
|
||||
#endif
|
||||
ESP_LOGI(TAG, "%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n",
|
||||
transmitted, received, loss, total_time_ms);
|
||||
esp_ping_delete_session(hdl);
|
||||
@@ -163,12 +176,19 @@ esp_err_t eppp_check_connection(esp_netif_t *netif)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
|
||||
#if CONFIG_LOG_MAXIMUM_LEVEL > 3
|
||||
config.task_stack_size += 1024; // Some additional stack needed for verbose logs
|
||||
#endif
|
||||
config.count = 100;
|
||||
ESP_LOGI(TAG, "Checking connection on EPPP interface #%" PRIu32, config.interface);
|
||||
ip_addr_t target_addr = {0};
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_netif_get_ip_info(netif, &ip);
|
||||
#if LWIP_IPV6
|
||||
target_addr.u_addr.ip4.addr = ip.gw.addr;
|
||||
#else
|
||||
target_addr.addr = ip.gw.addr;
|
||||
#endif
|
||||
config.target_addr = target_addr;
|
||||
esp_ping_callbacks_t cbs = {
|
||||
.cb_args = netif,
|
||||
|
||||
@@ -208,7 +208,9 @@ static esp_err_t init_master(struct eppp_config_spi_s *config, struct eppp_spi *
|
||||
};
|
||||
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err_dev, TAG, "Failed to config interrupt GPIO");
|
||||
ESP_GOTO_ON_ERROR(gpio_install_isr_service(0), err_dev, TAG, "Failed to install GPIO ISR");
|
||||
ret = gpio_install_isr_service(0);
|
||||
ESP_GOTO_ON_FALSE(ret == ESP_OK || ret == ESP_ERR_INVALID_STATE /* In case the GPIO ISR already installed */,
|
||||
ret, err_dev, TAG, "Failed to install GPIO ISR");
|
||||
ESP_GOTO_ON_ERROR(gpio_set_intr_type(config->intr, GPIO_INTR_ANYEDGE), err_dev, TAG, "Failed to set ISR type");
|
||||
ESP_GOTO_ON_ERROR(gpio_isr_handler_add(config->intr, gpio_isr_handler, h), err_dev, TAG, "Failed to add ISR handler");
|
||||
return ESP_OK;
|
||||
@@ -468,9 +470,6 @@ err:
|
||||
if (h->ready_semaphore) {
|
||||
vSemaphoreDelete(h->ready_semaphore);
|
||||
}
|
||||
if (h->out_queue) {
|
||||
vQueueDelete(h->out_queue);
|
||||
}
|
||||
free(h);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -80,12 +80,12 @@ static esp_err_t init_uart(struct eppp_uart *h, struct eppp_config_uart_s *confi
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
uart_config.flow_ctrl = config->flow_control;
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
ESP_RETURN_ON_ERROR(uart_driver_install(h->uart_port, config->rx_buffer_size, 0, config->queue_size, &h->uart_event_queue, 0), TAG, "Failed to install UART");
|
||||
ESP_RETURN_ON_ERROR(uart_param_config(h->uart_port, &uart_config), TAG, "Failed to set params");
|
||||
ESP_RETURN_ON_ERROR(uart_set_pin(h->uart_port, config->tx_io, config->rx_io, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), TAG, "Failed to set UART pins");
|
||||
ESP_RETURN_ON_ERROR(uart_set_pin(h->uart_port, config->tx_io, config->rx_io, config->rts_io, config->cts_io), TAG, "Failed to set UART pins");
|
||||
ESP_RETURN_ON_ERROR(uart_set_rx_timeout(h->uart_port, 1), TAG, "Failed to set UART Rx timeout");
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -157,6 +157,8 @@ static void process_packet(esp_netif_t *netif, uart_port_t uart_port, size_t ava
|
||||
esp_netif_receive(netif, in_buf + buf_start + sizeof(struct header), payload_size, NULL);
|
||||
} else {
|
||||
#ifdef CONFIG_EPPP_LINK_CHANNELS_SUPPORT
|
||||
struct eppp_handle *handle = esp_netif_get_io_driver(netif);
|
||||
struct eppp_uart *h = __containerof(handle, struct eppp_uart, parent);
|
||||
if (h->parent.channel_rx) {
|
||||
h->parent.channel_rx(netif, channel, in_buf + buf_start + sizeof(struct header), payload_size);
|
||||
}
|
||||
@@ -269,6 +271,7 @@ eppp_transport_handle_t eppp_uart_init(struct eppp_config_uart_s *config)
|
||||
ESP_GOTO_ON_ERROR(init_uart(h, config), err, TAG, "Failed to init UART");
|
||||
return &h->parent;
|
||||
err:
|
||||
free(h);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
4
components/eppp_link/examples/host/sdkconfig.ci.1
Normal file
4
components/eppp_link/examples/host/sdkconfig.ci.1
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
CONFIG_EPPP_LINK_DEVICE_SPI=y
|
||||
CONFIG_EPPP_LINK_CHANNELS_SUPPORT=y
|
||||
CONFIG_EPPP_LINK_USES_PPP=y
|
||||
3
components/eppp_link/examples/host/sdkconfig.ci.2
Normal file
3
components/eppp_link/examples/host/sdkconfig.ci.2
Normal file
@@ -0,0 +1,3 @@
|
||||
CONFIG_IDF_TARGET="esp32c3"
|
||||
CONFIG_EPPP_LINK_DEVICE_UART=y
|
||||
CONFIG_EPPP_LINK_CHANNELS_SUPPORT=y
|
||||
@@ -1,4 +1 @@
|
||||
CONFIG_LWIP_PPP_SUPPORT=y
|
||||
CONFIG_LWIP_PPP_SERVER_SUPPORT=y
|
||||
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n
|
||||
CONFIG_LWIP_PPP_DEBUG_ON=y
|
||||
|
||||
4
components/eppp_link/examples/slave/sdkconfig.ci.1
Normal file
4
components/eppp_link/examples/slave/sdkconfig.ci.1
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_IDF_TARGET="esp32s2"
|
||||
CONFIG_EPPP_LINK_DEVICE_SPI=y
|
||||
CONFIG_EPPP_LINK_CHANNELS_SUPPORT=y
|
||||
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n
|
||||
4
components/eppp_link/examples/slave/sdkconfig.ci.2
Normal file
4
components/eppp_link/examples/slave/sdkconfig.ci.2
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_IDF_TARGET="esp32c2"
|
||||
CONFIG_EPPP_LINK_DEVICE_UART=y
|
||||
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n
|
||||
CONFIG_EPPP_LINK_USES_PPP=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_LWIP_IPV6=n
|
||||
@@ -1,6 +1,3 @@
|
||||
CONFIG_LWIP_IP_FORWARD=y
|
||||
CONFIG_LWIP_IPV4_NAPT=y
|
||||
CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096
|
||||
CONFIG_LWIP_PPP_SUPPORT=y
|
||||
CONFIG_LWIP_PPP_SERVER_SUPPORT=y
|
||||
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: 1.0.0
|
||||
version: 1.1.3
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/eppp_link
|
||||
description: The component provides a general purpose PPP connectivity, typically used as WiFi-PPP router
|
||||
dependencies:
|
||||
|
||||
@@ -34,6 +34,9 @@ extern "C" {
|
||||
.rx_io = 26, \
|
||||
.queue_size = 16, \
|
||||
.rx_buffer_size = 1024, \
|
||||
.rts_io = -1, \
|
||||
.cts_io = -1, \
|
||||
.flow_control = 0, \
|
||||
}, \
|
||||
|
||||
#define EPPP_DEFAULT_SDIO_CONFIG() \
|
||||
@@ -135,6 +138,9 @@ typedef struct eppp_config_t {
|
||||
int rx_io;
|
||||
int queue_size;
|
||||
int rx_buffer_size;
|
||||
int rts_io;
|
||||
int cts_io;
|
||||
int flow_control;
|
||||
} uart;
|
||||
|
||||
struct eppp_config_sdio_s {
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
# The following four lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/tools/unit-test-app/components)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0")
|
||||
set(test_component_dir $ENV{IDF_PATH}/tools/test_apps/components)
|
||||
else()
|
||||
set(test_component_dir $ENV{IDF_PATH}/tools/unit-test-app/components)
|
||||
endif()
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS ${test_component_dir})
|
||||
|
||||
project(test_app)
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3")
|
||||
set(driver_deps esp_driver_gpio esp_driver_spi esp_driver_uart esp_driver_sdio)
|
||||
else()
|
||||
set(driver_deps driver)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS app_main.c
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES test_utils
|
||||
PRIV_REQUIRES unity nvs_flash esp_netif driver esp_event)
|
||||
PRIV_REQUIRES unity nvs_flash esp_netif esp_event ${driver_deps})
|
||||
|
||||
@@ -11,7 +11,12 @@ else()
|
||||
src/esp_modem_uart.cpp
|
||||
src/esp_modem_term_uart.cpp
|
||||
src/esp_modem_netif.cpp)
|
||||
set(dependencies driver esp_event esp_netif)
|
||||
set(dependencies esp_event esp_netif)
|
||||
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3")
|
||||
list(APPEND dependencies esp_driver_uart)
|
||||
else()
|
||||
list(APPEND dependencies driver)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
@@ -103,4 +103,14 @@ menu "esp-modem"
|
||||
Set this to 'y' if you're making changes to the actual sources of
|
||||
the AT command definitions (typically in esp_modem_command_declare.inc)
|
||||
|
||||
config ESP_MODEM_USE_PPP_MODE
|
||||
bool "Use PPP mode"
|
||||
default y
|
||||
select LWIP_PPP_SUPPORT
|
||||
help
|
||||
If enabled, the library can use PPP netif from lwip.
|
||||
This is the default and most common setting.
|
||||
But it's possible to disable it and use only AT commands,
|
||||
in this case it's not required to enable LWIP_PPP_SUPPORT.
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@@ -100,10 +100,10 @@ void wifi_init_softap(void)
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
|
||||
ESP_EVENT_ANY_ID,
|
||||
&wifi_event_handler,
|
||||
NULL,
|
||||
NULL));
|
||||
ESP_EVENT_ANY_ID,
|
||||
&wifi_event_handler,
|
||||
NULL,
|
||||
NULL));
|
||||
|
||||
wifi_config_t wifi_config = {
|
||||
.ap = {
|
||||
@@ -120,7 +120,7 @@ void wifi_init_softap(void)
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
|
||||
|
||||
@@ -9,5 +9,4 @@ idf_component_register(SRCS "modem_console_main.cpp"
|
||||
"${command_dir}/my_module_dce.cpp"
|
||||
"httpget_handle.c"
|
||||
"ping_handle.c"
|
||||
REQUIRES console esp_http_client nvs_flash
|
||||
INCLUDE_DIRS "." "${command_dir}")
|
||||
|
||||
@@ -63,13 +63,12 @@ void ConsoleCommand::RegisterCommand(const char *command, const char *help, cons
|
||||
|
||||
arg_type end = { .end = arg_end(1) };
|
||||
arg_table.emplace_back(end);
|
||||
const esp_console_cmd_t command_def = {
|
||||
.command = command,
|
||||
.help = help,
|
||||
.hint = nullptr,
|
||||
.func = command_func_pts[last_command],
|
||||
.argtable = &arg_table[0]
|
||||
};
|
||||
esp_console_cmd_t command_def = { };
|
||||
command_def.command = command;
|
||||
command_def.help = help;
|
||||
command_def.hint = nullptr;
|
||||
command_def.func = command_func_pts[last_command];
|
||||
command_def.argtable = &arg_table[0];
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&command_def));
|
||||
last_command++;
|
||||
console_commands.emplace_back(this);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "esp_modem_config.h"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include "esp_idf_version.h"
|
||||
#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
|
||||
#include "esp_modem_usb_config.h"
|
||||
#include "cxx_include/esp_modem_usb_api.hpp"
|
||||
@@ -29,6 +30,12 @@
|
||||
#include "console_helper.hpp"
|
||||
#include "my_module_dce.hpp"
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
#define GET_WAKEUP_CAUSE() esp_sleep_get_wakeup_causes()
|
||||
#else
|
||||
#define GET_WAKEUP_CAUSE() esp_sleep_get_wakeup_cause()
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_EXAMPLE_FLOW_CONTROL_NONE)
|
||||
#define EXAMPLE_FLOW_CONTROL ESP_MODEM_FLOW_CONTROL_NONE
|
||||
#elif defined(CONFIG_EXAMPLE_FLOW_CONTROL_SW)
|
||||
@@ -90,10 +97,21 @@ void wakeup_modem(void)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
command_result handle_urc(uint8_t *data, size_t len)
|
||||
esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo &info)
|
||||
{
|
||||
ESP_LOG_BUFFER_HEXDUMP("on_read", data, len, ESP_LOG_INFO);
|
||||
return command_result::TIMEOUT;
|
||||
// Log buffer information for debugging
|
||||
ESP_LOGI(TAG, "URC Buffer Info: total_size=%zu, processed_offset=%zu, new_data_size=%zu, command_active=%s",
|
||||
info.buffer_total_size, info.processed_offset, info.new_data_size,
|
||||
info.is_command_active ? "true" : "false");
|
||||
|
||||
// Log the new data content
|
||||
if (info.new_data_size > 0) {
|
||||
ESP_LOG_BUFFER_HEXDUMP("on_read", info.new_data_start, info.new_data_size, ESP_LOG_INFO);
|
||||
}
|
||||
|
||||
// For console example, we just log and don't consume anything
|
||||
// This allows the data to be processed by command handlers
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -208,7 +226,7 @@ extern "C" void app_main(void)
|
||||
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &s_repl));
|
||||
|
||||
switch (esp_sleep_get_wakeup_cause()) {
|
||||
switch (GET_WAKEUP_CAUSE()) {
|
||||
case ESP_SLEEP_WAKEUP_TIMER:
|
||||
if (esp_modem::modem_mode::CMUX_MODE == mode_rtc) {
|
||||
ESP_LOGI(TAG, "Deep sleep reset\n");
|
||||
@@ -374,14 +392,14 @@ extern "C" void app_main(void)
|
||||
CHECK_ERR(dce->reset(), ESP_LOGI(TAG, "OK"));
|
||||
});
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
const ConsoleCommand HandleURC("urc", "toggle urc handling", no_args, [&](ConsoleCommand * c) {
|
||||
const ConsoleCommand HandleURC("urc", "toggle enhanced urc handling", no_args, [&](ConsoleCommand * c) {
|
||||
static int cnt = 0;
|
||||
if (++cnt % 2) {
|
||||
ESP_LOGI(TAG, "Adding URC handler");
|
||||
dce->set_urc(handle_urc);
|
||||
ESP_LOGI(TAG, "Adding enhanced URC handler");
|
||||
dce->set_enhanced_urc(handle_enhanced_urc);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "URC removed");
|
||||
dce->set_urc(nullptr);
|
||||
ESP_LOGI(TAG, "Enhanced URC removed");
|
||||
dce->set_enhanced_urc(nullptr);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
@@ -428,7 +446,7 @@ extern "C" void app_main(void)
|
||||
const ConsoleCommand SetDeepSleep("set_deep_sleep", "Put esp32 to deep sleep", &deep_sleep_args, sizeof(deep_sleep_args), [&](ConsoleCommand * c) {
|
||||
int tout = c->get_int_of(&DeepSleepArgs::timeout);
|
||||
ESP_LOGI(TAG, "Entering deep sleep for %d sec", tout);
|
||||
ESP_LOGI(TAG, "Wakeup Cause: %d ", esp_sleep_get_wakeup_cause());
|
||||
ESP_LOGI(TAG, "Wakeup Cause: %d ", GET_WAKEUP_CAUSE());
|
||||
esp_deep_sleep(tout * 1000000);
|
||||
return 0;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
set(module_dir "generic_module")
|
||||
if (CONFIG_EXAMPLE_MODEM_DEVICE_BG96)
|
||||
set(device_srcs sock_commands_bg96.cpp)
|
||||
elseif(CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600)
|
||||
set(device_srcs sock_commands_sim7600.cpp)
|
||||
elseif(CONFIG_EXAMPLE_MODEM_DEVICE_ESPAT)
|
||||
set(device_srcs sock_commands_espat.cpp)
|
||||
set(module_dir "espat_module")
|
||||
endif()
|
||||
|
||||
if(CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE)
|
||||
@@ -14,4 +18,4 @@ idf_component_register(SRCS "modem_client.cpp"
|
||||
"${command_dir}/sock_dce.cpp"
|
||||
"tcp_transport_at.cpp"
|
||||
"${device_srcs}"
|
||||
INCLUDE_DIRS "." "${command_dir}")
|
||||
INCLUDE_DIRS "." "${command_dir}" "${module_dir}")
|
||||
|
||||
@@ -18,8 +18,26 @@ menu "Example Configuration"
|
||||
bool "SIM7600"
|
||||
help
|
||||
SIM7600 is Multi-Band LTE-TDD/LTE-FDD/HSPA+ and GSM/GPRS/EDGE module
|
||||
config EXAMPLE_MODEM_DEVICE_ESPAT
|
||||
bool "ESP-AT"
|
||||
help
|
||||
ESP-AT firmware for ESP32 modules with WiFi connectivity
|
||||
endchoice
|
||||
|
||||
if EXAMPLE_MODEM_DEVICE_ESPAT
|
||||
config EXAMPLE_WIFI_SSID
|
||||
string "WiFi SSID"
|
||||
default "myssid"
|
||||
help
|
||||
SSID (network name) to connect to.
|
||||
|
||||
config EXAMPLE_WIFI_PASSWORD
|
||||
string "WiFi Password"
|
||||
default "mypassword"
|
||||
help
|
||||
WiFi password (WPA or WPA2).
|
||||
endif
|
||||
|
||||
config EXAMPLE_MODEM_APN
|
||||
string "Set MODEM APN"
|
||||
default "internet"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include <cxx_include/esp_modem_dce_factory.hpp>
|
||||
#include "sock_commands.hpp"
|
||||
#include "sock_module.hpp"
|
||||
#include <sys/socket.h>
|
||||
|
||||
#pragma once
|
||||
@@ -97,8 +98,8 @@ private:
|
||||
std::shared_ptr<esp_modem::DTE> &dte;
|
||||
};
|
||||
|
||||
class DCE : public ::esp_modem::GenericModule {
|
||||
using esp_modem::GenericModule::GenericModule;
|
||||
class DCE : public Module {
|
||||
using Module::Module;
|
||||
public:
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_modem_config.h"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace sock_dce {
|
||||
|
||||
class Module: public esp_modem::GenericModule {
|
||||
using esp_modem::GenericModule::GenericModule;
|
||||
public:
|
||||
|
||||
esp_modem::command_result sync() override;
|
||||
esp_modem::command_result set_echo(bool on) override;
|
||||
esp_modem::command_result set_pdp_context(esp_modem::PdpContext &pdp) override;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include <cxx_include/esp_modem_dce_factory.hpp>
|
||||
#include "sock_commands.hpp"
|
||||
#include "sock_module.hpp"
|
||||
#include <sys/socket.h>
|
||||
|
||||
#pragma once
|
||||
@@ -97,8 +98,8 @@ private:
|
||||
std::shared_ptr<esp_modem::DTE> &dte;
|
||||
};
|
||||
|
||||
class DCE : public ::esp_modem::GenericModule {
|
||||
using esp_modem::GenericModule::GenericModule;
|
||||
class DCE : public Module {
|
||||
using Module::Module;
|
||||
public:
|
||||
|
||||
// --- ESP-MODEM command module starts here ---
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_modem_config.h"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace sock_dce {
|
||||
|
||||
using Module = esp_modem::GenericModule;
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@@ -23,8 +23,8 @@
|
||||
#include "tcp_transport_mbedtls.h"
|
||||
#include "tcp_transport_at.h"
|
||||
|
||||
#define BROKER_URL "mqtt.eclipseprojects.io"
|
||||
#define BROKER_PORT 8883
|
||||
#define BROKER_URL "test.mosquitto.org"
|
||||
#define BROKER_PORT 1883
|
||||
|
||||
|
||||
static const char *TAG = "modem_client";
|
||||
@@ -114,7 +114,7 @@ extern "C" void app_main(void)
|
||||
mqtt_config.broker.address.port = BROKER_PORT;
|
||||
mqtt_config.session.message_retransmit_timeout = 10000;
|
||||
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
|
||||
mqtt_config.broker.address.uri = "mqtts://127.0.0.1";
|
||||
mqtt_config.broker.address.uri = "mqtt://127.0.0.1";
|
||||
dce->start_listening(BROKER_PORT);
|
||||
#else
|
||||
mqtt_config.broker.address.uri = "mqtt://" BROKER_URL;
|
||||
@@ -124,14 +124,14 @@ extern "C" void app_main(void)
|
||||
mqtt_config.network.transport = ssl;
|
||||
#endif
|
||||
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
|
||||
esp_mqtt_client_register_event(mqtt_client, static_cast<esp_mqtt_event_id_t>(ESP_EVENT_ANY_ID), mqtt_event_handler, NULL);
|
||||
esp_mqtt_client_register_event(mqtt_client, static_cast<esp_mqtt_event_id_t>(ESP_EVENT_ANY_ID), mqtt_event_handler, nullptr);
|
||||
esp_mqtt_client_start(mqtt_client);
|
||||
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
|
||||
if (!dce->connect(BROKER_URL, BROKER_PORT)) {
|
||||
ESP_LOGE(TAG, "Failed to start DCE");
|
||||
return;
|
||||
}
|
||||
while (1) {
|
||||
while (true) {
|
||||
while (dce->perform_sock()) {
|
||||
ESP_LOGV(TAG, "...performing");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <charconv>
|
||||
#include <cstring>
|
||||
#include "sock_commands.hpp"
|
||||
#include "cxx_include/esp_modem_command_library_utils.hpp"
|
||||
#include "sock_dce.hpp"
|
||||
#include "sock_module.hpp"
|
||||
|
||||
static const char *TAG = "sock_commands_espat";
|
||||
|
||||
namespace sock_dce {
|
||||
|
||||
using namespace esp_modem;
|
||||
|
||||
command_result Module::sync()
|
||||
{
|
||||
return dce_commands::generic_command_common(dte.get(), "AT\r\n");
|
||||
}
|
||||
|
||||
command_result Module::set_echo(bool on)
|
||||
{
|
||||
return dce_commands::generic_command_common(dte.get(), "ATE0\r\n");
|
||||
}
|
||||
|
||||
command_result Module::set_pdp_context(PdpContext &pdp)
|
||||
{
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace sock_commands {
|
||||
|
||||
using namespace esp_modem;
|
||||
|
||||
command_result net_open(CommandableIf *t)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
|
||||
// Set WiFi mode to station
|
||||
auto ret = dce_commands::generic_command(t, "AT+CWMODE=1\r\n", "OK", "ERROR", 5000);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGE(TAG, "Failed to set WiFi mode");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Connect to WiFi network
|
||||
std::string wifi_cmd = "AT+CWJAP=\"" CONFIG_EXAMPLE_WIFI_SSID "\",\"" CONFIG_EXAMPLE_WIFI_PASSWORD "\"\r\n";
|
||||
ret = dce_commands::generic_command(t, wifi_cmd, "OK", "ERROR", 15000);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGE(TAG, "Failed to connect to WiFi");
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGI(TAG, "WiFi connected successfully");
|
||||
|
||||
// Set passive receive mode (1) for better control
|
||||
ret = set_rx_mode(t, 1);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGE(TAG, "Failed to set preferred Rx mode");
|
||||
return ret;
|
||||
}
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result net_close(CommandableIf *t)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
// Disconnect from WiFi
|
||||
auto ret = dce_commands::generic_command(t, "AT+CWQAP\r\n", "OK", "ERROR", 5000);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGW(TAG, "Failed to disconnect WiFi (may already be disconnected)");
|
||||
}
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result tcp_open(CommandableIf *t, const std::string &host, int port, int timeout)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
|
||||
// Set single connection mode (just in case)
|
||||
auto ret = dce_commands::generic_command(t, "AT+CIPMUX=0\r\n", "OK", "ERROR", 1000);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGW(TAG, "Failed to set single connection mode");
|
||||
}
|
||||
|
||||
// Establish TCP connection
|
||||
std::string tcp_cmd = "AT+CIPSTART=\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n";
|
||||
ret = dce_commands::generic_command(t, tcp_cmd, "CONNECT", "ERROR", timeout);
|
||||
if (ret != command_result::OK) {
|
||||
ESP_LOGE(TAG, "Failed to establish TCP connection to %s:%d", host.c_str(), port);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "TCP connection established to %s:%d", host.c_str(), port);
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result tcp_close(CommandableIf *t)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
return dce_commands::generic_command(t, "AT+CIPCLOSE\r\n", "CLOSED", "ERROR", 5000);
|
||||
}
|
||||
|
||||
command_result tcp_send(CommandableIf *t, uint8_t *data, size_t len)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
// This function is not used in the current implementation
|
||||
// Data sending is handled by the DCE responder
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
command_result tcp_recv(CommandableIf *t, uint8_t *data, size_t len, size_t &out_len)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
// This function is not used in the current implementation
|
||||
// Data receiving is handled by the DCE responder
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
command_result get_ip(CommandableIf *t, std::string &ip)
|
||||
{
|
||||
ESP_LOGV(TAG, "%s", __func__);
|
||||
std::string out;
|
||||
auto ret = dce_commands::at_raw(t, "AT+CIFSR\r\n", out, "OK", "ERROR", 5000);
|
||||
if (ret != command_result::OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Parse station IP from response
|
||||
// Expected format: +CIFSR:STAIP,"192.168.1.100"
|
||||
auto pos = out.find("+CIFSR:STAIP,\"");
|
||||
if (pos != std::string::npos) {
|
||||
pos += 14; // Move past "+CIFSR:STAIP,\""
|
||||
auto end_pos = out.find("\"", pos);
|
||||
if (end_pos != std::string::npos) {
|
||||
ip = out.substr(pos, end_pos - pos);
|
||||
ESP_LOGI(TAG, "Got IP address: %s", ip.c_str());
|
||||
return command_result::OK;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "Failed to parse IP address from response");
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
||||
command_result set_rx_mode(CommandableIf *t, int mode)
|
||||
{
|
||||
ESP_LOGE(TAG, "%s", __func__);
|
||||
// Active mode (0) sends data automatically, Passive mode (1) notifies about data for reading
|
||||
std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(mode) + "\r\n";
|
||||
return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000);
|
||||
}
|
||||
|
||||
} // sock_commands
|
||||
|
||||
namespace sock_dce {
|
||||
|
||||
void Responder::start_sending(size_t len)
|
||||
{
|
||||
data_to_send = len;
|
||||
send_stat = 0;
|
||||
send_cmd("AT+CIPSEND=" + std::to_string(len) + "\r\n");
|
||||
}
|
||||
|
||||
void Responder::start_receiving(size_t len)
|
||||
{
|
||||
send_cmd("AT+CIPRECVDATA=" + std::to_string(len) + "\r\n");
|
||||
}
|
||||
|
||||
bool Responder::start_connecting(std::string host, int port)
|
||||
{
|
||||
std::string cmd = "AT+CIPSTART=\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n";
|
||||
send_cmd(cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
Responder::ret Responder::recv(uint8_t *data, size_t len)
|
||||
{
|
||||
const int MIN_MESSAGE = 6;
|
||||
size_t actual_len = 0;
|
||||
auto *recv_data = (char *)data;
|
||||
|
||||
if (data_to_recv == 0) {
|
||||
const std::string_view head = "+CIPRECVDATA:";
|
||||
|
||||
// Find the response header
|
||||
auto head_pos = std::search(recv_data, recv_data + len, head.data(), head.data() + head.size(), [](char a, char b) {
|
||||
return a == b;
|
||||
});
|
||||
|
||||
if (head_pos == recv_data + len) {
|
||||
return ret::FAIL;
|
||||
}
|
||||
|
||||
// Find the end of the length field
|
||||
auto next_comma = (char *)memchr(head_pos + head.size(), ',', MIN_MESSAGE);
|
||||
if (next_comma == nullptr) {
|
||||
return ret::FAIL;
|
||||
}
|
||||
|
||||
// Parse the actual length
|
||||
if (std::from_chars(head_pos + head.size(), next_comma, actual_len).ec == std::errc::invalid_argument) {
|
||||
ESP_LOGE(TAG, "Cannot convert length");
|
||||
return ret::FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Received: actual len=%zu", actual_len);
|
||||
if (actual_len == 0) {
|
||||
ESP_LOGD(TAG, "No data received");
|
||||
return ret::FAIL;
|
||||
}
|
||||
|
||||
if (actual_len > buffer_size) {
|
||||
ESP_LOGE(TAG, "Data too large: %zu > %zu", actual_len, buffer_size);
|
||||
return ret::FAIL;
|
||||
}
|
||||
|
||||
// Move to the actual data after the comma
|
||||
recv_data = next_comma + 1;
|
||||
auto first_data_len = len - (recv_data - (char *)data);
|
||||
|
||||
if (actual_len > first_data_len) {
|
||||
on_read(recv_data, first_data_len);
|
||||
data_to_recv = actual_len - first_data_len;
|
||||
return ret::NEED_MORE_DATA;
|
||||
}
|
||||
on_read(recv_data, actual_len);
|
||||
|
||||
} else if (data_to_recv > len) { // Continue receiving
|
||||
on_read(recv_data, len);
|
||||
data_to_recv -= len;
|
||||
return ret::NEED_MORE_DATA;
|
||||
|
||||
} else if (data_to_recv <= len) { // Last chunk
|
||||
on_read(recv_data, data_to_recv);
|
||||
actual_len = data_to_recv;
|
||||
}
|
||||
|
||||
// Look for "OK" marker after the data
|
||||
char *ok_pos = nullptr;
|
||||
if (actual_len + 1 + 2 /* OK */ <= len) {
|
||||
ok_pos = (char *)memchr(recv_data + actual_len + 1, 'O', MIN_MESSAGE);
|
||||
if (ok_pos == nullptr || ok_pos[1] != 'K') {
|
||||
data_to_recv = 0;
|
||||
return ret::FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset and prepare for next receive
|
||||
data_to_recv = 0;
|
||||
return ret::OK;
|
||||
}
|
||||
|
||||
Responder::ret Responder::send(uint8_t *data, size_t len)
|
||||
{
|
||||
if (send_stat < 3) {
|
||||
// Look for the '>' prompt
|
||||
if (memchr(data, '>', len) == NULL) {
|
||||
if (send_stat++ < 2) {
|
||||
return ret::NEED_MORE_DATA;
|
||||
}
|
||||
ESP_LOGE(TAG, "Missed '>' prompt");
|
||||
return ret::FAIL;
|
||||
}
|
||||
|
||||
// Send the actual data
|
||||
auto written = dte->write(&buffer[0], data_to_send);
|
||||
if (written != data_to_send) {
|
||||
ESP_LOGE(TAG, "Failed to write data: %d/%zu", written, data_to_send);
|
||||
return ret::FAIL;
|
||||
}
|
||||
data_to_send = 0;
|
||||
send_stat = 3;
|
||||
}
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
Responder::ret Responder::send(std::string_view response)
|
||||
{
|
||||
if (send_stat == 3) {
|
||||
if (response.find("SEND OK") != std::string::npos) {
|
||||
send_stat = 0;
|
||||
return ret::OK;
|
||||
} else if (response.find("SEND FAIL") != std::string::npos) {
|
||||
ESP_LOGE(TAG, "Send failed");
|
||||
return ret::FAIL;
|
||||
} else if (response.find("ERROR") != std::string::npos) {
|
||||
ESP_LOGE(TAG, "Send error");
|
||||
return ret::FAIL;
|
||||
}
|
||||
}
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
Responder::ret Responder::connect(std::string_view response)
|
||||
{
|
||||
if (response.find("CONNECT") != std::string::npos) {
|
||||
ESP_LOGI(TAG, "TCP connected!");
|
||||
return ret::OK;
|
||||
}
|
||||
if (response.find("ERROR") != std::string::npos) {
|
||||
ESP_LOGE(TAG, "Failed to connect");
|
||||
return ret::FAIL;
|
||||
}
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
Responder::ret Responder::check_async_replies(status state, std::string_view &response)
|
||||
{
|
||||
ESP_LOGD(TAG, "Response: %.*s", static_cast<int>(response.size()), response.data());
|
||||
|
||||
// Handle WiFi status messages
|
||||
if (response.find("WIFI CONNECTED") != std::string::npos) {
|
||||
ESP_LOGI(TAG, "WiFi connected");
|
||||
} else if (response.find("WIFI DISCONNECTED") != std::string::npos) {
|
||||
ESP_LOGW(TAG, "WiFi disconnected");
|
||||
}
|
||||
|
||||
// Handle TCP status messages
|
||||
if (response.find("CONNECT") != std::string::npos && state == status::CONNECTING) {
|
||||
return connect(response);
|
||||
} else if (response.find("CLOSED") != std::string::npos) {
|
||||
ESP_LOGW(TAG, "TCP connection closed");
|
||||
return ret::FAIL;
|
||||
}
|
||||
|
||||
// Handle data notifications in active mode (if we switch to it later)
|
||||
if (response.find("+IPD,") != std::string::npos) {
|
||||
uint64_t data_ready = 1;
|
||||
write(data_ready_fd, &data_ready, sizeof(data_ready));
|
||||
ESP_LOGD(TAG, "Data available notification");
|
||||
}
|
||||
|
||||
if (state == status::SENDING) {
|
||||
return send(response);
|
||||
} else if (state == status::CONNECTING) {
|
||||
return connect(response);
|
||||
}
|
||||
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
Responder::ret Responder::process_data(status state, uint8_t *data, size_t len)
|
||||
{
|
||||
if (state == status::SENDING) {
|
||||
return send(data, len);
|
||||
}
|
||||
if (state == status::RECEIVING) {
|
||||
return recv(data, len);
|
||||
}
|
||||
return ret::IN_PROGRESS;
|
||||
}
|
||||
|
||||
status Responder::pending()
|
||||
{
|
||||
// For ESP-AT, we don't need a pending check like BG96
|
||||
// Just return current status
|
||||
return status::SENDING;
|
||||
}
|
||||
|
||||
} // sock_dce
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
CONFIG_EXAMPLE_MODEM_DEVICE_ESPAT=y
|
||||
@@ -13,5 +13,5 @@ CONFIG_EXAMPLE_MODEM_PPP_APN="lpwa.vodafone.com"
|
||||
CONFIG_EXAMPLE_MQTT_TEST_TOPIC="/ci/esp-modem/pppos-client"
|
||||
CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL=y
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||
CONFIG_ESP32_PANIC_PRINT_HALT=y
|
||||
CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT=y
|
||||
CONFIG_EXAMPLE_MQTT_BROKER_URI="mqtt://${CI_MQTT_BROKER_URI}"
|
||||
|
||||
@@ -8,7 +8,6 @@ CONFIG_LWIP_PPP_ENABLE_IPV6=n
|
||||
CONFIG_EXAMPLE_SERIAL_CONFIG_USB=y
|
||||
CONFIG_EXAMPLE_MODEM_DEVICE_A7670=y
|
||||
CONFIG_EXAMPLE_MODEM_PPP_APN="lpwa.vodafone.com"
|
||||
CONFIG_EXAMPLE_MODEM_PPP_AUTH_NONE=y
|
||||
CONFIG_EXAMPLE_MQTT_TEST_TOPIC="/ci/esp-modem/pppos-client"
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||
CONFIG_ESP32_PANIC_PRINT_HALT=y
|
||||
CONFIG_EXAMPLE_MQTT_BROKER_URI="mqtt://${CI_MQTT_BROKER_URI}"
|
||||
|
||||
@@ -11,9 +11,8 @@ CONFIG_EXAMPLE_MODEM_DEVICE_SIM800=y
|
||||
CONFIG_EXAMPLE_MODEM_DEVICE_BG96=n
|
||||
CONFIG_EXAMPLE_MODEM_PPP_APN="lpwa.vodafone.com"
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||
CONFIG_ESP32_PANIC_PRINT_HALT=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_EXAMPLE_CLOSE_CMUX_AT_END=y
|
||||
CONFIG_EXAMPLE_MQTT_TEST_TOPIC="/ci/esp-modem/pppos-client"
|
||||
CONFIG_BROKER_URI="mqtt://test.mosquitto.org"
|
||||
CONFIG_BROKER_URI="mqtt://${CI_MQTT_BROKER_URI}"
|
||||
|
||||
@@ -62,12 +62,16 @@ class Creator {
|
||||
public:
|
||||
Creator(std::shared_ptr<DTE> dte, esp_netif_t *esp_netif): dte(std::move(dte)), device(nullptr), netif(esp_netif)
|
||||
{
|
||||
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
|
||||
ESP_MODEM_THROW_IF_FALSE(netif != nullptr, "Null netif");
|
||||
#endif
|
||||
}
|
||||
|
||||
Creator(std::shared_ptr<DTE> dte, esp_netif_t *esp_netif, std::shared_ptr<T_Module> dev): dte(std::move(dte)), device(std::move(dev)), netif(esp_netif)
|
||||
{
|
||||
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
|
||||
ESP_MODEM_THROW_IF_FALSE(netif != nullptr, "Null netif");
|
||||
#endif
|
||||
}
|
||||
|
||||
explicit Creator(std::shared_ptr<DTE> dte): dte(std::move(dte)), device(nullptr), netif(nullptr)
|
||||
|
||||
@@ -103,6 +103,11 @@ public:
|
||||
{
|
||||
dte->set_urc_cb(on_read_cb);
|
||||
}
|
||||
|
||||
void set_enhanced_urc(esp_modem::DTE::enhanced_urc_cb enhanced_cb)
|
||||
{
|
||||
dte->set_enhanced_urc_cb(enhanced_cb);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -115,6 +115,42 @@ public:
|
||||
{
|
||||
command_cb.urc_handler = std::move(line_cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enhanced URC handler with buffer consumption control
|
||||
* @param buffer_info Information about the current buffer state
|
||||
* @return Information about how much of the buffer to consume
|
||||
*/
|
||||
struct UrcBufferInfo {
|
||||
const uint8_t* buffer_start; // Start of entire buffer
|
||||
size_t buffer_total_size; // Total buffer size
|
||||
size_t processed_offset; // Offset of already processed data
|
||||
size_t new_data_size; // Size of new data since last call
|
||||
const uint8_t* new_data_start; // Pointer to start of new data
|
||||
bool is_command_active; // Whether a command is currently waiting for response
|
||||
};
|
||||
|
||||
enum class UrcConsumeResult {
|
||||
CONSUME_NONE, // Don't consume anything, continue waiting
|
||||
CONSUME_PARTIAL, // Consume only part of the buffer
|
||||
CONSUME_ALL // Consume entire buffer
|
||||
};
|
||||
|
||||
struct UrcConsumeInfo {
|
||||
UrcConsumeResult result;
|
||||
size_t consume_size; // How many bytes to consume (0 = none, SIZE_MAX = all)
|
||||
};
|
||||
|
||||
typedef std::function<UrcConsumeInfo(const UrcBufferInfo &)> enhanced_urc_cb;
|
||||
|
||||
/**
|
||||
* @brief Set enhanced URC callback with buffer consumption control
|
||||
* @param enhanced_cb Enhanced callback that can control buffer consumption
|
||||
*/
|
||||
void set_enhanced_urc_cb(enhanced_urc_cb enhanced_cb)
|
||||
{
|
||||
command_cb.enhanced_urc_handler = std::move(enhanced_cb);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -171,6 +207,33 @@ private:
|
||||
[[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode and cleanup */
|
||||
void exit_cmux_internal(); /*!< Cleanup CMUX */
|
||||
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
/**
|
||||
* @brief Buffer state tracking for enhanced URC processing
|
||||
*/
|
||||
struct BufferState {
|
||||
size_t total_processed = 0; /*!< Total bytes processed by URC handlers */
|
||||
size_t last_urc_processed = 0; /*!< Last offset processed by URC */
|
||||
bool command_waiting = false; /*!< Whether command is waiting for response */
|
||||
size_t command_start_offset = 0; /*!< Where current command response started */
|
||||
} buffer_state;
|
||||
|
||||
/**
|
||||
* @brief Update buffer state when new data arrives
|
||||
* @param new_data_size Size of new data added to buffer
|
||||
*/
|
||||
void update_buffer_state(size_t new_data_size);
|
||||
|
||||
/**
|
||||
* @brief Create URC buffer information for enhanced handlers
|
||||
* @param data Buffer data pointer
|
||||
* @param consumed Already consumed bytes
|
||||
* @param len New data length
|
||||
* @return UrcBufferInfo structure with complete buffer context
|
||||
*/
|
||||
UrcBufferInfo create_urc_info(uint8_t* data, size_t consumed, size_t len);
|
||||
#endif
|
||||
|
||||
Lock internal_lock{}; /*!< Locks DTE operations */
|
||||
unique_buffer buffer; /*!< DTE buffer */
|
||||
std::shared_ptr<CMux> cmux_term; /*!< Primary terminal for this DTE */
|
||||
@@ -216,6 +279,7 @@ private:
|
||||
struct command_cb {
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
got_line_cb urc_handler {}; /*!< URC callback if enabled */
|
||||
enhanced_urc_cb enhanced_urc_handler {}; /*!< Enhanced URC callback with consumption control */
|
||||
#endif
|
||||
static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */
|
||||
got_line_cb got_line; /*!< Supplied command callback */
|
||||
@@ -223,7 +287,7 @@ private:
|
||||
char separator{}; /*!< Command reply separator (end of line/processing unit) */
|
||||
command_result result{}; /*!< Command return code */
|
||||
SignalGroup signal; /*!< Event group used to signal request-response operations */
|
||||
bool process_line(uint8_t *data, size_t consumed, size_t len); /*!< Lets the processing callback handle one line (processing unit) */
|
||||
bool process_line(uint8_t *data, size_t consumed, size_t len, DTE* dte = nullptr); /*!< Lets the processing callback handle one line (processing unit) */
|
||||
bool wait_for_line(uint32_t time_ms) /*!< Waiting for command processing */
|
||||
{
|
||||
return signal.wait_any(command_cb::GOT_LINE, time_ms);
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
#define MPPE_SUPPORT 0
|
||||
|
||||
#define PPP_MAXIDLEFLAG 0
|
||||
#define PPP_MAXIDLEFLAG 100
|
||||
|
||||
#define PRINTPKT_SUPPORT 1
|
||||
#define PPP_PROTOCOLNAME 1
|
||||
|
||||
@@ -104,7 +104,7 @@ static void ppp_link_status_cb(ppp_pcb *pcb, int err_code, void *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
static u32_t ppp_output_cb(struct ppp_pcb_s *pcb, const void *data, u32_t len, void *ctx)
|
||||
static u32_t ppp_output_cb(struct ppp_pcb_s *pcb, u8_t *data, u32_t len, void *ctx)
|
||||
{
|
||||
esp_netif_t *netif = (esp_netif_t *)ctx;
|
||||
if (netif->transmit) {
|
||||
|
||||
@@ -65,6 +65,10 @@ void DTE::set_command_callbacks()
|
||||
{
|
||||
primary_term->set_read_cb([this](uint8_t *data, size_t len) {
|
||||
Scoped<Lock> l(command_cb.line_lock);
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
// Update buffer state when new data arrives
|
||||
update_buffer_state(len);
|
||||
#endif
|
||||
#ifndef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
if (command_cb.got_line == nullptr || command_cb.result != command_result::TIMEOUT) {
|
||||
return false; // this line has been processed already (got OK or FAIL previously)
|
||||
@@ -80,7 +84,7 @@ void DTE::set_command_callbacks()
|
||||
std::memcpy(inflatable.current(), data, len);
|
||||
data = inflatable.begin();
|
||||
}
|
||||
if (command_cb.process_line(data, inflatable.consumed, len)) {
|
||||
if (command_cb.process_line(data, inflatable.consumed, len, this)) {
|
||||
return true;
|
||||
}
|
||||
// at this point we're sure that the data processing hasn't finished,
|
||||
@@ -92,7 +96,7 @@ void DTE::set_command_callbacks()
|
||||
inflatable.consumed += len;
|
||||
return false;
|
||||
#else
|
||||
if (command_cb.process_line(data, 0, len)) {
|
||||
if (command_cb.process_line(data, 0, len, this)) {
|
||||
return true;
|
||||
}
|
||||
// cannot inflate and the processing hasn't finishes in the first iteration, but continue
|
||||
@@ -105,7 +109,7 @@ void DTE::set_command_callbacks()
|
||||
if (buffer.size > buffer.consumed) {
|
||||
data = buffer.get();
|
||||
len = primary_term->read(data + buffer.consumed, buffer.size - buffer.consumed);
|
||||
if (command_cb.process_line(data, buffer.consumed, len)) {
|
||||
if (command_cb.process_line(data, buffer.consumed, len, this)) {
|
||||
return true;
|
||||
}
|
||||
buffer.consumed += len;
|
||||
@@ -121,7 +125,7 @@ void DTE::set_command_callbacks()
|
||||
inflatable.grow(inflatable.consumed + len);
|
||||
}
|
||||
len = primary_term->read(inflatable.current(), len);
|
||||
if (command_cb.process_line(inflatable.begin(), inflatable.consumed, len)) {
|
||||
if (command_cb.process_line(inflatable.begin(), inflatable.consumed, len, this)) {
|
||||
return true;
|
||||
}
|
||||
inflatable.consumed += len;
|
||||
@@ -150,10 +154,19 @@ void DTE::set_command_callbacks()
|
||||
command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator)
|
||||
{
|
||||
Scoped<Lock> l1(internal_lock);
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
// Track command start
|
||||
buffer_state.command_waiting = true;
|
||||
buffer_state.command_start_offset = buffer_state.total_processed;
|
||||
#endif
|
||||
command_cb.set(got_line, separator);
|
||||
primary_term->write((uint8_t *)command.c_str(), command.length());
|
||||
command_cb.wait_for_line(time_ms);
|
||||
command_cb.set(nullptr);
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
// Track command end
|
||||
buffer_state.command_waiting = false;
|
||||
#endif
|
||||
buffer.consumed = 0;
|
||||
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
|
||||
inflatable.deflate();
|
||||
@@ -365,18 +378,54 @@ 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)
|
||||
bool DTE::command_cb::process_line(uint8_t *data, size_t consumed, size_t len, DTE* dte)
|
||||
{
|
||||
// returning true indicates that the processing finished and lower layers can destroy the accumulated buffer
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
bool consume_buffer = false;
|
||||
if (urc_handler) {
|
||||
consume_buffer = urc_handler(data, consumed + len) != command_result::TIMEOUT;
|
||||
// Call enhanced URC handler if registered
|
||||
if (enhanced_urc_handler && dte) {
|
||||
// Create buffer info for enhanced URC handler
|
||||
UrcBufferInfo buffer_info = dte->create_urc_info(data, consumed, len);
|
||||
|
||||
// Call enhanced URC handler
|
||||
UrcConsumeInfo consume_info = enhanced_urc_handler(buffer_info);
|
||||
|
||||
// Handle consumption control
|
||||
switch (consume_info.result) {
|
||||
case UrcConsumeResult::CONSUME_NONE:
|
||||
// Don't consume anything, continue with command processing
|
||||
break;
|
||||
|
||||
case UrcConsumeResult::CONSUME_PARTIAL:
|
||||
// Consume only specified amount
|
||||
dte->buffer_state.last_urc_processed += consume_info.consume_size;
|
||||
// Adjust data pointers for command processing
|
||||
data += consume_info.consume_size;
|
||||
consumed = (consumed + len) - consume_info.consume_size;
|
||||
len = 0;
|
||||
break;
|
||||
|
||||
case UrcConsumeResult::CONSUME_ALL:
|
||||
// Consume entire buffer
|
||||
dte->buffer_state.last_urc_processed = consumed + len;
|
||||
return true; // Signal buffer consumption
|
||||
}
|
||||
}
|
||||
if (result != command_result::TIMEOUT || got_line == nullptr) {
|
||||
return consume_buffer; // this line has been processed already (got OK or FAIL previously)
|
||||
|
||||
// Fallback to legacy URC handler if enhanced handler not set
|
||||
if (urc_handler) {
|
||||
bool consume_buffer = urc_handler(data, consumed + len) != command_result::TIMEOUT;
|
||||
if (result != command_result::TIMEOUT || got_line == nullptr) {
|
||||
return consume_buffer; // this line has been processed already (got OK or FAIL previously)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Continue with normal command processing
|
||||
if (result != command_result::TIMEOUT || got_line == nullptr) {
|
||||
return false; // Command processing continues
|
||||
}
|
||||
|
||||
if (memchr(data + consumed, separator, len)) {
|
||||
result = got_line(data, consumed + len);
|
||||
if (result == command_result::OK || result == command_result::FAIL) {
|
||||
@@ -423,3 +472,22 @@ void DTE::extra_buffer::grow(size_t need_size)
|
||||
*/
|
||||
unique_buffer::unique_buffer(size_t size):
|
||||
data(std::make_unique<uint8_t[]>(size)), size(size), consumed(0) {}
|
||||
|
||||
#ifdef CONFIG_ESP_MODEM_URC_HANDLER
|
||||
void DTE::update_buffer_state(size_t new_data_size)
|
||||
{
|
||||
buffer_state.total_processed += new_data_size;
|
||||
}
|
||||
|
||||
DTE::UrcBufferInfo DTE::create_urc_info(uint8_t* data, size_t consumed, size_t len)
|
||||
{
|
||||
return {
|
||||
.buffer_start = data,
|
||||
.buffer_total_size = consumed + len,
|
||||
.processed_offset = buffer_state.last_urc_processed,
|
||||
.new_data_size = (consumed + len) - buffer_state.last_urc_processed,
|
||||
.new_data_start = data + buffer_state.last_urc_processed,
|
||||
.is_command_active = buffer_state.command_waiting
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "esp_netif_ppp.h"
|
||||
|
||||
|
||||
namespace esp_modem {
|
||||
|
||||
void Netif::on_ppp_changed(void *arg, esp_event_base_t event_base,
|
||||
@@ -39,6 +38,7 @@ esp_err_t Netif::esp_modem_dte_transmit(void *h, void *buffer, size_t len)
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
|
||||
esp_err_t Netif::esp_modem_post_attach(esp_netif_t *esp_netif, void *args)
|
||||
{
|
||||
auto d = (ppp_netif_driver *) args;
|
||||
@@ -62,6 +62,7 @@ esp_err_t Netif::esp_modem_post_attach(esp_netif_t *esp_netif, void *args)
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif // CONFIG_ESP_MODEM_USE_PPP_MODE
|
||||
|
||||
void Netif::receive(uint8_t *data, size_t len)
|
||||
{
|
||||
@@ -73,12 +74,16 @@ Netif::Netif(std::shared_ptr<DTE> e, esp_netif_t *ppp_netif) :
|
||||
{
|
||||
driver.base.netif = ppp_netif;
|
||||
driver.ppp = this;
|
||||
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
|
||||
driver.base.post_attach = esp_modem_post_attach;
|
||||
ESP_MODEM_THROW_IF_ERROR(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, (void *) this));
|
||||
#endif
|
||||
ESP_MODEM_THROW_IF_ERROR(esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, ppp_netif));
|
||||
ESP_MODEM_THROW_IF_ERROR(
|
||||
esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected, ppp_netif));
|
||||
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
|
||||
ESP_MODEM_THROW_IF_ERROR(esp_netif_attach(ppp_netif, &driver));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Netif::start()
|
||||
@@ -120,7 +125,9 @@ Netif::~Netif()
|
||||
signal.clear(PPP_STARTED);
|
||||
signal.wait(PPP_EXIT, 30000);
|
||||
}
|
||||
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
|
||||
esp_event_handler_unregister(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed);
|
||||
#endif
|
||||
esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected);
|
||||
esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected);
|
||||
}
|
||||
|
||||
@@ -223,9 +223,9 @@ static int ppp_cmd_iperf(int argc, char **argv)
|
||||
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,
|
||||
(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,
|
||||
@@ -234,6 +234,18 @@ static int ppp_cmd_iperf(int argc, char **argv)
|
||||
iperf_start(&cfg);
|
||||
return 0;
|
||||
}
|
||||
static int restart(int argc, char **argv)
|
||||
{
|
||||
ESP_LOGI("main", "Restarting");
|
||||
esp_restart();
|
||||
return 0;
|
||||
}
|
||||
static int heap_size(int argc, char **argv)
|
||||
{
|
||||
uint32_t heap_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
|
||||
ESP_LOGI(TAG, "min heap size: %" PRIu32, heap_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_pppd(void)
|
||||
{
|
||||
@@ -286,4 +298,25 @@ void register_pppd(void)
|
||||
.argtable = &iperf_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&iperf_cmd));
|
||||
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "restart",
|
||||
.help = "Restart the program",
|
||||
.hint = NULL,
|
||||
.func = &restart,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
|
||||
|
||||
}
|
||||
{
|
||||
const esp_console_cmd_t heap_cmd = {
|
||||
.command = "heap",
|
||||
.help = "Get minimum size of free heap memory that was available during program execution",
|
||||
.hint = NULL,
|
||||
.func = &heap_size,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&heap_cmd));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,5 @@
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../.." "../../../mbedtls_cxx")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(ota_test)
|
||||
|
||||
10
components/esp_modem/test/target_ota/main/idf_component.yml
Normal file
10
components/esp_modem/test/target_ota/main/idf_component.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/esp_modem:
|
||||
version: "*"
|
||||
override_path: ../../..
|
||||
espressif/mbedtls_cxx:
|
||||
version: "*"
|
||||
override_path: ../../../../mbedtls_cxx
|
||||
idf:
|
||||
version: ">=4.1.0"
|
||||
@@ -14,11 +14,17 @@
|
||||
#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 "esp_idf_version.h"
|
||||
#include "network_dce.hpp"
|
||||
#include "manual_ota.hpp"
|
||||
#include "mqtt_client.h"
|
||||
// For optional VFS support
|
||||
#include "vfs_resource/vfs_create.hpp"
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
#include "driver/uart_vfs.h"
|
||||
#else
|
||||
#include "esp_vfs_dev.h"
|
||||
#endif
|
||||
|
||||
using namespace esp_modem;
|
||||
|
||||
@@ -189,7 +195,12 @@ extern "C" void app_main(void)
|
||||
assert(vfs_create_uart(&uart_config, &dte_config.vfs_config) == true);
|
||||
|
||||
auto dte = create_vfs_dte(&dte_config);
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||
uart_vfs_dev_use_driver(uart_config.uart.port_num);
|
||||
#else
|
||||
esp_vfs_dev_uart_use_driver(uart_config.uart.port_num);
|
||||
#endif
|
||||
|
||||
#else
|
||||
auto dte = create_uart_dte(&dte_config);
|
||||
#endif // CONFIG_TEST_USE_VFS_TERM
|
||||
|
||||
@@ -1,4 +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_CA_CERT="MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTflMrY="
|
||||
CONFIG_TEST_OTA_CN="github.com"
|
||||
|
||||
152
components/esp_modem/test/target_urc/README.md
Normal file
152
components/esp_modem/test/target_urc/README.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# ESP Modem Enhanced URC Test
|
||||
|
||||
## Overview
|
||||
|
||||
This test validates the enhanced URC (Unsolicited Result Code) interface with buffer consumption control. It demonstrates the new `set_enhanced_urc()` API that provides granular control over buffer consumption and complete buffer visibility.
|
||||
|
||||
## Test Configuration
|
||||
|
||||
- **Target**: ESP-AT device with HTTP server
|
||||
- **UART**: 115200 baud, 8N1, TX=17, RX=18
|
||||
- **Buffer Size**: 1024 bytes
|
||||
- **Timeout**: 15 seconds
|
||||
|
||||
## Test Cases
|
||||
|
||||
### 1. Enhanced URC Handler Registration
|
||||
- **Objective**: Verify enhanced URC handler can be registered
|
||||
- **Method**: Call `set_enhanced_urc()` with custom handler
|
||||
- **Expected**: Handler receives `UrcBufferInfo` with complete buffer context
|
||||
|
||||
### 2. Buffer Visibility
|
||||
- **Objective**: Verify URC handler receives complete buffer information
|
||||
- **Method**: Log buffer state information in handler
|
||||
- **Expected**: Handler receives `buffer_start`, `buffer_total_size`, `processed_offset`, `new_data_size`, `is_command_active`
|
||||
|
||||
### 3. Granular Consumption Control
|
||||
- **Objective**: Verify handler can consume partial buffer data
|
||||
- **Method**: Process HTTP URCs line-by-line using `CONSUME_PARTIAL`
|
||||
- **Expected**: Each complete line is consumed individually, remaining data preserved
|
||||
|
||||
### 4. Multi-part Response Handling
|
||||
- **Objective**: Verify handling of chunked HTTP responses
|
||||
- **Method**: Process 9 HTTP chunks from ESP-AT server
|
||||
- **Expected**: All chunks processed correctly with proper offset tracking
|
||||
|
||||
### 5. Transfer Completion Detection
|
||||
- **Objective**: Verify detection of transfer completion message
|
||||
- **Method**: Search for "Transfer completed" in buffer
|
||||
- **Expected**: Completion detected and event group signaled
|
||||
|
||||
### 6. Command State Awareness
|
||||
- **Objective**: Verify handler knows command state
|
||||
- **Method**: Check `is_command_active` flag during processing
|
||||
- **Expected**: Flag correctly reflects command state
|
||||
|
||||
## Test Implementation
|
||||
|
||||
The test uses an ESP-AT device running an HTTP server that sends chunked responses. The enhanced URC handler processes each HTTP URC line individually and detects completion.
|
||||
|
||||
### Key Components
|
||||
|
||||
```cpp
|
||||
// Enhanced URC handler registration
|
||||
set_enhanced_urc(handle_enhanced_urc);
|
||||
|
||||
// Handler implementation
|
||||
static esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo& info)
|
||||
{
|
||||
// Process HTTP URCs with granular consumption control
|
||||
if (line.starts_with("+HTTPCGET:")) {
|
||||
// Consume this line only
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_PARTIAL, line_end + 1};
|
||||
}
|
||||
|
||||
// Check for completion
|
||||
if (buffer.find("Transfer completed") != std::string_view::npos) {
|
||||
xEventGroupSetBits(s_event_group, transfer_completed);
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_ALL, 0};
|
||||
}
|
||||
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
}
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
### Successful Test Run
|
||||
```
|
||||
I (908) urc_test: Starting Enhanced URC Test
|
||||
I (938) urc_test: Start HTTP server...(0)
|
||||
I (948) urc_test: HTTP GET...(43)
|
||||
I (1228) urc_test: HTTP URC: +HTTPCGET:27,=== Async Response #4 ===
|
||||
I (2778) urc_test: HTTP URC: +HTTPCGET:61,[1/9] [633135 ms] This is a simulated slow server response.
|
||||
I (4288) urc_test: HTTP URC: +HTTPCGET:71,[2/9] [634639 ms] Chunk 1: The ESP-AT HTTP server is demonstrating...
|
||||
I (5788) urc_test: HTTP URC: +HTTPCGET:73,[3/9] [636143 ms] Chunk 2: ...asynchronous chunked transfer encoding...
|
||||
I (7288) urc_test: HTTP URC: +HTTPCGET:72,[4/9] [637647 ms] Chunk 3: ...with artificial delays between chunks...
|
||||
I (8788) urc_test: HTTP URC: +HTTPCGET:74,[5/9] [639151 ms] Chunk 4: ...to simulate real-world network conditions.
|
||||
I (10288) urc_test: HTTP URC: +HTTPCGET:62,[6/9] [640655 ms] Chunk 5: Processing data... please wait...
|
||||
I (11788) urc_test: HTTP URC: +HTTPCGET:63,[7/9] [642159 ms] Chunk 6: Still processing... almost done...
|
||||
I (13288) urc_test: HTTP URC: +HTTPCGET:61,[8/9] [643663 ms] Chunk 7: Final chunk - transfer complete!
|
||||
I (14758) urc_test: HTTP URC: +HTTPCGET:43,[9/9] [645168 ms] === END OF RESPONSE ===
|
||||
I (15258) urc_test: Transfer completed detected in buffer!
|
||||
I (15298) urc_test: Enhanced URC test completed successfully!
|
||||
I (15308) urc_test: The enhanced URC handler successfully processed all HTTP chunks
|
||||
I (15308) urc_test: with granular buffer consumption control
|
||||
```
|
||||
|
||||
### Debug Output (with ESP_LOG_LEVEL_DEBUG)
|
||||
```
|
||||
D (958) urc_test: URC Buffer Info: total_size=43, processed_offset=0, new_data_size=43, command_active=false
|
||||
D (958) urc_test: Buffer content (first 43 chars): AT+HTTPCGET="http://127.0.0.1:8080/async"
|
||||
D (968) urc_test: Other data: AT+HTTPCGET="http://127.0.0.1:8080/async"
|
||||
D (1218) urc_test: URC Buffer Info: total_size=85, processed_offset=43, new_data_size=42, command_active=false
|
||||
D (1228) urc_test: Consuming 40 bytes (line_end=82, processed_offset=43)
|
||||
D (2778) urc_test: Consuming 76 bytes (line_end=158, processed_offset=83)
|
||||
```
|
||||
|
||||
### Failed Test (Timeout)
|
||||
```
|
||||
I (908) urc_test: Starting Enhanced URC Test
|
||||
I (948) urc_test: HTTP GET...(43)
|
||||
E (15385) urc_test: Enhanced URC test timed out
|
||||
I (15385) urc_test: Enhanced URC test done
|
||||
```
|
||||
|
||||
## Test Validation
|
||||
|
||||
### Success Criteria
|
||||
- All 9 HTTP chunks processed successfully
|
||||
- Transfer completion detected within 15 seconds
|
||||
- No buffer arithmetic errors (no negative `new_data_size`)
|
||||
- Proper consumption control (line-by-line processing)
|
||||
|
||||
### Failure Indicators
|
||||
- Test timeout (15 seconds)
|
||||
- Buffer arithmetic errors (negative `new_data_size`)
|
||||
- Missing HTTP chunks
|
||||
- Transfer completion not detected
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-AT device with HTTP server capability
|
||||
- UART connection configured
|
||||
- `CONFIG_ESP_MODEM_URC_HANDLER=y`
|
||||
- FreeRTOS event groups
|
||||
|
||||
## Build and Run
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
idf.py flash monitor
|
||||
```
|
||||
|
||||
## Comparison with Legacy URC
|
||||
|
||||
| Feature | Legacy URC | Enhanced URC |
|
||||
|---------|------------|--------------|
|
||||
| Buffer Consumption | All or nothing | Granular (partial) |
|
||||
| Buffer Visibility | None | Complete context |
|
||||
| Command State | Unknown | Known (`is_command_active`) |
|
||||
| Processing State | Unknown | Tracked (`processed_offset`) |
|
||||
| Multi-part Support | Limited | Full support |
|
||||
@@ -3,7 +3,26 @@
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file urc_test.cpp
|
||||
* @brief Enhanced URC (Unsolicited Result Code) Test
|
||||
*
|
||||
* This test demonstrates the new enhanced URC interface with buffer consumption control.
|
||||
* It tests the following features:
|
||||
*
|
||||
* 1. Enhanced URC Handler Registration: Uses set_enhanced_urc() instead of set_urc()
|
||||
* 2. Buffer Visibility: URC handler receives complete buffer information
|
||||
* 3. Granular Consumption Control: Handler can consume none, partial, or all buffer data
|
||||
* 4. Processing State Awareness: Handler knows what data is new vs. already processed
|
||||
* 5. Command State Awareness: Handler knows if a command is currently active
|
||||
*
|
||||
* The test works with ESP-AT HTTP server that sends chunked responses, demonstrating
|
||||
* how the enhanced URC handler can process multi-part responses with precise control
|
||||
* over buffer consumption.
|
||||
*/
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_netif.h"
|
||||
@@ -67,7 +86,7 @@ public:
|
||||
bool http_get(const std::string &url)
|
||||
{
|
||||
std::string command = "AT+HTTPCGET=\"" + url + "\"\r\n";
|
||||
set_urc(handle_urc);
|
||||
set_enhanced_urc(handle_enhanced_urc);
|
||||
auto ret = dte->write(esp_modem::DTE_Command(command));
|
||||
ESP_LOGI(TAG, "HTTP GET...(%d)", static_cast<int>(ret));
|
||||
return ret > 0;
|
||||
@@ -82,25 +101,88 @@ public:
|
||||
|
||||
static constexpr int transfer_completed = 1;
|
||||
private:
|
||||
static esp_modem::command_result handle_urc(uint8_t *data, size_t len)
|
||||
static esp_modem::DTE::UrcConsumeInfo handle_enhanced_urc(const esp_modem::DTE::UrcBufferInfo &info)
|
||||
{
|
||||
static int start_chunk = 0;
|
||||
static int end_chunk = 0;
|
||||
std::string_view chunk((const char*)data + start_chunk, len - start_chunk);
|
||||
int newline = chunk.find('\n');
|
||||
if (newline == std::string_view::npos) {
|
||||
end_chunk = len; // careful, this grows buffer usage
|
||||
printf(".");
|
||||
return esp_modem::command_result::TIMEOUT;
|
||||
// Log buffer information for debugging
|
||||
ESP_LOGD(TAG, "URC Buffer Info: total_size=%zu, processed_offset=%zu, new_data_size=%zu, command_active=%s",
|
||||
info.buffer_total_size, info.processed_offset, info.new_data_size,
|
||||
info.is_command_active ? "true" : "false");
|
||||
|
||||
// Debug: Show buffer content (first 200 chars)
|
||||
if (info.buffer_total_size > 0) {
|
||||
size_t debug_len = std::min(info.buffer_total_size, (size_t)200);
|
||||
ESP_LOGD(TAG, "Buffer content (first %zu chars): %.*s",
|
||||
debug_len, (int)debug_len, (const char*)info.buffer_start);
|
||||
}
|
||||
printf("%.*s\n", newline, (char*)data + start_chunk);
|
||||
start_chunk = end_chunk;
|
||||
// check for the last one
|
||||
constexpr char last_chunk[] = "Transfer completed";
|
||||
if (memmem(data, len, last_chunk, sizeof(last_chunk) - 1) != nullptr) {
|
||||
|
||||
// Create string view of the entire buffer for processing
|
||||
std::string_view buffer((const char*)info.buffer_start, info.buffer_total_size);
|
||||
|
||||
// First, check if we have the completion message anywhere in the buffer
|
||||
if (buffer.find("Transfer completed") != std::string_view::npos) {
|
||||
ESP_LOGI(TAG, "Transfer completed detected in buffer!");
|
||||
xEventGroupSetBits(s_event_group, transfer_completed);
|
||||
// Consume everything
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_ALL, 0};
|
||||
}
|
||||
return esp_modem::command_result::OK;
|
||||
|
||||
// Process from the last processed offset
|
||||
size_t search_start = info.processed_offset;
|
||||
|
||||
// Look for complete lines starting from the processed offset
|
||||
while (search_start < info.buffer_total_size) {
|
||||
size_t line_end = buffer.find('\n', search_start);
|
||||
|
||||
if (line_end == std::string_view::npos) {
|
||||
// No complete line found, wait for more data
|
||||
ESP_LOGD(TAG, "Waiting for more data... (search_start=%zu, total_size=%zu)",
|
||||
search_start, info.buffer_total_size);
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
}
|
||||
|
||||
// Found a complete line, process it
|
||||
std::string_view line = buffer.substr(search_start, line_end - search_start);
|
||||
|
||||
// Remove carriage return if present
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.remove_suffix(1);
|
||||
}
|
||||
|
||||
// Check if this is an HTTP URC
|
||||
if (line.starts_with("+HTTPCGET:")) {
|
||||
ESP_LOGI(TAG, "HTTP URC: %.*s", (int)line.length(), line.data());
|
||||
|
||||
// Check for transfer completion - look for "Transfer completed" anywhere in the line
|
||||
if (line.find("Transfer completed") != std::string_view::npos) {
|
||||
ESP_LOGI(TAG, "Transfer completed detected!");
|
||||
xEventGroupSetBits(s_event_group, transfer_completed);
|
||||
}
|
||||
|
||||
// Consume this line (including the newline)
|
||||
size_t consume_size = line_end + 1 - info.processed_offset;
|
||||
ESP_LOGD(TAG, "Consuming %zu bytes (line_end=%zu, processed_offset=%zu)",
|
||||
consume_size, line_end, info.processed_offset);
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_PARTIAL, consume_size};
|
||||
|
||||
} else if (line.starts_with("+HTTPCGET")) {
|
||||
// Partial HTTP URC, don't consume yet
|
||||
ESP_LOGD(TAG, "Partial HTTP URC: %.*s", (int)line.length(), line.data());
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
|
||||
} else if (!line.empty()) {
|
||||
// Other data, log and consume
|
||||
ESP_LOGD(TAG, "Other data: %.*s", (int)line.length(), line.data());
|
||||
size_t consume_size = line_end + 1 - info.processed_offset;
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_PARTIAL, consume_size};
|
||||
}
|
||||
|
||||
// Move to next line
|
||||
search_start = line_end + 1;
|
||||
}
|
||||
|
||||
// Processed all available data
|
||||
ESP_LOGD(TAG, "Processed all available data");
|
||||
return {esp_modem::DTE::UrcConsumeResult::CONSUME_NONE, 0};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,8 +213,8 @@ extern "C" void app_main(void)
|
||||
|
||||
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
|
||||
dte_config.dte_buffer_size = 1024;
|
||||
dte_config.uart_config.tx_io_num = 18;
|
||||
dte_config.uart_config.rx_io_num = 17;
|
||||
dte_config.uart_config.tx_io_num = 17;
|
||||
dte_config.uart_config.rx_io_num = 18;
|
||||
auto uart_dte = esp_modem::create_uart_dte(&dte_config);
|
||||
if (uart_dte == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create UART DTE");
|
||||
@@ -144,15 +226,24 @@ extern "C" void app_main(void)
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting Enhanced URC Test");
|
||||
ESP_LOGI(TAG, "This test demonstrates the new enhanced URC interface with buffer consumption control");
|
||||
|
||||
dce->start_http_server();
|
||||
|
||||
ESP_LOGI(TAG, "Sending HTTP GET request with enhanced URC handler");
|
||||
dce->http_get("http://127.0.0.1:8080/async");
|
||||
|
||||
EventBits_t bits = xEventGroupWaitBits(s_event_group, 1, pdTRUE, pdFALSE, pdMS_TO_TICKS(15000));
|
||||
if (bits & DCE::transfer_completed) {
|
||||
ESP_LOGI(TAG, "Request finished!");
|
||||
ESP_LOGI(TAG, "Enhanced URC test completed successfully!");
|
||||
ESP_LOGI(TAG, "The enhanced URC handler successfully processed all HTTP chunks");
|
||||
ESP_LOGI(TAG, "with granular buffer consumption control");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Enhanced URC test timed out");
|
||||
}
|
||||
|
||||
dce->sync();
|
||||
vEventGroupDelete(s_event_group);
|
||||
ESP_LOGI(TAG, "Done");
|
||||
ESP_LOGI(TAG, "Enhanced URC test done");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(websocket): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py esp_websocket_client
|
||||
tag_format: websocket-v$version
|
||||
version: 1.5.0
|
||||
version: 1.6.0
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
## [1.6.0](https://github.com/espressif/esp-protocols/commits/websocket-v1.6.0)
|
||||
|
||||
### Features
|
||||
|
||||
- add WEBSOCKET_EVENT_HEADER_RECEIVED (#827) ([18f0d028](https://github.com/espressif/esp-protocols/commit/18f0d028), [#715](https://github.com/espressif/esp-protocols/issues/715))
|
||||
- enhance example with docs, pytest setup, and standalone test server - Add comprehensive README with TOC and quick start - Add pytest setup and certificate generation scripts - Add standalone WebSocket test server with TLS support - Add troubleshooting and multiple testing approaches ([cad527d2](https://github.com/espressif/esp-protocols/commit/cad527d2))
|
||||
- Add websocket HTTP redirect ([ce1560ac](https://github.com/espressif/esp-protocols/commit/ce1560ac))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- remove redundant timeout check in client task loop ([1e83bee4](https://github.com/espressif/esp-protocols/commit/1e83bee4))
|
||||
- fix PING timing - enable periodic PING during active traffic ([7f424325](https://github.com/espressif/esp-protocols/commit/7f424325))
|
||||
- Update linux build docs on required libs ([e52a5757](https://github.com/espressif/esp-protocols/commit/e52a5757))
|
||||
- clean up component dependencies - Remove unused 'json', 'nvs_flash', 'esp_stubs', dependency from Linux build configuration - Add cJSON dependency to target example's idf_component.yml ([d665e6f1](https://github.com/espressif/esp-protocols/commit/d665e6f1))
|
||||
- fix relying on asprintf() to NULL strp on failure ([54eb0027](https://github.com/espressif/esp-protocols/commit/54eb0027))
|
||||
- Update Remaining Websocket Echo Server (#893) ([18faeb3d](https://github.com/espressif/esp-protocols/commit/18faeb3d))
|
||||
- avoid long stopping time when waiting to auto-reconnect ([2432e41d](https://github.com/espressif/esp-protocols/commit/2432e41d))
|
||||
- Update Websocket Echo Server ([94bd5b07](https://github.com/espressif/esp-protocols/commit/94bd5b07))
|
||||
|
||||
### Updated
|
||||
|
||||
- ci(common): Update test component dir for IDFv6.0 ([18418c83](https://github.com/espressif/esp-protocols/commit/18418c83))
|
||||
|
||||
## [1.5.0](https://github.com/espressif/esp-protocols/commits/websocket-v1.5.0)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -11,7 +11,7 @@ endif()
|
||||
if(${IDF_TARGET} STREQUAL "linux")
|
||||
idf_component_register(SRCS "esp_websocket_client.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES esp-tls tcp_transport http_parser esp_event nvs_flash esp_stubs json
|
||||
REQUIRES esp-tls tcp_transport http_parser esp_event
|
||||
PRIV_REQUIRES esp_timer)
|
||||
else()
|
||||
idf_component_register(SRCS "esp_websocket_client.c"
|
||||
|
||||
@@ -64,10 +64,17 @@ static const char *TAG = "websocket_client";
|
||||
#define WS_OVER_TCP_SCHEME "ws"
|
||||
#define WS_OVER_TLS_SCHEME "wss"
|
||||
#define WS_HTTP_BASIC_AUTH "Basic "
|
||||
#define WS_HTTP_REDIRECT(code) ((code >= 300) && (code < 400))
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
|
||||
// Features supported in 5.5.0
|
||||
#define WS_TRANSPORT_REDIRECT_HEADER_SUPPORT 1
|
||||
#endif
|
||||
|
||||
const static int STOPPED_BIT = BIT0;
|
||||
const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame was sent by the client
|
||||
// and we are waiting for the server to continue with clean close
|
||||
const static int REQUESTED_STOP_BIT = BIT2; // Indicates that a client stop has been requested
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS);
|
||||
|
||||
@@ -471,11 +478,20 @@ static esp_err_t stop_wait_task(esp_websocket_client_handle_t client)
|
||||
}
|
||||
|
||||
client->run = false;
|
||||
xEventGroupSetBits(client->status_bits, REQUESTED_STOP_BIT);
|
||||
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
|
||||
static void websocket_header_hook(void * client, const char * line, int line_len)
|
||||
{
|
||||
ESP_LOGD(TAG, "%s header:%.*s", __func__, line_len, line);
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_HEADER_RECEIVED, line, line_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, const char *scheme)
|
||||
{
|
||||
esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, scheme);
|
||||
@@ -485,6 +501,10 @@ static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_
|
||||
.sub_protocol = client->config->subprotocol,
|
||||
.user_agent = client->config->user_agent,
|
||||
.headers = client->config->headers,
|
||||
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
|
||||
.header_hook = websocket_header_hook,
|
||||
.header_user_context = client,
|
||||
#endif
|
||||
.auth = client->config->auth,
|
||||
.propagate_control_frames = true
|
||||
};
|
||||
@@ -719,10 +739,14 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail);
|
||||
|
||||
if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) {
|
||||
asprintf(&client->config->scheme, WS_OVER_TCP_SCHEME);
|
||||
if (asprintf(&client->config->scheme, WS_OVER_TCP_SCHEME) < 0) {
|
||||
client->config->scheme = NULL;
|
||||
}
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
|
||||
} else if (config->transport == WEBSOCKET_TRANSPORT_OVER_SSL) {
|
||||
asprintf(&client->config->scheme, WS_OVER_TLS_SCHEME);
|
||||
if (asprintf(&client->config->scheme, WS_OVER_TLS_SCHEME) < 0) {
|
||||
client->config->scheme = NULL;
|
||||
}
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
|
||||
}
|
||||
|
||||
@@ -767,7 +791,9 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
|
||||
}
|
||||
|
||||
if (client->config->scheme == NULL) {
|
||||
asprintf(&client->config->scheme, WS_OVER_TCP_SCHEME);
|
||||
if (asprintf(&client->config->scheme, WS_OVER_TCP_SCHEME) < 0) {
|
||||
client->config->scheme = NULL;
|
||||
}
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
|
||||
}
|
||||
|
||||
@@ -844,26 +870,34 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con
|
||||
}
|
||||
if (puri.field_data[UF_SCHEMA].len) {
|
||||
free(client->config->scheme);
|
||||
asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off);
|
||||
if (asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off) < 0) {
|
||||
client->config->scheme = NULL;
|
||||
}
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (puri.field_data[UF_HOST].len) {
|
||||
free(client->config->host);
|
||||
asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off);
|
||||
if (asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off) < 0) {
|
||||
client->config->host = NULL;
|
||||
}
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->host, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
|
||||
if (puri.field_data[UF_PATH].len || puri.field_data[UF_QUERY].len) {
|
||||
free(client->config->path);
|
||||
int aret = -1;
|
||||
if (puri.field_data[UF_QUERY].len == 0) {
|
||||
asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off);
|
||||
aret = asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off);
|
||||
} else if (puri.field_data[UF_PATH].len == 0) {
|
||||
asprintf(&client->config->path, "/?%.*s", puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
|
||||
aret = asprintf(&client->config->path, "/?%.*s", puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
|
||||
} else {
|
||||
asprintf(&client->config->path, "%.*s?%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off,
|
||||
puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
|
||||
aret = asprintf(&client->config->path, "%.*s?%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off,
|
||||
puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
|
||||
}
|
||||
if (aret < 0) {
|
||||
client->config->path = NULL;
|
||||
}
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
@@ -873,7 +907,9 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con
|
||||
|
||||
if (puri.field_data[UF_USERINFO].len) {
|
||||
char *user_info = NULL;
|
||||
asprintf(&user_info, "%.*s", puri.field_data[UF_USERINFO].len, uri + puri.field_data[UF_USERINFO].off);
|
||||
if (asprintf(&user_info, "%.*s", puri.field_data[UF_USERINFO].len, uri + puri.field_data[UF_USERINFO].off) < 0) {
|
||||
user_info = NULL;
|
||||
}
|
||||
if (user_info) {
|
||||
char *pass = strchr(user_info, ':');
|
||||
if (pass) {
|
||||
@@ -1072,6 +1108,29 @@ static void esp_websocket_client_task(void *pv)
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
break;
|
||||
}
|
||||
#if WS_TRANSPORT_REDIRECT_HEADER_SUPPORT
|
||||
else if (WS_HTTP_REDIRECT(result)) {
|
||||
const char *redir = esp_transport_ws_get_redir_uri(client->transport);
|
||||
if (redir) {
|
||||
// Redirecting to a new URI
|
||||
free(client->config->uri);
|
||||
|
||||
client->config->uri = strdup(redir);
|
||||
client->config->port = 0;
|
||||
|
||||
esp_websocket_client_set_uri(client, client->config->uri);
|
||||
|
||||
if (client->config->port == 0) {
|
||||
client->config->port = esp_transport_get_default_port(client->transport);
|
||||
}
|
||||
|
||||
// Rerun the connection with the redir uri.
|
||||
client->state = WEBSOCKET_STATE_INIT;
|
||||
ESP_LOGI(TAG, "Redirecting to %s", client->config->uri);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
ESP_LOGD(TAG, "Transport connected to %s://%s:%d", client->config->scheme, client->config->host, client->config->port);
|
||||
|
||||
client->state = WEBSOCKET_STATE_CONNECTED;
|
||||
@@ -1109,13 +1168,6 @@ static void esp_websocket_client_task(void *pv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (read_select == 0) {
|
||||
ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_read()...");
|
||||
break;
|
||||
}
|
||||
client->ping_tick_ms = _tick_get_ms();
|
||||
break;
|
||||
case WEBSOCKET_STATE_WAIT_TIMEOUT:
|
||||
|
||||
@@ -1168,10 +1220,12 @@ static void esp_websocket_client_task(void *pv)
|
||||
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_poll_read().");
|
||||
}
|
||||
} else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) {
|
||||
// waiting for reconnecting...
|
||||
vTaskDelay(client->wait_timeout_ms / 2 / portTICK_PERIOD_MS);
|
||||
// waiting for reconnection or a request to stop the client...
|
||||
xEventGroupWaitBits(client->status_bits, REQUESTED_STOP_BIT, false, true, client->wait_timeout_ms / 2 / portTICK_PERIOD_MS);
|
||||
} else if (WEBSOCKET_STATE_CLOSING == client->state &&
|
||||
(CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits))) {
|
||||
ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server");
|
||||
@@ -1233,7 +1287,7 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
|
||||
ESP_LOGE(TAG, "Error create websocket task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
|
||||
xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT | REQUESTED_STOP_BIT);
|
||||
ESP_LOGI(TAG, "Started");
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -1302,6 +1356,7 @@ static esp_err_t esp_websocket_client_close_with_optional_body(esp_websocket_cli
|
||||
|
||||
// If could not close gracefully within timeout, stop the client and disconnect
|
||||
client->run = false;
|
||||
xEventGroupSetBits(client->status_bits, REQUESTED_STOP_BIT);
|
||||
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
return ESP_OK;
|
||||
|
||||
@@ -6,29 +6,42 @@ This example demonstrates the ESP websocket client using the `linux` target. It
|
||||
|
||||
To compile and execute this example on Linux need to set target `linux`
|
||||
|
||||
* Debian/Ubuntu: `sudo apt-get install -y libbsd-dev`
|
||||
* Fedora/RHEL: `sudo dnf install libbsd-devel`
|
||||
* Arch: `sudo pacman -S libbsd`
|
||||
* Alpine: `sudo apk add libbsd-dev`
|
||||
|
||||
```
|
||||
idf.py --preview set-target linux
|
||||
idf.py build
|
||||
./websocket.elf
|
||||
./build/websocket.elf
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (164532) websocket: [APP] Startup..
|
||||
I (164532) websocket: [APP] Free memory: 4294967295 bytes
|
||||
I (164532) websocket: [APP] IDF version: v5.3-dev-1353-gb3f7e2c8a4
|
||||
I (164538) websocket: Connecting to ws://echo.websocket.events...
|
||||
W (164538) websocket_client: `reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
|
||||
W (164538) websocket_client: `network_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
|
||||
I (165103) websocket: WEBSOCKET_EVENT_CONNECTED
|
||||
I (165539) websocket: Sending hello 0000
|
||||
I (165627) websocket: WEBSOCKET_EVENT_DATA
|
||||
I (165627) websocket: Received opcode=1
|
||||
W (165627) websocket: Received=hello 0000
|
||||
W (165627) websocket: Total payload length=10, data_len=10, current payload offset=0
|
||||
I (76826192) websocket: [APP] Startup..
|
||||
I (76826193) websocket: [APP] Free memory: 4294967295 bytes
|
||||
I (76826193) websocket: [APP] IDF version: v6.0-dev-2414-gab3feab1d13
|
||||
I (76826195) websocket: Connecting to wss://echo.websocket.org...
|
||||
W (76826195) websocket_client: `reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
|
||||
W (76826195) websocket_client: `network_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
|
||||
I (76826195) websocket: WEBSOCKET_EVENT_BEGIN
|
||||
I (76826196) websocket_client: Started
|
||||
I (76826294) esp-x509-crt-bundle: Certificate validated
|
||||
I (76827230) websocket: WEBSOCKET_EVENT_CONNECTED
|
||||
I (76827239) websocket: WEBSOCKET_EVENT_DATA
|
||||
I (76827239) websocket: Received opcode=1
|
||||
W (76827239) websocket: Received=Request served by 4d896d95b55478
|
||||
W (76827239) websocket: Total payload length=32, data_len=32, current payload offset=0
|
||||
|
||||
I (166539) websocket: Sending fragmented message
|
||||
I (76828198) websocket: Sending hello 0000
|
||||
I (76828827) websocket: WEBSOCKET_EVENT_DATA
|
||||
I (76828827) websocket: Received opcode=1
|
||||
W (76828827) websocket: Received=hello 0000
|
||||
W (76828827) websocket: Total payload length=10, data_len=10, current payload offset=0
|
||||
|
||||
I (76829207) websocket: Sending fragmented text message
|
||||
```
|
||||
|
||||
## Coverage Reporting
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
idf_component_register(SRCS "websocket_linux.c"
|
||||
REQUIRES esp_websocket_client protocol_examples_common)
|
||||
REQUIRES esp_websocket_client protocol_examples_common esp_netif)
|
||||
|
||||
if(CONFIG_GCOV_ENABLED)
|
||||
target_compile_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
|
||||
|
||||
@@ -8,8 +8,27 @@ menu "Host-test config"
|
||||
|
||||
config WEBSOCKET_URI
|
||||
string "Websocket endpoint URI"
|
||||
default "ws://echo.websocket.events"
|
||||
default "wss://echo.websocket.org"
|
||||
help
|
||||
URL of websocket endpoint this example connects to and sends echo
|
||||
|
||||
config WS_OVER_TLS_SERVER_AUTH
|
||||
bool "Enable WebSocket over TLS with Server Certificate Verification Only"
|
||||
default y
|
||||
help
|
||||
Enables WebSocket connections over TLS (WSS) with server certificate verification.
|
||||
The client verifies the server certificate; the server does not require a client certificate.
|
||||
|
||||
config WS_OVER_TLS_MUTUAL_AUTH
|
||||
bool "Enable WebSocket over TLS with Server Client Mutual Authentification"
|
||||
default n
|
||||
help
|
||||
Enables WebSocket connections over TLS (WSS) with server and client mutual certificate verification.
|
||||
|
||||
config WS_OVER_TLS_SKIP_COMMON_NAME_CHECK
|
||||
bool "Skip common name(CN) check during TLS authentification"
|
||||
default n
|
||||
help
|
||||
Skip Common Name (CN) check during TLS (WSS) authentication. Use only for testing.
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <esp_log.h>
|
||||
#include "nvs_flash.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "esp_websocket_client.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_crt_bundle.h"
|
||||
|
||||
static const char *TAG = "websocket";
|
||||
|
||||
@@ -28,6 +28,11 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i
|
||||
case WEBSOCKET_EVENT_BEGIN:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_BEGIN");
|
||||
break;
|
||||
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
|
||||
case WEBSOCKET_EVENT_HEADER_RECEIVED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_HEADER_RECEIVED: %.*s", data->data_len, data->data_ptr);
|
||||
break;
|
||||
#endif
|
||||
case WEBSOCKET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
|
||||
break;
|
||||
@@ -75,6 +80,33 @@ static void websocket_app_start(void)
|
||||
|
||||
websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
|
||||
|
||||
#if CONFIG_WS_OVER_TLS_MUTUAL_AUTH
|
||||
/* Configuring client certificates for mutual authentification */
|
||||
extern const char cacert_start[] asm("_binary_ca_cert_pem_start");
|
||||
extern const char cert_start[] asm("_binary_client_cert_pem_start");
|
||||
extern const char cert_end[] asm("_binary_client_cert_pem_end");
|
||||
extern const char key_start[] asm("_binary_client_key_pem_start");
|
||||
extern const char key_end[] asm("_binary_client_key_pem_end");
|
||||
|
||||
websocket_cfg.cert_pem = cacert_start;
|
||||
websocket_cfg.client_cert = cert_start;
|
||||
websocket_cfg.client_cert_len = cert_end - cert_start;
|
||||
websocket_cfg.client_key = key_start;
|
||||
websocket_cfg.client_key_len = key_end - key_start;
|
||||
#elif CONFIG_WS_OVER_TLS_SERVER_AUTH
|
||||
// Using certificate bundle as default server certificate source
|
||||
websocket_cfg.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
// If using a custom certificate it could be added to certificate bundle,
|
||||
// added to the build similar to client certificates in this examples,
|
||||
// or read from NVS.
|
||||
/* extern const char cacert_start[] asm("ADDED_CERTIFICATE"); */
|
||||
/* websocket_cfg.cert_pem = cacert_start; */
|
||||
#endif
|
||||
|
||||
#if CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK
|
||||
websocket_cfg.skip_cert_common_name_check = true;
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
|
||||
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
@@ -127,7 +159,6 @@ int main(void)
|
||||
esp_log_level_set("transport_ws", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("trans_tcp", ESP_LOG_DEBUG);
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_IDF_TARGET_LINUX=y
|
||||
CONFIG_ESP_EVENT_POST_FROM_ISR=n
|
||||
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
|
||||
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
|
||||
CONFIG_WEBSOCKET_URI="wss://echo.websocket.org"
|
||||
|
||||
@@ -2,4 +2,4 @@ CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_IDF_TARGET_LINUX=y
|
||||
CONFIG_ESP_EVENT_POST_FROM_ISR=n
|
||||
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
|
||||
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
|
||||
CONFIG_WEBSOCKET_URI="wss://echo.websocket.org"
|
||||
|
||||
@@ -2,4 +2,4 @@ CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_IDF_TARGET_LINUX=y
|
||||
CONFIG_ESP_EVENT_POST_FROM_ISR=n
|
||||
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
|
||||
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
|
||||
CONFIG_WEBSOCKET_URI="wss://echo.websocket.org"
|
||||
|
||||
@@ -1,6 +1,42 @@
|
||||
# Websocket Sample application
|
||||
|
||||
This example will shows how to set up and communicate over a websocket.
|
||||
This example shows how to set up and communicate over a websocket.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Hardware Required](#hardware-required)
|
||||
- [Configure the project](#configure-the-project)
|
||||
- [Pre-configured SDK Configurations](#pre-configured-sdk-configurations)
|
||||
- [Server Certificate Verification](#server-certificate-verification)
|
||||
- [Generating Self-signed Certificates](#generating-a-self-signed-certificates-with-openssl)
|
||||
- [Build and Flash](#build-and-flash)
|
||||
- [Testing with pytest](#testing-with-pytest)
|
||||
- [Example Output](#example-output)
|
||||
- [WebSocket Test Server](#websocket-test-server)
|
||||
- [Python Flask Echo Server](#alternative-python-flask-echo-server)
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Install dependencies:**
|
||||
```bash
|
||||
pip install -r esp-protocols/ci/requirements.txt
|
||||
```
|
||||
|
||||
2. **Configure and build:**
|
||||
```bash
|
||||
idf.py menuconfig # Configure WiFi/Ethernet and WebSocket URI
|
||||
idf.py build
|
||||
```
|
||||
|
||||
3. **Flash and monitor:**
|
||||
```bash
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
4. **Run tests:**
|
||||
```bash
|
||||
pytest .
|
||||
```
|
||||
|
||||
## How to Use Example
|
||||
|
||||
@@ -15,6 +51,20 @@ This example can be executed on any ESP32 board, the only required interface is
|
||||
* Configure the websocket endpoint URI under "Example Configuration", if "WEBSOCKET_URI_FROM_STDIN" is selected then the example application will connect to the URI it reads from stdin (used for testing)
|
||||
* To test a WebSocket client example over TLS, please enable one of the following configurations: `CONFIG_WS_OVER_TLS_MUTUAL_AUTH` or `CONFIG_WS_OVER_TLS_SERVER_AUTH`. See the sections below for more details.
|
||||
|
||||
### Pre-configured SDK Configurations
|
||||
|
||||
This example includes several pre-configured `sdkconfig.ci.*` files for different testing scenarios:
|
||||
|
||||
* **sdkconfig.ci** - Default configuration with WebSocket over Ethernet (IP101 PHY, ESP32, IPv6) and hardcoded URI.
|
||||
* **sdkconfig.ci.plain_tcp** - WebSocket over plain TCP (no TLS, URI from stdin) using Ethernet (IP101 PHY, ESP32, IPv6).
|
||||
* **sdkconfig.ci.mutual_auth** - WebSocket with mutual TLS authentication (client/server certificate verification, skips CN check) and URI from stdin.
|
||||
* **sdkconfig.ci.dynamic_buffer** - WebSocket with dynamic buffer allocation, Ethernet (IP101 PHY, ESP32, IPv6), and hardcoded URI.
|
||||
|
||||
Example:
|
||||
```
|
||||
idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.ci.plain_tcp" build
|
||||
```
|
||||
|
||||
### Server Certificate Verification
|
||||
|
||||
* Mutual Authentication: When `CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y` is enabled, it's essential to provide valid certificates for both the server and client.
|
||||
@@ -26,7 +76,7 @@ This example can be executed on any ESP32 board, the only required interface is
|
||||
Please note: This example represents an extremely simplified approach to generating self-signed certificates/keys with a single common CA, devoid of CN checks, lacking password protection, and featuring hardcoded key sizes and types. It is intended solely for testing purposes.
|
||||
In the outlined steps, we are omitting the configuration of the CN (Common Name) field due to the context of a testing environment. However, it's important to recognize that the CN field is a critical element of SSL/TLS certificates, significantly influencing the security and efficacy of HTTPS communications. This field facilitates the verification of a website's identity, enhancing trust and security in web interactions. In practical deployments beyond testing scenarios, ensuring the CN field is accurately set is paramount for maintaining the integrity and reliability of secure communications
|
||||
|
||||
### Generating a self signed Certificates with OpenSSL
|
||||
### Generating a self signed Certificates with OpenSSL manually
|
||||
* The example below outlines the process for creating new certificates for both the server and client using OpenSSL, a widely-used command line tool for implementing TLS protocol:
|
||||
|
||||
```
|
||||
@@ -63,6 +113,46 @@ Please see the openssl man pages (man openssl) for more details.
|
||||
It is **strongly recommended** to not reuse the example certificate in your application;
|
||||
it is included only for demonstration.
|
||||
|
||||
### Certificate Generation Options
|
||||
|
||||
#### Option 1: Manual OpenSSL Commands
|
||||
Follow the step-by-step process in the section above to understand certificate generation.
|
||||
|
||||
#### Option 2: Automated Script
|
||||
**Note:** Test certificates are already available in the example. If you want to regenerate them or create new ones, use the provided `generate_certs.sh` script:
|
||||
|
||||
```bash
|
||||
# Auto-detect local IP address (recommended for network testing)
|
||||
./generate_certs.sh
|
||||
|
||||
# Specify custom hostname or IP address
|
||||
./generate_certs.sh 192.168.1.100
|
||||
|
||||
# Use localhost (for local-only testing)
|
||||
./generate_certs.sh localhost
|
||||
```
|
||||
|
||||
This script automatically generates all required certificates in the correct directories and cleans up temporary files.
|
||||
|
||||
**Important:** The server certificate's Common Name (CN) must match the hostname or IP address that ESP32 clients use to connect. If not specified, the script attempts to auto-detect your local IP address. Certificate verification will fail if there's a mismatch between the CN and the actual connection address.
|
||||
|
||||
**CN Mismatch Handling:**
|
||||
If you encounter certificate verification failures due to CN mismatch, you have two options:
|
||||
|
||||
1. **Recommended (Secure):** Regenerate certificates with the correct CN:
|
||||
```bash
|
||||
./generate_certs.sh <actual_hostname_or_ip>
|
||||
```
|
||||
|
||||
2. **Testing Only (Less Secure):** Skip CN verification by enabling `CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK=y` in `idf.py menuconfig`.
|
||||
|
||||
⚠️ **WARNING:** This option disables an important security check and should **NEVER** be used in production environments. It makes your application vulnerable to man-in-the-middle attacks.
|
||||
|
||||
#### Option 3: Online Certificate Generators
|
||||
- **mkcert**: `install mkcert` then `mkcert -install` and `mkcert localhost`
|
||||
- **Let's Encrypt**: For production certificates (free, automated renewal)
|
||||
- **Online generators**: Search for "self-signed certificate generator" online
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
@@ -73,7 +163,36 @@ idf.py -p PORT flash monitor
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
See the [ESP-IDF Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Testing with pytest
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
Before running the pytest tests, you need to install the required Python packages:
|
||||
|
||||
```
|
||||
pip install -r esp-protocols/ci/requirements.txt
|
||||
```
|
||||
|
||||
### Run pytest
|
||||
|
||||
After installing the dependencies, you can run the pytest tests:
|
||||
|
||||
Run all tests in current directory:
|
||||
```
|
||||
pytest .
|
||||
```
|
||||
|
||||
Run specific test file:
|
||||
```
|
||||
pytest pytest_websocket.py
|
||||
```
|
||||
|
||||
To specify the target device or serial port, use:
|
||||
```
|
||||
pytest --target esp32 --port /dev/ttyUSB0
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
@@ -84,7 +203,7 @@ I (4472) tcpip_adapter: eth ip: 192.168.2.137, mask: 255.255.255.0, gw: 192.168.
|
||||
I (4472) example_connect: Connected to Ethernet
|
||||
I (4472) example_connect: IPv4 address: 192.168.2.137
|
||||
I (4472) example_connect: IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed4:a92b
|
||||
I (4482) WEBSOCKET: Connecting to ws://echo.websocket.events...
|
||||
I (4482) WEBSOCKET: Connecting to wss://echo.websocket.org...
|
||||
I (5012) WEBSOCKET: WEBSOCKET_EVENT_CONNECTED
|
||||
I (5492) WEBSOCKET: Sending hello 0000
|
||||
I (6052) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
@@ -105,9 +224,101 @@ W (9162) WEBSOCKET: Received=hello 0003
|
||||
```
|
||||
|
||||
|
||||
## Python Flask echo server
|
||||
## WebSocket Test Server
|
||||
|
||||
By default, the `ws://echo.websocket.events` endpoint is used. You can setup a Python websocket echo server locally and try the `ws://<your-ip>:5000` endpoint. To do this, install Flask-sock Python package
|
||||
### Standalone Test Server
|
||||
|
||||
This example includes a standalone WebSocket test server (`websocket_server.py`) that can be used for testing your ESP32 WebSocket client:
|
||||
|
||||
#### Quick Start
|
||||
|
||||
**Plain WebSocket (No TLS):**
|
||||
```bash
|
||||
# Plain WebSocket server (no encryption)
|
||||
python websocket_server.py
|
||||
|
||||
# Custom port
|
||||
python websocket_server.py --port 9000
|
||||
```
|
||||
|
||||
**Server-Only Authentication:**
|
||||
```bash
|
||||
# TLS WebSocket server (ESP32 verifies server)
|
||||
python websocket_server.py --tls
|
||||
|
||||
# Custom port with TLS
|
||||
python websocket_server.py --port 9000 --tls
|
||||
```
|
||||
|
||||
**Mutual Authentication:**
|
||||
```bash
|
||||
# TLS with client certificate verification (both verify each other)
|
||||
python websocket_server.py --tls --client-verify
|
||||
|
||||
# Custom port with mutual authentication
|
||||
python websocket_server.py --port 9000 --tls --client-verify
|
||||
```
|
||||
|
||||
#### Server Features
|
||||
- **Echo functionality** - Automatically echoes back received messages
|
||||
- **TLS support** - Secure WebSocket (WSS) connections
|
||||
- **Client certificate verification** - Mutual authentication support
|
||||
- **Binary and text messages** - Handles both data types
|
||||
- **Auto IP detection** - Shows connection URL with your local IP
|
||||
|
||||
#### Verification Modes
|
||||
|
||||
**Plain WebSocket (No TLS):**
|
||||
- No certificate verification on either side
|
||||
- Use for local testing or trusted networks
|
||||
- Configuration: `CONFIG_WS_OVER_TLS_MUTUAL_AUTH=n` and `CONFIG_WS_OVER_TLS_SERVER_AUTH=n`
|
||||
|
||||
**Server-Only Authentication (`--tls` without `--client-verify`):**
|
||||
- ESP32 verifies the server's certificate
|
||||
- Server does NOT verify the ESP32's certificate
|
||||
- Use when you trust the client but want to verify the server
|
||||
- Configuration: `CONFIG_WS_OVER_TLS_SERVER_AUTH=y`
|
||||
|
||||
**Mutual Authentication (`--tls --client-verify`):**
|
||||
- ESP32 verifies the server's certificate
|
||||
- Server verifies the ESP32's certificate
|
||||
- Use when both parties need to authenticate each other
|
||||
- Configuration: `CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y`
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
**Plain WebSocket (No TLS or Client Verification):**
|
||||
```bash
|
||||
# Basic server (port 8080)
|
||||
python websocket_server.py
|
||||
|
||||
# Custom port
|
||||
python websocket_server.py --port 9000
|
||||
```
|
||||
|
||||
**Server-Only Authentication (ESP32 verifies server, server doesn't verify ESP32):**
|
||||
```bash
|
||||
# TLS server without client verification
|
||||
python websocket_server.py --tls
|
||||
|
||||
# Custom port with TLS
|
||||
python websocket_server.py --port 9000 --tls
|
||||
```
|
||||
|
||||
**Mutual Authentication (Both ESP32 and server verify each other's certificates):**
|
||||
```bash
|
||||
# TLS server with client certificate verification
|
||||
python websocket_server.py --tls --client-verify
|
||||
|
||||
# Custom port with mutual authentication
|
||||
python websocket_server.py --port 9000 --tls --client-verify
|
||||
```
|
||||
|
||||
The server will display the connection URL (e.g., `wss://192.168.1.100:8080`) that you can use in your ESP32 configuration.
|
||||
|
||||
### Alternative: Python Flask Echo Server
|
||||
|
||||
By default, the `wss://echo.websocket.org` endpoint is used. You can also setup a Python Flask websocket echo server locally and try the `ws://<your-ip>:5000` endpoint. To do this, install Flask-sock Python package
|
||||
|
||||
```
|
||||
pip install flask-sock
|
||||
@@ -135,3 +346,36 @@ if __name__ == '__main__':
|
||||
# gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app
|
||||
app.run(host="0.0.0.0", debug=True)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Connection failed:**
|
||||
- Verify WiFi/Ethernet configuration in `idf.py menuconfig`
|
||||
- Check if the WebSocket server is running and accessible
|
||||
- Ensure the URI is correct (use `wss://` for TLS, `ws://` for plain TCP)
|
||||
|
||||
**TLS certificate errors:**
|
||||
- **Certificate verification failed:** The most common cause is CN mismatch. Ensure the server certificate's Common Name matches the hostname/IP you're connecting to:
|
||||
- Check your connection URI (e.g., if connecting to `wss://192.168.1.100:8080`, the certificate CN must be `192.168.1.100`)
|
||||
- Regenerate certificates with the correct CN: `./generate_certs.sh <your_hostname_or_ip>`
|
||||
- For testing only, you can bypass CN check with `CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK=y` (NOT recommended for production)
|
||||
- Verify certificate files are properly formatted and accessible
|
||||
- Ensure the CA certificate used to sign the server certificate is loaded on the ESP32
|
||||
|
||||
**Build errors:**
|
||||
- Clean build: `idf.py fullclean`
|
||||
- Check ESP-IDF version compatibility
|
||||
- Verify all dependencies are installed
|
||||
|
||||
**Test failures:**
|
||||
- Ensure the device is connected and accessible via the specified port
|
||||
- Check that the target device matches the configuration (`--target esp32`)
|
||||
- Verify pytest dependencies are installed correctly
|
||||
|
||||
### Getting Help
|
||||
|
||||
- Check the [ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/)
|
||||
- Review the [WebSocket client component documentation](../../README.md)
|
||||
- Report issues on the [ESP Protocols repository](https://github.com/espressif/esp-protocols)
|
||||
|
||||
85
components/esp_websocket_client/examples/target/generate_certs.sh
Executable file
85
components/esp_websocket_client/examples/target/generate_certs.sh
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
# Generate CA, Server, and Client certificates automatically
|
||||
#
|
||||
# Usage: ./generate_certs.sh [SERVER_CN]
|
||||
# SERVER_CN: The Common Name (hostname or IP) for the server certificate.
|
||||
# This should match the hostname/IP that ESP32 clients will use to connect.
|
||||
# If not provided, the script will attempt to auto-detect the local IP address.
|
||||
# Falls back to "localhost" if auto-detection fails.
|
||||
#
|
||||
# IMPORTANT: The server certificate's Common Name (CN) must match the hostname or IP address
|
||||
# that ESP32 clients use to connect. If there's a mismatch, certificate verification will fail
|
||||
# during the TLS handshake. For production use, always specify the correct hostname/IP.
|
||||
|
||||
# Get server hostname/IP from command line argument or auto-detect
|
||||
if [ -n "$1" ]; then
|
||||
SERVER_CN="$1"
|
||||
echo "Using provided SERVER_CN: $SERVER_CN"
|
||||
else
|
||||
# Attempt to auto-detect local IP address
|
||||
# Try multiple methods for better compatibility across different systems
|
||||
if command -v hostname >/dev/null 2>&1; then
|
||||
# Try to get IP from hostname command (works on most Unix systems)
|
||||
SERVER_CN=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
fi
|
||||
|
||||
# If the above failed, try ifconfig (macOS and some Linux systems)
|
||||
if [ -z "$SERVER_CN" ] && command -v ifconfig >/dev/null 2>&1; then
|
||||
SERVER_CN=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | head -n1)
|
||||
fi
|
||||
|
||||
# If still empty, try ip command (modern Linux systems)
|
||||
if [ -z "$SERVER_CN" ] && command -v ip >/dev/null 2>&1; then
|
||||
SERVER_CN=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127.0.0.1 | head -n1)
|
||||
fi
|
||||
|
||||
# Fall back to localhost if auto-detection failed
|
||||
if [ -z "$SERVER_CN" ]; then
|
||||
SERVER_CN="localhost"
|
||||
echo "Warning: Could not auto-detect IP address. Using 'localhost' as SERVER_CN."
|
||||
echo " If your server runs on a different machine or IP, re-run with: ./generate_certs.sh <hostname_or_ip>"
|
||||
else
|
||||
echo "Auto-detected SERVER_CN: $SERVER_CN"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Note: ESP32 clients must connect using: $SERVER_CN"
|
||||
echo ""
|
||||
|
||||
# Create directories if they don't exist
|
||||
mkdir -p main/certs/server
|
||||
|
||||
echo "Generating CA certificate..."
|
||||
openssl genrsa -out main/certs/ca_key.pem 2048
|
||||
openssl req -new -x509 -days 3650 -key main/certs/ca_key.pem -out main/certs/ca_cert.pem -subj "/C=US/ST=State/L=City/O=Organization/CN=TestCA"
|
||||
|
||||
echo "Generating Server certificate with CN=$SERVER_CN..."
|
||||
openssl genrsa -out main/certs/server/server_key.pem 2048
|
||||
openssl req -new -key main/certs/server/server_key.pem -out server_csr.pem -subj "/C=US/ST=State/L=City/O=Organization/CN=$SERVER_CN"
|
||||
openssl x509 -req -days 3650 -in server_csr.pem -CA main/certs/ca_cert.pem -CAkey main/certs/ca_key.pem -CAcreateserial -out main/certs/server/server_cert.pem
|
||||
|
||||
echo "Generating Client certificate..."
|
||||
openssl genrsa -out main/certs/client_key.pem 2048
|
||||
openssl req -new -key main/certs/client_key.pem -out client_csr.pem -subj "/C=US/ST=State/L=City/O=Organization/CN=TestClient"
|
||||
openssl x509 -req -days 3650 -in client_csr.pem -CA main/certs/ca_cert.pem -CAkey main/certs/ca_key.pem -CAcreateserial -out main/certs/client_cert.pem
|
||||
|
||||
# Clean up CSR files
|
||||
rm server_csr.pem client_csr.pem
|
||||
|
||||
echo "Certificates generated successfully!"
|
||||
echo ""
|
||||
echo "Generated files:"
|
||||
echo " - main/certs/ca_cert.pem (CA certificate)"
|
||||
echo " - main/certs/ca_key.pem (CA private key)"
|
||||
echo " - main/certs/client_cert.pem (Client certificate)"
|
||||
echo " - main/certs/client_key.pem (Client private key)"
|
||||
echo " - main/certs/server/server_cert.pem (Server certificate with CN=$SERVER_CN)"
|
||||
echo " - main/certs/server/server_key.pem (Server private key)"
|
||||
echo ""
|
||||
echo "IMPORTANT: Configure ESP32 clients to connect to: $SERVER_CN"
|
||||
echo " The server certificate is valid for this hostname/IP only."
|
||||
echo ""
|
||||
echo "Note: If the CN doesn't match your connection hostname/IP, you have two options:"
|
||||
echo " 1. Regenerate certificates with correct CN: ./generate_certs.sh <correct_hostname_or_ip>"
|
||||
echo " 2. Skip CN verification (TESTING ONLY): Enable CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK=y"
|
||||
echo " WARNING: Option 2 reduces security and should NOT be used in production!"
|
||||
@@ -16,7 +16,7 @@ menu "Example Configuration"
|
||||
config WEBSOCKET_URI
|
||||
string "Websocket endpoint URI"
|
||||
depends on WEBSOCKET_URI_FROM_STRING
|
||||
default "wss://echo.websocket.events"
|
||||
default "wss://echo.websocket.org"
|
||||
help
|
||||
URL of websocket endpoint this example connects to and sends echo
|
||||
|
||||
|
||||
@@ -4,5 +4,7 @@ dependencies:
|
||||
espressif/esp_websocket_client:
|
||||
version: "^1.0.0"
|
||||
override_path: "../../../"
|
||||
espressif/cjson:
|
||||
version: "^1.7.15"
|
||||
protocol_examples_common:
|
||||
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@@ -77,6 +77,11 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i
|
||||
case WEBSOCKET_EVENT_BEGIN:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_BEGIN");
|
||||
break;
|
||||
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
|
||||
case WEBSOCKET_EVENT_HEADER_RECEIVED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_HEADER_RECEIVED: %.*s", data->data_len, data->data_ptr);
|
||||
break;
|
||||
#endif
|
||||
case WEBSOCKET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
|
||||
break;
|
||||
@@ -142,7 +147,11 @@ static void websocket_app_start(void)
|
||||
#if CONFIG_WEBSOCKET_URI_FROM_STDIN
|
||||
char line[128];
|
||||
|
||||
ESP_LOGI(TAG, "Please enter uri of websocket endpoint");
|
||||
ESP_LOGI(TAG, "Please enter WebSocket endpoint URI");
|
||||
ESP_LOGI(TAG, "Examples:");
|
||||
ESP_LOGI(TAG, " ws://192.168.1.100:8080 (plain WebSocket)");
|
||||
ESP_LOGI(TAG, " wss://192.168.1.100:8080 (secure WebSocket)");
|
||||
ESP_LOGI(TAG, " wss://echo.websocket.org (public test server)");
|
||||
get_string(line, sizeof(line));
|
||||
|
||||
websocket_cfg.uri = line;
|
||||
|
||||
@@ -1,84 +1,12 @@
|
||||
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import socket
|
||||
import ssl
|
||||
import string
|
||||
import sys
|
||||
from threading import Event, Thread
|
||||
|
||||
from SimpleWebSocketServer import (SimpleSSLWebSocketServer,
|
||||
SimpleWebSocketServer, WebSocket)
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(('8.8.8.8', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
IP = '127.0.0.1'
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
|
||||
class WebsocketTestEcho(WebSocket):
|
||||
def handleMessage(self):
|
||||
if isinstance(self.data, bytes):
|
||||
print(f'\n Server received binary data: {self.data.hex()}\n')
|
||||
self.sendMessage(self.data, binary=True)
|
||||
else:
|
||||
print(f'\n Server received: {self.data}\n')
|
||||
self.sendMessage(self.data)
|
||||
|
||||
def handleConnected(self):
|
||||
print('Connection from: {}'.format(self.address))
|
||||
|
||||
def handleClose(self):
|
||||
print('{} closed the connection'.format(self.address))
|
||||
|
||||
|
||||
# Simple Websocket server for testing purposes
|
||||
class Websocket(object):
|
||||
|
||||
def send_data(self, data):
|
||||
for nr, conn in self.server.connections.items():
|
||||
conn.sendMessage(data)
|
||||
|
||||
def run(self):
|
||||
if self.use_tls is True:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.load_cert_chain(certfile='main/certs/server/server_cert.pem', keyfile='main/certs/server/server_key.pem')
|
||||
if self.client_verify is True:
|
||||
ssl_context.load_verify_locations(cafile='main/certs/ca_cert.pem')
|
||||
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.check_hostname = False
|
||||
self.server = SimpleSSLWebSocketServer('', self.port, WebsocketTestEcho, ssl_context=ssl_context)
|
||||
else:
|
||||
self.server = SimpleWebSocketServer('', self.port, WebsocketTestEcho)
|
||||
while not self.exit_event.is_set():
|
||||
self.server.serveonce()
|
||||
|
||||
def __init__(self, port, use_tls, verify):
|
||||
self.port = port
|
||||
self.use_tls = use_tls
|
||||
self.client_verify = verify
|
||||
self.exit_event = Event()
|
||||
self.thread = Thread(target=self.run)
|
||||
self.thread.start()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.exit_event.set()
|
||||
self.thread.join(10)
|
||||
if self.thread.is_alive():
|
||||
print('Thread cannot be joined', 'orange')
|
||||
from websocket_server import WebsocketServer, get_my_ip
|
||||
|
||||
|
||||
def test_examples_protocol_websocket(dut):
|
||||
@@ -228,13 +156,13 @@ def test_examples_protocol_websocket(dut):
|
||||
|
||||
if uri_from_stdin:
|
||||
server_port = 8080
|
||||
with Websocket(server_port, use_tls, client_verify) as ws:
|
||||
with WebsocketServer(server_port, use_tls, client_verify) as ws:
|
||||
if use_tls is True:
|
||||
uri = 'wss://{}:{}'.format(get_my_ip(), server_port)
|
||||
else:
|
||||
uri = 'ws://{}:{}'.format(get_my_ip(), server_port)
|
||||
print('DUT connecting to {}'.format(uri))
|
||||
dut.expect('Please enter uri of websocket endpoint', timeout=30)
|
||||
dut.expect("Please enter WebSocket endpoint URI", timeout=30)
|
||||
dut.write(uri)
|
||||
test_echo(dut)
|
||||
test_recv_long_msg(dut, ws, 2000, 3)
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import socket
|
||||
import ssl
|
||||
from threading import Event, Thread
|
||||
|
||||
from SimpleWebSocketServer import (SimpleSSLWebSocketServer,
|
||||
SimpleWebSocketServer, WebSocket)
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
"""Get the local IP address of this machine."""
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(('8.8.8.8', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
IP = '127.0.0.1'
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
|
||||
class WebsocketTestEcho(WebSocket):
|
||||
"""WebSocket handler that echoes back received messages."""
|
||||
|
||||
def handleMessage(self):
|
||||
if isinstance(self.data, bytes):
|
||||
print(f'\n Server received binary data: {self.data.hex()}\n')
|
||||
self.sendMessage(self.data, binary=True)
|
||||
else:
|
||||
print(f'\n Server received: {self.data}\n')
|
||||
self.sendMessage(self.data)
|
||||
|
||||
def handleConnected(self):
|
||||
print('Connection from: {}'.format(self.address))
|
||||
|
||||
def handleClose(self):
|
||||
print('{} closed the connection'.format(self.address))
|
||||
|
||||
|
||||
class WebsocketServer:
|
||||
"""WebSocket server for testing purposes."""
|
||||
|
||||
def __init__(self, port, use_tls=False, client_verify=False):
|
||||
self.port = port
|
||||
self.use_tls = use_tls
|
||||
self.client_verify = client_verify
|
||||
self.exit_event = Event()
|
||||
self.thread = None
|
||||
self.server = None
|
||||
|
||||
def send_data(self, data):
|
||||
"""Send data to all connected clients."""
|
||||
if self.server and hasattr(self.server, 'connections'):
|
||||
for nr, conn in self.server.connections.items():
|
||||
conn.sendMessage(data)
|
||||
|
||||
def run(self):
|
||||
"""Run the WebSocket server."""
|
||||
if self.use_tls:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.load_cert_chain(
|
||||
certfile='main/certs/server/server_cert.pem',
|
||||
keyfile='main/certs/server/server_key.pem'
|
||||
)
|
||||
if self.client_verify:
|
||||
ssl_context.load_verify_locations(cafile='main/certs/ca_cert.pem')
|
||||
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.check_hostname = False
|
||||
self.server = SimpleSSLWebSocketServer('', self.port, WebsocketTestEcho, ssl_context=ssl_context)
|
||||
else:
|
||||
self.server = SimpleWebSocketServer('', self.port, WebsocketTestEcho)
|
||||
|
||||
print(f"WebSocket server starting on port {self.port} (TLS: {self.use_tls}, Client verify: {self.client_verify})")
|
||||
|
||||
while not self.exit_event.is_set():
|
||||
self.server.serveonce()
|
||||
|
||||
def start(self):
|
||||
"""Start the server in a separate thread."""
|
||||
self.thread = Thread(target=self.run)
|
||||
self.thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the server."""
|
||||
self.exit_event.set()
|
||||
if self.thread:
|
||||
self.thread.join(10)
|
||||
if self.thread.is_alive():
|
||||
print('Thread cannot be joined', 'orange')
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry."""
|
||||
self.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
"""Context manager exit."""
|
||||
self.stop()
|
||||
|
||||
|
||||
def create_websocket_server(port, use_tls=False, client_verify=False):
|
||||
"""Factory function to create a WebSocket server."""
|
||||
return WebsocketServer(port, use_tls, client_verify)
|
||||
|
||||
|
||||
def run_forever(port=8080, use_tls=False, client_verify=False):
|
||||
"""Run WebSocket server forever (for standalone use)."""
|
||||
print(f"Starting WebSocket server on port {port}")
|
||||
print(f"TLS enabled: {use_tls}")
|
||||
print(f"Client verification: {client_verify}")
|
||||
print(f"Server IP: {get_my_ip()}")
|
||||
print(f"Connect with-->{'wss' if use_tls else 'ws'}://{get_my_ip()}:{port}")
|
||||
print("Press Ctrl+C to stop the server")
|
||||
|
||||
server = WebsocketServer(port, use_tls, client_verify)
|
||||
|
||||
try:
|
||||
server.start()
|
||||
# Wait for the server thread to complete or be interrupted
|
||||
server.thread.join()
|
||||
except KeyboardInterrupt:
|
||||
print("\nServer stopped by user")
|
||||
except Exception as e:
|
||||
print(f"Server error: {e}")
|
||||
finally:
|
||||
server.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="WebSocket Test Server")
|
||||
parser.add_argument("--port", type=int, default=8080, help="Server port (default: 8080)")
|
||||
parser.add_argument("--tls", action="store_true", help="Enable TLS/WSS")
|
||||
parser.add_argument("--client-verify", action="store_true", help="Require client certificate verification")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Usage examples:
|
||||
# python3 websocket_server.py # Plain WebSocket on port 8080
|
||||
# python3 websocket_server.py --tls # TLS WebSocket on port 8080
|
||||
# python3 websocket_server.py --tls --client-verify # TLS with client cert verification
|
||||
# python3 websocket_server.py --port 9000 --tls # Custom port with TLS
|
||||
|
||||
run_forever(port=args.port, use_tls=args.tls, client_verify=args.client_verify)
|
||||
@@ -1,4 +1,4 @@
|
||||
version: "1.5.0"
|
||||
version: "1.6.0"
|
||||
description: WebSocket protocol client for ESP-IDF
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_websocket_client
|
||||
dependencies:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_idf_version.h"
|
||||
#include <sys/socket.h>
|
||||
#include "esp_transport_ws.h"
|
||||
|
||||
@@ -21,6 +22,13 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
// Features supported in 6.0.0
|
||||
#define WS_TRANSPORT_HEADER_CALLBACK_SUPPORT 1
|
||||
#else
|
||||
#define WS_TRANSPORT_HEADER_CALLBACK_SUPPORT 0
|
||||
#endif
|
||||
|
||||
typedef struct esp_websocket_client *esp_websocket_client_handle_t;
|
||||
|
||||
ESP_EVENT_DECLARE_BASE(WEBSOCKET_EVENTS); // declaration of the task events family
|
||||
@@ -31,6 +39,9 @@ ESP_EVENT_DECLARE_BASE(WEBSOCKET_EVENTS); // declaration of the task eve
|
||||
typedef enum {
|
||||
WEBSOCKET_EVENT_ANY = -1,
|
||||
WEBSOCKET_EVENT_ERROR = 0, /*!< This event occurs when there are any errors during execution */
|
||||
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
|
||||
WEBSOCKET_EVENT_HEADER_RECEIVED,/*!< This event occurs for each pre-upgrade HTTP header */
|
||||
#endif
|
||||
WEBSOCKET_EVENT_CONNECTED, /*!< Once the Websocket has been connected to the server, no data exchange has been performed */
|
||||
WEBSOCKET_EVENT_DISCONNECTED, /*!< The connection has been disconnected */
|
||||
WEBSOCKET_EVENT_DATA, /*!< When receiving data from the server, possibly multiple portions of the packet */
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS ../../esp_websocket_client
|
||||
"$ENV{IDF_PATH}/tools/unit-test-app/components")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0")
|
||||
set(test_component_dir $ENV{IDF_PATH}/tools/test_apps/components)
|
||||
else()
|
||||
set(test_component_dir $ENV{IDF_PATH}/tools/unit-test-app/components)
|
||||
endif()
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS ../../esp_websocket_client
|
||||
${test_component_dir})
|
||||
|
||||
project(websocket_unit_test)
|
||||
|
||||
@@ -52,14 +52,14 @@ I (18208) lws-client: LWS minimal ws client echo
|
||||
216516: __lws_lc_tag: ++ [vh|0|default||-1] (1)
|
||||
I (18248) lws-client: connect_cb: connecting
|
||||
|
||||
210112: __lws_lc_tag: ++ [wsicli|0|WS/h1/default/echo.websocket.events] (1)
|
||||
204800: [wsicli|0|WS/h1/default/echo.websocket.events]: lws_client_connect_3_connect: trying 13.248.241.119
|
||||
210112: __lws_lc_tag: ++ [wsicli|0|WS/h1/default/echo.websocket.org] (1)
|
||||
204800: [wsicli|0|WS/h1/default/echo.websocket.org]: lws_client_connect_3_connect: trying 13.248.241.119
|
||||
180776: lws_ssl_client_bio_create: allowing selfsigned
|
||||
I (19998) wifi:<ba-add>idx:0 (ifx:0, b4:89:01:63:9d:08), tid:0, ssn:321, winSize:64
|
||||
I (20768) lws-client: WEBSOCKET_EVENT_CONNECTED
|
||||
I (20768) lws-client: Sending hello 0000
|
||||
I (20778) lws-client: WEBSOCKET_EVENT_DATA
|
||||
W (20778) lws-client: Received=echo.websocket.events sponsored by Lob.com
|
||||
W (20778) lws-client: Received=echo.websocket.org sponsored by Lob.com
|
||||
|
||||
|
||||
I (20968) lws-client: WEBSOCKET_EVENT_DATA
|
||||
|
||||
@@ -14,7 +14,7 @@ menu "Example Configuration"
|
||||
|
||||
config WEBSOCKET_URI
|
||||
string "Websocket endpoint URI"
|
||||
default "echo.websocket.events"
|
||||
default "echo.websocket.org"
|
||||
help
|
||||
URL or IP of websocket endpoint this example connects to and sends echo
|
||||
config WEBSOCKET_PORT
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
extern int __real_mbedtls_ssl_handshake_step(mbedtls_ssl_context *ssl);
|
||||
|
||||
int __wrap_mbedtls_ssl_handshake_step( mbedtls_ssl_context *ssl )
|
||||
int __wrap_mbedtls_ssl_handshake_step(mbedtls_ssl_context *ssl)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
|
||||
@@ -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.8.2
|
||||
version: 1.9.1
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## [1.9.1](https://github.com/espressif/esp-protocols/commits/mdns-v1.9.1)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix to use tagged AFL image + minor format fix ([2b2f009a](https://github.com/espressif/esp-protocols/commit/2b2f009a))
|
||||
- Fix unused variable `dcst` warning for wifi-remote chips ([081eef88](https://github.com/espressif/esp-protocols/commit/081eef88))
|
||||
|
||||
## [1.9.0](https://github.com/espressif/esp-protocols/commits/mdns-v1.9.0)
|
||||
|
||||
### Features
|
||||
|
||||
- support null value for boolean txt records ([fa96de3b](https://github.com/espressif/esp-protocols/commit/fa96de3b))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add test case for bool/NULL txt handling ([5068f221](https://github.com/espressif/esp-protocols/commit/5068f221))
|
||||
- Temporary fix for build issues on IDF master ([0197c994](https://github.com/espressif/esp-protocols/commit/0197c994))
|
||||
- Add tests for delegated answers ([487a746d](https://github.com/espressif/esp-protocols/commit/487a746d))
|
||||
- Add fuzzing into mdns CI ([af6bb1b5](https://github.com/espressif/esp-protocols/commit/af6bb1b5))
|
||||
- Host test to use hw_support include dir ([8bba3a97](https://github.com/espressif/esp-protocols/commit/8bba3a97))
|
||||
- Fixes case where we create our own malloc/free allocators, therefore we need to call mdns_mem_free and not free ([63bf7091](https://github.com/espressif/esp-protocols/commit/63bf7091))
|
||||
- put srv/txt records in additional section for ptr queries ([b7b8c5db](https://github.com/espressif/esp-protocols/commit/b7b8c5db))
|
||||
|
||||
### Updated
|
||||
|
||||
- ci(common): Update test component dir for IDFv6.0 ([18418c83](https://github.com/espressif/esp-protocols/commit/18418c83))
|
||||
|
||||
## [1.8.2](https://github.com/espressif/esp-protocols/commits/mdns-v1.8.2)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -12,6 +12,7 @@ CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_IP101=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_GENERIC=y
|
||||
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user