Compare commits

...

47 Commits

Author SHA1 Message Date
david-cermak
245b5a2ffb Merge pull request #956 from david-cermak/fix/modem_ping_in_examples
[modem]: Bump -> v2.0
2025-11-25 12:23:15 +01:00
David Cermak
b9ea0c31ce bump(modem): 1.4.0 -> 2.0.0
2.0.0
Breaking changes
- inc headers for AT command definitions are no longer used directly, but pregenerated into *.h(pp) (Use generated AT command definitions for IDE navigation)
Features
- Add support for multiple connection in AT based example (2826287d)
- Add enhanced URC observer API (4889dd6f)
- Support esp-modem use without PPP (858f8570, #851)
- Modem simulator based on esp-at (e5787e3d)
Bug Fixes
- Update tests and examples to use modem-v2.0 (4aa0e4ba)
- Replace MQTT client with simple ping command (0ccaf2c0)
- Replace MQTT client with simple ping command (9b2b1f68)
- Update example to use optional mqtt deps (3141d6ca)
- Minor fixed in the test code (e772ce67)
- Add missing set_echo() C wrapper (d1e67080, #926)
- Fix modem console dependencies (453be4cd)
- Address build issues (018ba58e)
- Fix driver dependency issue on v6.0 (67c682d9)
- Fix CI build issues with IDFv6.0 (15140e04)
- Add support for ESP-AT based tcp-client example (14d3cb6b)
- Use idf-build-apps for building target tests (e9d9b3a8)
- Make MQTT public broker endpoint configurable (6d541194)
- Fix URC handling in DTE data callback (93029946)
- Use another public broker for examples and tests (fac2edbe)
- Fix incompatible iterator in std::search() in new gcc (ed0f6334)
- Fix autodetect to support ACFC mode in PPP frames (8b328a69, #801)
- Fix get_network_registration_state() to accept two params (5f54d907, #826)
- Consume buffer after handled URC (6eceb28f)
- Use generated AT command definitions for IDE navigation (e2fa1110, !BREAKING)
2025-11-24 15:57:10 +01:00
David Cermak
4aa0e4ba49 fix(modem): Update tests and examples to use modem-v2.0 2025-11-24 15:57:06 +01:00
David Cermak
c48c2ebe7e fix(modem): Use current IDF releases for modem tests 2025-11-24 15:23:17 +01:00
David Cermak
0ccaf2c0bb fix(modem): Replace MQTT client with simple ping command 2025-11-24 14:43:08 +01:00
david-cermak
ed569d8509 Merge pull request #954 from david-cermak/fix/mosq_mqtt_deps
[mosq]: Add optional mqtt deps to examples
2025-11-21 13:28:33 +01:00
David Cermak
9b2b1f680d fix(modem): Replace MQTT client with simple ping command 2025-11-21 12:37:09 +01:00
david-cermak
370dfecc15 Merge pull request #933 from david-cermak/fix/modem_set_echo
fix(modem): Add missing set_echo() C wrapper
2025-11-20 13:12:17 +01:00
David Cermak
e50c5eb40e fix(mosq): Update IDF version matrix in CI
publish-connect test moved from IDF -> esp-mqtt in v6.0
serverless example not supported on latest releases
2025-11-20 08:55:00 +01:00
David Cermak
6f6110e30e fix(mosq): Add optional mqtt deps to examples 2025-11-20 08:33:01 +01:00
david-cermak
41f7157ffb Merge pull request #951 from david-cermak/fix/asio_ci
[asio]: Fix CI target tests
2025-11-19 21:07:24 +01:00
David Cermak
858f67c280 fix(asio): Run target tests on virtual env 2025-11-19 13:40:43 +01:00
david-cermak
83ffeb0d12 Merge pull request #950 from david-cermak/fix/lws_remove_v6.0_support
[lws]: Remove lws support for IDF>=v6.0
2025-11-19 13:39:29 +01:00
david-cermak
6e99202a18 Merge pull request #948 from david-cermak/fix/ci_v6.0
Common fixes per v6.0/6.1 changes
2025-11-19 13:39:16 +01:00
david-cermak
fb5279ae88 Merge pull request #938 from david-cermak/feat/mosq_basic_auth
[mosq]: Add support for basic MQTT authentication
2025-11-19 13:39:05 +01:00
David Cermak
b70cc3fc09 fix(lws): Remove lws support for IDF>=v6.0 2025-11-19 11:57:57 +01:00
David Cermak
911c2dbe9f fix(eppp): Add optional mqtt dependency 2025-11-19 10:41:34 +01:00
David Cermak
c620855d56 fix(sock_utils): Run target tests via py-venv 2025-11-19 10:40:04 +01:00
david-cermak
cccfdd9315 Merge pull request #946 from david-cermak/fix/examples_mqtt
Add optional mqtt deps to examples
2025-11-19 10:33:14 +01:00
David Cermak
ecc7258093 ci(examples): Fix examples CI to build/test on supported releases
Ignore "The smallest app partition is nearly full" unconditionally
as most stable releases produce this on most projects
2025-11-19 10:11:13 +01:00
David Cermak
0caea67542 fix(examples): Add optional return for low level netif input
Also fixes driver/uart-driver dependency
2025-11-19 08:33:36 +01:00
david-cermak
11a8567598 Merge pull request #884 from david-cermak/feat/modem_prepare_v2.0
[modem]: Prepare for v2.0 release
2025-11-18 18:03:25 +01:00
David Cermak
9b80a7ef7d fix(example): Use eth-phy-generic on IDF>=5.4 2025-11-18 16:26:23 +01:00
David Cermak
12028ab250 fix(examples): Add optional mqtt deps to examples 2025-11-18 15:38:04 +01:00
David Cermak
68e299a357 docs(modem): Update docs and migration manual
Document the development mode
2025-11-18 15:28:05 +01:00
david-cermak
2681b9b3c6 Merge pull request #945 from david-cermak/fix/common_mqtt_deps
Add mqtt dependency to component's projects
2025-11-18 15:26:14 +01:00
david-cermak
782b7cd119 Merge pull request #849 from david-cermak/feat/modem_tcp_client_multi_conn
[modem]: tcp-client example to support multiple connections
2025-11-18 15:25:21 +01:00
David Cermak
3141d6cab5 fix(modem): Update example to use optional mqtt deps 2025-11-18 14:39:18 +01:00
David Cermak
7b8770e2fc fix(console): Add explicit dependency on esp-mqtt if needed 2025-11-18 14:16:34 +01:00
David Cermak
6153c0002a fix(console): Workaround ethernet-init IDF dependency 2025-11-18 14:16:34 +01:00
David Cermak
3d5e11b82f fix(mqtt_cxx): Add explicit dependency on esp-mqtt if needed 2025-11-18 14:16:34 +01:00
David Cermak
eacc3a0aa8 fix(common): Use actual IDF releases for console ci
And ignore potential ethernet-init warnings when used with cmd_ifconfig
2025-11-18 14:15:30 +01:00
David Cermak
2826287d43 feat(modem): Add support for multiple connection in AT based example 2025-11-18 10:37:01 +01:00
david-cermak
3bfa00389d Merge pull request #944 from david-cermak/bump/mdns_v1.9.1
[mdns]: Bump to `v1.9.1`
2025-11-18 10:36:46 +01:00
David Cermak
ace7fca8c6 bump(mdns): 1.9.0 -> 1.9.1
1.9.1
Bug Fixes
- Fix to use tagged AFL image + minor format fix (2b2f009a)
- Fix unused variable `dcst` warning for wifi-remote chips (081eef88)
2025-11-14 17:20:28 +01:00
David Cermak
2b2f009a65 fix(mdns): Fix to use tagged AFL image + minor format fix 2025-11-14 17:19:55 +01:00
david-cermak
1444d575f0 Merge pull request #940 from vikramdattu/bugfix/mdns_unused_var_wifi_remote
fix(mdns): Fix unused variable `dcst` warning for wifi-remote chips (IDFGH-16787)
2025-11-14 17:18:30 +01:00
Vikram Dattu
081eef88cf fix(mdns): Fix unused variable dcst warning for wifi-remote chips
- Cover WIFI_REMOTE as well for MDNS_ESP_WIFI_ENABLED macro
 - localize `esp_netif_dhcp_status_t dcst;` declaration where used
2025-11-14 17:43:54 +05:30
david-cermak
8b0704eaf4 Merge pull request #883 from david-cermak/feat/modem_urc
[modem]: Enhanced URC handling
2025-11-14 12:52:03 +01:00
David Cermak
4889dd6fcb feat(modem): Add enhanced URC observer API 2025-11-14 11:44:48 +01:00
david-cermak
134247d88f Merge pull request #711 from david-cermak/fix/modem_ota_test
[modem]: Minor fixed in the test code
2025-11-14 11:36:36 +01:00
David Cermak
e772ce673d fix(modem): Minor fixed in the test code
* Updated iperf commands
* Updated Github certificat
2025-11-14 10:27:29 +01:00
David Cermak
3838485229 fix(mosq): Update example to optionally use basic mqtt auth 2025-11-12 11:45:40 +01:00
David Cermak
ba3377b262 fix(mosq): Fix unpwd-check wrap function 2025-11-12 11:44:55 +01:00
David Cermak
e5bed394ee fix(ci): Run mosquitto tests on py-venv3.12 2025-11-10 11:55:29 +01:00
David Cermak
65b58aa05a feat(mosq): Add support for basic MQTT authentication 2025-11-10 09:19:23 +01:00
David Cermak
d1e6708063 fix(modem): Add missing set_echo() C wrapper
Closes https://github.com/espressif/esp-protocols/issues/926
2025-11-05 12:57:04 +01:00
83 changed files with 1828 additions and 583 deletions

View File

@@ -81,18 +81,24 @@ jobs:
with:
name: examples_app_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.example }}
path: ${{ env.TEST_DIR }}/${{ matrix.example }}/build
- name: Install Python packages
env:
PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple"
run: |
sudo apt-get install -y dnsutils
- name: Download Example Test to target ${{ matrix.config }}
run: |
python -m esptool --chip ${{ matrix.idf_target }} write_flash 0x0 ${{ env.TEST_DIR }}/${{ matrix.example }}/build/flash_image.bin
- name: Run Example Test ${{ matrix.example }} on target
working-directory: ${{ env.TEST_DIR }}/${{ matrix.example }}
run: |
python -m pytest --log-cli-level DEBUG --junit-xml=./examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.config }}.xml --target=${{ matrix.idf_target }}
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
python -m pytest --log-cli-level DEBUG --junit-xml=./examples_results_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.config }}.xml --target=${{ matrix.idf_target }}
- uses: actions/upload-artifact@v4
if: always()
with:

View File

@@ -13,7 +13,7 @@ jobs:
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
idf_target: ["esp32"]
test: [ { app: ifconfig-basic, path: "components/console_cmd_ifconfig/examples"}]
include:

View File

@@ -13,7 +13,7 @@ jobs:
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
idf_target: ["esp32"]
test: [ { app: mqtt_ssl_auth_console, path: "components/console_cmd_mqtt/examples" }]
runs-on: ubuntu-22.04

View File

@@ -13,7 +13,7 @@ jobs:
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
idf_target: ["esp32"]
test: [ { app: ping-basic, path: "components/console_cmd_ping/examples" }]
runs-on: ubuntu-22.04

View File

@@ -13,7 +13,7 @@ jobs:
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
idf_target: ["esp32"]
test: [ { app: wifi-basic, path: "components/console_cmd_wifi/examples" }]
runs-on: ubuntu-22.04

View File

@@ -13,7 +13,7 @@ jobs:
name: Build
strategy:
matrix:
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
idf_target: ["esp32"]
test: [ { app: console_basic, path: "components/console_simple_init/examples" }]
runs-on: ubuntu-22.04

View File

@@ -13,10 +13,7 @@ jobs:
name: Build examples
strategy:
matrix:
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
include:
- idf_ver: "latest"
warning: "Warning: The smallest app partition is nearly full"
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
runs-on: ubuntu-22.04
container: espressif/idf:${{ matrix.idf_ver }}
env:
@@ -27,7 +24,7 @@ jobs:
uses: actions/checkout@v4
- name: Build with IDF-${{ matrix.idf_ver }}
env:
EXPECTED_WARNING: ${{ matrix.warning }}
EXPECTED_WARNING: "Warning: The smallest app partition is nearly full"
shell: bash
run: |
. ${IDF_PATH}/export.sh
@@ -75,7 +72,7 @@ jobs:
needs: build_all_examples
strategy:
matrix:
idf_ver: ["release-v5.4", "latest"]
idf_ver: ["release-v5.5", "latest"]
runs-on:
- self-hosted
- modem

View File

@@ -13,7 +13,7 @@ jobs:
name: Libwebsockets build
strategy:
matrix:
idf_ver: ["latest", "release-v5.3", "release-v5.4"]
idf_ver: ["release-v5.3", "release-v5.4", "release-v5.5"]
test: [ { app: example, path: "examples/client" }]
runs-on: ubuntu-22.04
container: espressif/idf:${{ matrix.idf_ver }}
@@ -51,7 +51,7 @@ jobs:
strategy:
fail-fast: false
matrix:
idf_ver: ["latest", "release-v5.3", "release-v5.4"]
idf_ver: ["release-v5.3", "release-v5.4", "release-v5.5"]
idf_target: ["esp32"]
test: [ { app: example, path: "examples/client" }]
runs-on:
@@ -65,14 +65,24 @@ jobs:
with:
name: lws_target_esp32_${{ matrix.idf_ver }}_${{ matrix.test.app }}
path: ${{ env.TEST_DIR }}/ci/
- name: Install Python packages
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 --only-binary cryptography --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

View File

@@ -77,7 +77,7 @@ jobs:
idf_ver: ["latest"]
runs-on: ubuntu-22.04
container: aflplusplus/aflplusplus
container: aflplusplus/aflplusplus:v4.34c
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v4

View File

@@ -13,7 +13,7 @@ jobs:
name: Build examples
strategy:
matrix:
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
example: ["pppos_client", "modem_console", "modem_tcp_client", "ap_to_pppos", "simple_cmux_client"]
runs-on: ubuntu-22.04
container: espressif/idf:${{ matrix.idf_ver }}
@@ -39,7 +39,7 @@ jobs:
name: Build tests
strategy:
matrix:
idf_ver: ["release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "latest"]
idf_ver: ["latest","release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4", "release-v5.5", "release-v6.0"]
test: ["target", "target_ota", "target_iperf", "target_urc"]
runs-on: ubuntu-22.04

View File

@@ -13,7 +13,7 @@ jobs:
name: Build Target tests
strategy:
matrix:
idf_ver: ["latest"]
idf_ver: ["latest", "release-v5.5", "release-v6.0"]
idf_target: ["esp32c3"]
test: [ { app: pppd, path: test/target }, { app: pppd_chap_auth, path: test/target }, { app: sim800_c3, path: examples/pppos_client }, { app: sim800_cmux, path: examples/simple_cmux_client } ]
include:
@@ -54,7 +54,7 @@ jobs:
name: Run Target tests
strategy:
matrix:
idf_ver: ["latest"]
idf_ver: ["latest", "release-v5.5", "release-v6.0"]
idf_target: ["esp32c3"]
test: [ { app: pppd, path: test/target }, { app: pppd_chap_auth, path: test/target }, { app: sim800_c3, path: examples/pppos_client }, { app: sim800_cmux, path: examples/simple_cmux_client } ]
include:

View File

@@ -13,13 +13,15 @@ jobs:
name: Mosquitto build
strategy:
matrix:
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"
idf_ver: ["latest", "release-v6.0", "release-v5.5", "release-v5.4", "release-v5.3", "release-v5.2", "release-v5.1"]
example: ["broker"]
include:
# serverless_mqtt is not supported on >=v6.0 (esp-peer dependency)
- idf_ver: "release-v5.3"
example: "serverless_mqtt"
- idf_ver: "latest"
- idf_ver: "release-v5.4"
example: "serverless_mqtt"
- idf_ver: "release-v5.5"
example: "serverless_mqtt"
runs-on: ubuntu-22.04
@@ -61,7 +63,7 @@ jobs:
needs: build_mosq
strategy:
matrix:
idf_ver: ["latest", "release-v5.3"]
idf_ver: ["release-v5.4", "release-v5.5", "release-v6.0", "latest"]
runs-on:
- self-hosted
- ESP32-ETHERNET-KIT
@@ -77,6 +79,20 @@ jobs:
- name: Run Test
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
python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results
unzip ci/artifacts.zip -d ci
for dir in `ls -d ci/build_*`; do
@@ -120,7 +136,7 @@ jobs:
name: Build IDF tests
strategy:
matrix:
idf_ver: ["latest"]
idf_ver: ["release-v5.5"] # TODO: add release-v6.0/latest with esp-mqtt directly
idf_target: ["esp32"]
test: [ { app: publish, path: "tools/test_apps/protocols/mqtt/publish_connect_test" }]
runs-on: ubuntu-22.04
@@ -165,7 +181,7 @@ jobs:
needs: build_idf_tests_with_mosq
strategy:
matrix:
idf_ver: ["latest"]
idf_ver: ["release-v5.5"]
runs-on:
- self-hosted
- ESP32-ETHERNET-KIT
@@ -180,6 +196,20 @@ jobs:
- name: Run Test
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
python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results "paho-mqtt<2" --upgrade
unzip ci/artifacts.zip -d ci
for dir in `ls -d ci/build_*`; do

View File

@@ -87,6 +87,20 @@ jobs:
- name: Run Test
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
unzip ci/artifacts.zip -d ci
for dir in `ls -d ci/build_*`; do
rm -rf build sdkconfig.defaults

View File

@@ -5,3 +5,4 @@ Warning: Deprecated: Option '--flash_freq' is deprecated. Use '--flash-freq' ins
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'
WARNING: The following Kconfig variables were used in "if" clauses, but not

View File

@@ -9,4 +9,8 @@ dependencies:
override_path: '../console_simple_init'
public: true
espressif/ethernet_init:
version: '>=0.0.7'
matches:
- if: idf_version >=6.0
version: ^1.0.0
- if: idf_version <6.0
version: '==0.3.0'

View File

@@ -9,3 +9,7 @@ dependencies:
version: '>=1.1.0'
override_path: '../console_simple_init'
public: true
espressif/mqtt:
rules:
- if: idf_version >=6.0
version: ^1.0.0

View File

@@ -5,3 +5,7 @@ dependencies:
override_path: ../../..
console_cmd_ping:
version: '*'
espressif/mqtt:
rules:
- if: idf_version >=6.0
version: ^1.0.0

View File

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

View File

@@ -1,5 +1,41 @@
# Changelog
## [2.0.0](https://github.com/espressif/esp-protocols/commits/modem-v2.0.0)
### Breaking changes
- inc headers for AT command definitions are no longer used directly, but pregenerated into *.h(pp) ([Use generated AT command definitions for IDE navigation](https://github.com/espressif/esp-protocols/commit/e2fa1110))
### Features
- Add support for multiple connection in AT based example ([2826287d](https://github.com/espressif/esp-protocols/commit/2826287d))
- Add enhanced URC observer API ([4889dd6f](https://github.com/espressif/esp-protocols/commit/4889dd6f))
- Support esp-modem use without PPP ([858f8570](https://github.com/espressif/esp-protocols/commit/858f8570), [#851](https://github.com/espressif/esp-protocols/issues/851))
- Modem simulator based on esp-at ([e5787e3d](https://github.com/espressif/esp-protocols/commit/e5787e3d))
### Bug Fixes
- Update tests and examples to use modem-v2.0 ([4aa0e4ba](https://github.com/espressif/esp-protocols/commit/4aa0e4ba))
- Replace MQTT client with simple ping command ([0ccaf2c0](https://github.com/espressif/esp-protocols/commit/0ccaf2c0))
- Replace MQTT client with simple ping command ([9b2b1f68](https://github.com/espressif/esp-protocols/commit/9b2b1f68))
- Update example to use optional mqtt deps ([3141d6ca](https://github.com/espressif/esp-protocols/commit/3141d6ca))
- Minor fixed in the test code ([e772ce67](https://github.com/espressif/esp-protocols/commit/e772ce67))
- Add missing set_echo() C wrapper ([d1e67080](https://github.com/espressif/esp-protocols/commit/d1e67080), [#926](https://github.com/espressif/esp-protocols/issues/926))
- Fix modem console dependencies ([453be4cd](https://github.com/espressif/esp-protocols/commit/453be4cd))
- Address build issues ([018ba58e](https://github.com/espressif/esp-protocols/commit/018ba58e))
- Fix driver dependency issue on v6.0 ([67c682d9](https://github.com/espressif/esp-protocols/commit/67c682d9))
- Fix CI build issues with IDFv6.0 ([15140e04](https://github.com/espressif/esp-protocols/commit/15140e04))
- Add support for ESP-AT based tcp-client example ([14d3cb6b](https://github.com/espressif/esp-protocols/commit/14d3cb6b))
- Use idf-build-apps for building target tests ([e9d9b3a8](https://github.com/espressif/esp-protocols/commit/e9d9b3a8))
- Make MQTT public broker endpoint configurable ([6d541194](https://github.com/espressif/esp-protocols/commit/6d541194))
- Fix URC handling in DTE data callback ([93029946](https://github.com/espressif/esp-protocols/commit/93029946))
- Use another public broker for examples and tests ([fac2edbe](https://github.com/espressif/esp-protocols/commit/fac2edbe))
- Fix incompatible iterator in std::search() in new gcc ([ed0f6334](https://github.com/espressif/esp-protocols/commit/ed0f6334))
- Fix autodetect to support ACFC mode in PPP frames ([8b328a69](https://github.com/espressif/esp-protocols/commit/8b328a69), [#801](https://github.com/espressif/esp-protocols/issues/801))
- Fix get_network_registration_state() to accept two params ([5f54d907](https://github.com/espressif/esp-protocols/commit/5f54d907), [#826](https://github.com/espressif/esp-protocols/issues/826))
- Consume buffer after handled URC ([6eceb28f](https://github.com/espressif/esp-protocols/commit/6eceb28f))
- Use generated AT command definitions for IDE navigation ([e2fa1110](https://github.com/espressif/esp-protocols/commit/e2fa1110), !BREAKING)
## [1.4.0](https://github.com/espressif/esp-protocols/commits/modem-v1.4.0)
### Features

View File

@@ -1,4 +1,4 @@
dependencies:
espressif/esp_modem:
version: "^1.0.1"
version: "^2"
override_path: "../../../"

View File

@@ -1,4 +1,4 @@
dependencies:
espressif/esp_modem:
version: "^1.0.1"
version: "^2"
override_path: "../../../"

View File

@@ -3,7 +3,7 @@ dependencies:
## Required IDF version
idf: ">=4.1.0"
espressif/esp_modem:
version: "^1.0.0"
version: "^2"
override_path: "../../../"
espressif/esp_modem_usb_dte:
version: "^1.2.0"

View File

@@ -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
*/
@@ -97,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
@@ -381,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;
});

View File

@@ -1,4 +1,4 @@
dependencies:
espressif/esp_modem:
version: "^1.0.1"
version: "^2"
override_path: "../../../"

View File

@@ -22,3 +22,18 @@ To enable this mode, please set `EXAMPLE_CUSTOM_TCP_TRANSPORT=y`
This configuration could be used with any network library, which is connecting to a localhost endpoint instead of remote one. This example creates a localhost listener which basically mimics the remote endpoint by forwarding the traffic between the library and the TCP/socket layer of the modem (which is already secure if the TLS is used in the network library)
![with localhost listener](at_client_localhost.png)
### Multi-connection support
This example supports opening multiple TCP connections concurrently when the modem firmware allows it.
- ESP-AT: Multi-connection mode is enabled via `AT+CIPMUX=1`. The example assigns a unique link ID per DCE instance and includes the link ID in `CIPSTART/CIPSEND/CIPRECVDATA` commands.
- BG96/SIM7600: The example uses module-specific multi-connection syntax (for example `QIOPEN/CIPOPEN` with a connection ID) and tracks link IDs internally.
How it works:
- The `sock_dce` layer creates multiple DCE instances over a shared DTE. A lightweight mutex coordinates access to the UART so only one DCE issues AT commands at a time.
- Asynchronous URCs (for example `+IPD`, `+QIURC`, `+CIPRXGET: 1,<cid>`) wake the corresponding DCE which then performs receive operations for its link.
Usage:
- `app_main` starts two DCE tasks to demonstrate concurrent connections. Adjust the number of DCE instances as needed.
- For ESP-AT, ensure your firmware supports `CIPMUX=1` and passive receive (`CIPRECVTYPE`).

View File

@@ -44,6 +44,18 @@ menu "Example Configuration"
help
Set APN (Access Point Name), a logical name to choose data network
config EXAMPLE_USE_TLS
bool "Use TLS for MQTT broker"
default n
help
Enable TLS for connection to the MQTT broker.
config EXAMPLE_BROKER_HOST
string "MQTT broker host"
default "test.mosquitto.org"
help
Hostname or IP address of the MQTT broker.
menu "UART Configuration"
config EXAMPLE_MODEM_UART_TX_PIN
int "TXD Pin Number"

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <algorithm>
#include <charconv>
#include <sys/socket.h>
#include "esp_vfs.h"
@@ -14,6 +15,29 @@
namespace sock_dce {
constexpr auto const *TAG = "sock_dce";
constexpr auto WAIT_TO_IDLE_TIMEOUT = 5000;
// Definition of the static member variables
std::vector<DCE *> DCE::dce_list{};
bool DCE::network_init = false;
int Responder::s_link_id = 0;
SemaphoreHandle_t Responder::s_dte_mutex{};
// Constructor - add this DCE instance to the static list
DCE::DCE(std::shared_ptr<esp_modem::DTE> dte_arg, const esp_modem_dce_config *config)
: Module(std::move(dte_arg), config)
{
dce_list.push_back(this);
}
// Destructor - remove this DCE instance from the static list
DCE::~DCE()
{
auto it = std::find(dce_list.begin(), dce_list.end(), this);
if (it != dce_list.end()) {
dce_list.erase(it);
}
}
bool DCE::perform_sock()
@@ -61,13 +85,26 @@ bool DCE::perform_sock()
void DCE::perform_at(uint8_t *data, size_t len)
{
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE);
if (state != status::RECEIVING) {
std::string_view resp_sv((char *)data, len);
at.check_urc(state, resp_sv);
if (state == status::IDLE) {
return;
}
}
// Trace incoming AT bytes when handling a response; use DEBUG level
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_DEBUG);
switch (at.process_data(state, data, len)) {
case Responder::ret::OK:
// Release DTE access for this link after processing data
ESP_LOGD(TAG, "GIVE data %d", at.link_id);
xSemaphoreGive(at.s_dte_mutex);
state = status::IDLE;
signal.set(IDLE);
return;
case Responder::ret::FAIL:
ESP_LOGD(TAG, "GIVE data %d", at.link_id);
xSemaphoreGive(at.s_dte_mutex);
state = status::FAILED;
signal.set(IDLE);
return;
@@ -82,10 +119,14 @@ void DCE::perform_at(uint8_t *data, size_t len)
std::string_view response((char *)data, len);
switch (at.check_async_replies(state, response)) {
case Responder::ret::OK:
ESP_LOGD(TAG, "GIVE command %d", at.link_id);
xSemaphoreGive(at.s_dte_mutex);
state = status::IDLE;
signal.set(IDLE);
return;
case Responder::ret::FAIL:
ESP_LOGD(TAG, "GIVE command %d", at.link_id);
xSemaphoreGive(at.s_dte_mutex);
state = status::FAILED;
signal.set(IDLE);
return;
@@ -121,7 +162,7 @@ bool DCE::at_to_sock()
uint64_t data;
read(data_ready_fd, &data, sizeof(data));
ESP_LOGD(TAG, "select read: modem data available %" PRIu64, data);
if (!signal.wait(IDLE, 1000)) {
if (!signal.wait(IDLE, WAIT_TO_IDLE_TIMEOUT)) {
ESP_LOGE(TAG, "Failed to get idle");
close_sock();
return false;
@@ -131,6 +172,10 @@ bool DCE::at_to_sock()
close_sock();
return false;
}
// Take DTE mutex before issuing receive on this link
ESP_LOGD(TAG, "TAKE RECV %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD(TAG, "TAKEN RECV %d", at.link_id);
state = status::RECEIVING;
at.start_receiving(at.get_buf_len());
return true;
@@ -139,7 +184,7 @@ bool DCE::at_to_sock()
bool DCE::sock_to_at()
{
ESP_LOGD(TAG, "socket read: data available");
if (!signal.wait(IDLE, 1000)) {
if (!signal.wait(IDLE, WAIT_TO_IDLE_TIMEOUT)) {
ESP_LOGE(TAG, "Failed to get idle");
close_sock();
return false;
@@ -149,6 +194,10 @@ bool DCE::sock_to_at()
close_sock();
return false;
}
// Take DTE mutex before issuing send on this link
ESP_LOGD(TAG, "TAKE SEND %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD(TAG, "TAKEN SEND %d", at.link_id);
state = status::SENDING;
int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0);
if (len < 0) {
@@ -201,7 +250,7 @@ void DCE::start_listening(int port)
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ESP_LOGI(TAG, "Socket created");
ESP_LOGD(TAG, "Socket created");
struct sockaddr_in addr = { };
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
@@ -213,7 +262,7 @@ void DCE::start_listening(int port)
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
return;
}
ESP_LOGI(TAG, "Socket bound, port %d", 1883);
ESP_LOGD(TAG, "Socket bound, port %d", 1883);
err = listen(listen_sock, 1);
if (err != 0) {
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
@@ -224,12 +273,12 @@ void DCE::start_listening(int port)
bool DCE::connect(std::string host, int port)
{
dte->on_read(nullptr);
tcp_close();
dte->on_read([this](uint8_t *data, size_t len) {
this->perform_at(data, len);
return esp_modem::command_result::TIMEOUT;
});
data_ready_fd = eventfd(0, EFD_SUPPORT_ISR);
assert(data_ready_fd > 0);
// Take DTE mutex before starting connect for this link
ESP_LOGD(TAG, "TAKE CONNECT %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD(TAG, "TAKEN CONNECT %d", at.link_id);
if (!at.start_connecting(host, port)) {
ESP_LOGE(TAG, "Unable to start connecting");
dte->on_read(nullptr);
@@ -241,12 +290,15 @@ bool DCE::connect(std::string host, int port)
bool DCE::init()
{
if (network_init) {
return true;
}
network_init = true;
Responder::s_dte_mutex = xSemaphoreCreateBinary();
xSemaphoreGive(at.s_dte_mutex);
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
esp_vfs_eventfd_register(&config);
data_ready_fd = eventfd(0, EFD_SUPPORT_ISR);
assert(data_ready_fd > 0);
dte->on_read(nullptr);
const int retries = 5;
int i = 0;
@@ -287,6 +339,10 @@ bool DCE::init()
esp_modem::Task::Delay(5000);
}
ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str());
dte->on_read([](uint8_t *data, size_t len) {
read_callback(data, len);
return esp_modem::command_result::TIMEOUT;
});
return true;
}

View File

@@ -34,6 +34,7 @@ public:
sock(s), data_ready_fd(ready_fd), dte(dte_arg) {}
ret process_data(status state, uint8_t *data, size_t len);
ret check_async_replies(status state, std::string_view &response);
ret check_urc(status state, std::string_view &response);
void start_sending(size_t len);
void start_receiving(size_t len);
@@ -63,13 +64,19 @@ public:
return total_len;
}
// Unique link identifier used to target multi-connection AT commands
int link_id{s_link_id++};
// Shared mutex guarding DTE access across concurrent DCE instances
static SemaphoreHandle_t s_dte_mutex;
private:
static int s_link_id;
static constexpr size_t buffer_size = 512;
bool on_read(char *data, size_t len)
{
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
::send(sock, data, len, 0);
printf("sending %d\n", len);
#else
::memcpy(&buffer[actual_read], data, len);
actual_read += len;
@@ -101,6 +108,8 @@ private:
class DCE : public Module {
using Module::Module;
public:
DCE(std::shared_ptr<esp_modem::DTE> dte_arg, const esp_modem_dce_config *config);
~DCE();
/**
* @brief Opens network in AT command mode
@@ -163,6 +172,10 @@ public:
return 0;
}
at.clear_offsets();
// Take DTE mutex before issuing receive on this link
ESP_LOGD("TAG", "TAKE RECV %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD("TAG", "TAKEN RECV %d", at.link_id);
state = status::RECEIVING;
uint64_t data;
read(data_ready_fd, &data, sizeof(data));
@@ -184,6 +197,10 @@ public:
if (!wait_to_idle(timeout_ms)) {
return -1;
}
// Take DTE mutex before issuing send on this link
ESP_LOGD("TAG", "TAKE SEND %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD("TAG", "TAKEN SEND %d", at.link_id);
state = status::SENDING;
memcpy(at.get_buf(), buffer, len_to_send);
ESP_LOG_BUFFER_HEXDUMP("dce", at.get_buf(), len, ESP_LOG_VERBOSE);
@@ -224,6 +241,14 @@ public:
}
return -1;
}
static std::vector<DCE *> dce_list;
static bool network_init;
static void read_callback(uint8_t *data, size_t len)
{
for (auto dce : dce_list) {
dce->perform_at(data, len);
}
}
private:
esp_modem::SignalGroup signal;
void close_sock();

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <algorithm>
#include <charconv>
#include <sys/socket.h>
#include "esp_vfs.h"
@@ -14,6 +15,29 @@
namespace sock_dce {
constexpr auto const *TAG = "sock_dce";
constexpr auto WAIT_TO_IDLE_TIMEOUT = 5000;
// Definition of the static member variables
std::vector<DCE*> DCE::dce_list{};
bool DCE::network_init = false;
int Responder::s_link_id = 0;
SemaphoreHandle_t Responder::s_dte_mutex{};
// Constructor - add this DCE instance to the static list
DCE::DCE(std::shared_ptr<esp_modem::DTE> dte_arg, const esp_modem_dce_config *config)
: Module(std::move(dte_arg), config)
{
dce_list.push_back(this);
}
// Destructor - remove this DCE instance from the static list
DCE::~DCE()
{
auto it = std::find(dce_list.begin(), dce_list.end(), this);
if (it != dce_list.end()) {
dce_list.erase(it);
}
}
bool DCE::perform_sock()
@@ -61,13 +85,26 @@ bool DCE::perform_sock()
void DCE::perform_at(uint8_t *data, size_t len)
{
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_VERBOSE);
if (state != status::RECEIVING) {
std::string_view resp_sv((char *)data, len);
at.check_urc(state, resp_sv);
if (state == status::IDLE) {
return;
}
}
// Trace incoming AT bytes when handling a response; use DEBUG level
ESP_LOG_BUFFER_HEXDUMP(TAG, data, len, ESP_LOG_DEBUG);
switch (at.process_data(state, data, len)) {
case Responder::ret::OK:
// Release DTE access for this link after processing data
ESP_LOGD(TAG, "GIVE data %d", at.link_id);
xSemaphoreGive(at.s_dte_mutex);
state = status::IDLE;
signal.set(IDLE);
return;
case Responder::ret::FAIL:
ESP_LOGD(TAG, "GIVE data %d", at.link_id);
xSemaphoreGive(at.s_dte_mutex);
state = status::FAILED;
signal.set(IDLE);
return;
@@ -82,10 +119,14 @@ void DCE::perform_at(uint8_t *data, size_t len)
std::string_view response((char *)data, len);
switch (at.check_async_replies(state, response)) {
case Responder::ret::OK:
ESP_LOGD(TAG, "GIVE command %d", at.link_id);
xSemaphoreGive(at.s_dte_mutex);
state = status::IDLE;
signal.set(IDLE);
return;
case Responder::ret::FAIL:
ESP_LOGD(TAG, "GIVE command %d", at.link_id);
xSemaphoreGive(at.s_dte_mutex);
state = status::FAILED;
signal.set(IDLE);
return;
@@ -121,7 +162,7 @@ bool DCE::at_to_sock()
uint64_t data;
read(data_ready_fd, &data, sizeof(data));
ESP_LOGD(TAG, "select read: modem data available %" PRIu64, data);
if (!signal.wait(IDLE, 1000)) {
if (!signal.wait(IDLE, WAIT_TO_IDLE_TIMEOUT)) {
ESP_LOGE(TAG, "Failed to get idle");
close_sock();
return false;
@@ -131,6 +172,10 @@ bool DCE::at_to_sock()
close_sock();
return false;
}
// Take DTE mutex before issuing receive on this link
ESP_LOGD(TAG, "TAKE RECV %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD(TAG, "TAKEN RECV %d", at.link_id);
state = status::RECEIVING;
at.start_receiving(at.get_buf_len());
return true;
@@ -139,7 +184,7 @@ bool DCE::at_to_sock()
bool DCE::sock_to_at()
{
ESP_LOGD(TAG, "socket read: data available");
if (!signal.wait(IDLE, 1000)) {
if (!signal.wait(IDLE, WAIT_TO_IDLE_TIMEOUT)) {
ESP_LOGE(TAG, "Failed to get idle");
close_sock();
return false;
@@ -149,6 +194,10 @@ bool DCE::sock_to_at()
close_sock();
return false;
}
// Take DTE mutex before issuing send on this link
ESP_LOGD(TAG, "TAKE SEND %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD(TAG, "TAKEN SEND %d", at.link_id);
state = status::SENDING;
int len = ::recv(sock, at.get_buf(), at.get_buf_len(), 0);
if (len < 0) {
@@ -201,7 +250,7 @@ void DCE::start_listening(int port)
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ESP_LOGI(TAG, "Socket created");
ESP_LOGD(TAG, "Socket created");
struct sockaddr_in addr = { };
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
@@ -213,7 +262,7 @@ void DCE::start_listening(int port)
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
return;
}
ESP_LOGI(TAG, "Socket bound, port %d", 1883);
ESP_LOGD(TAG, "Socket bound, port %d", 1883);
err = listen(listen_sock, 1);
if (err != 0) {
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
@@ -224,12 +273,12 @@ void DCE::start_listening(int port)
bool DCE::connect(std::string host, int port)
{
dte->on_read(nullptr);
tcp_close();
dte->on_read([this](uint8_t *data, size_t len) {
this->perform_at(data, len);
return esp_modem::command_result::TIMEOUT;
});
data_ready_fd = eventfd(0, EFD_SUPPORT_ISR);
assert(data_ready_fd > 0);
// Take DTE mutex before starting connect for this link
ESP_LOGD(TAG, "TAKE CONNECT %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD(TAG, "TAKEN CONNECT %d", at.link_id);
if (!at.start_connecting(host, port)) {
ESP_LOGE(TAG, "Unable to start connecting");
dte->on_read(nullptr);
@@ -241,12 +290,15 @@ bool DCE::connect(std::string host, int port)
bool DCE::init()
{
if (network_init) {
return true;
}
network_init = true;
Responder::s_dte_mutex = xSemaphoreCreateBinary();
xSemaphoreGive(at.s_dte_mutex);
esp_vfs_eventfd_config_t config = ESP_VFS_EVENTD_CONFIG_DEFAULT();
esp_vfs_eventfd_register(&config);
data_ready_fd = eventfd(0, EFD_SUPPORT_ISR);
assert(data_ready_fd > 0);
dte->on_read(nullptr);
const int retries = 5;
int i = 0;
@@ -287,6 +339,10 @@ bool DCE::init()
esp_modem::Task::Delay(5000);
}
ESP_LOGI(TAG, "Got IP %s", ip_addr.c_str());
dte->on_read([](uint8_t *data, size_t len) {
read_callback(data, len);
return esp_modem::command_result::TIMEOUT;
});
return true;
}

View File

@@ -34,6 +34,7 @@ public:
sock(s), data_ready_fd(ready_fd), dte(dte_arg) {}
ret process_data(status state, uint8_t *data, size_t len);
ret check_async_replies(status state, std::string_view &response);
ret check_urc(status state, std::string_view &response);
void start_sending(size_t len);
void start_receiving(size_t len);
@@ -63,13 +64,19 @@ public:
return total_len;
}
// Unique link identifier used to target multi-connection AT commands
int link_id{s_link_id++};
// Shared mutex guarding DTE access across concurrent DCE instances
static SemaphoreHandle_t s_dte_mutex;
private:
static int s_link_id;
static constexpr size_t buffer_size = 512;
bool on_read(char *data, size_t len)
{
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
::send(sock, data, len, 0);
printf("sending %d\n", len);
#else
::memcpy(&buffer[actual_read], data, len);
actual_read += len;
@@ -101,6 +108,8 @@ private:
class DCE : public Module {
using Module::Module;
public:
DCE(std::shared_ptr<esp_modem::DTE> dte_arg, const esp_modem_dce_config *config);
~DCE();
// --- ESP-MODEM command module starts here ---
#include "esp_modem_command_declare_helper.inc"
@@ -141,6 +150,10 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__));
return 0;
}
at.clear_offsets();
// Take DTE mutex before issuing receive on this link
ESP_LOGD("TAG", "TAKE RECV %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD("TAG", "TAKEN RECV %d", at.link_id);
state = status::RECEIVING;
uint64_t data;
read(data_ready_fd, &data, sizeof(data));
@@ -163,6 +176,10 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__));
if (!wait_to_idle(timeout_ms)) {
return -1;
}
// Take DTE mutex before issuing send on this link
ESP_LOGD("TAG", "TAKE SEND %d", at.link_id);
xSemaphoreTake(at.s_dte_mutex, portMAX_DELAY);
ESP_LOGD("TAG", "TAKEN SEND %d", at.link_id);
state = status::SENDING;
memcpy(at.get_buf(), buffer, len_to_send);
ESP_LOG_BUFFER_HEXDUMP("dce", at.get_buf(), len, ESP_LOG_VERBOSE);
@@ -204,6 +221,14 @@ esp_modem::return_type name(ESP_MODEM_COMMAND_PARAMS(__VA_ARGS__));
}
return -1;
}
static std::vector<DCE*> dce_list;
static bool network_init;
static void read_callback(uint8_t *data, size_t len)
{
for (auto dce : dce_list) {
dce->perform_at(data, len);
}
}
private:
esp_modem::SignalGroup signal;

View File

@@ -1,7 +1,11 @@
dependencies:
espressif/esp_modem:
version: "^1.0.1"
override_path: "../../../"
version: ^2
override_path: ../../../
espressif/mbedtls_cxx:
version: "*"
override_path: "../../../../mbedtls_cxx"
version: '*'
override_path: ../../../../mbedtls_cxx
espressif/mqtt:
rules:
- if: idf_version >=6.0
version: ^1.0.0

View File

@@ -22,15 +22,26 @@
#include "esp_log.h"
#include "tcp_transport_mbedtls.h"
#include "tcp_transport_at.h"
#include "sdkconfig.h"
#define BROKER_URL "test.mosquitto.org"
#define USE_TLS CONFIG_EXAMPLE_USE_TLS
#define BROKER_HOST CONFIG_EXAMPLE_BROKER_HOST
#if USE_TLS
#define BROKER_SCHEME "mqtts"
#define BROKER_PORT 8883
#else
#define BROKER_SCHEME "mqtt"
#define BROKER_PORT 1883
#endif
#define BROKER_URL BROKER_SCHEME "://" BROKER_HOST
static const char *TAG = "modem_client";
static EventGroupHandle_t event_group = NULL;
static const int CONNECT_BIT = BIT0;
static const int GOT_DATA_BIT = BIT2;
static const int DCE0_DONE = BIT3;
static const int DCE1_DONE = BIT4;
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
@@ -73,13 +84,15 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
}
}
static void perform(void* ctx);
extern "C" void app_main(void)
{
/* Init and register system/core components */
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Default to INFO; individual modules use DEBUG for verbose tracing
esp_log_level_set("*", ESP_LOG_INFO);
event_group = xEventGroupCreate();
@@ -104,30 +117,60 @@ extern "C" void app_main(void)
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_APN);
/* create the DCE and initialize network manually (using AT commands) */
auto dce = sock_dce::create(&dce_config, std::move(dte));
auto dce = sock_dce::create(&dce_config, dte);
if (!dce->init()) {
ESP_LOGE(TAG, "Failed to setup network");
return;
}
esp_mqtt_client_config_t mqtt_config = {};
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 = "mqtt://127.0.0.1";
dce->start_listening(BROKER_PORT);
#else
mqtt_config.broker.address.uri = "mqtt://" BROKER_URL;
esp_transport_handle_t at = esp_transport_at_init(dce.get());
esp_transport_handle_t ssl = esp_transport_tls_init(at);
xTaskCreate(perform, "perform", 4096, dce.get(), 4, nullptr);
mqtt_config.network.transport = ssl;
/* create another DCE to serve a new connection */
auto dce1 = sock_dce::create(&dce_config, dte);
if (!dce1->init()) {
ESP_LOGE(TAG, "Failed to setup network");
return;
}
xTaskCreate(perform, "perform", 4096, dce1.get(), 4, nullptr);
xEventGroupWaitBits(event_group, DCE0_DONE | DCE1_DONE, pdFALSE, pdTRUE, portMAX_DELAY);
#ifdef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
// this example does never keeps both DCEs running and in tcp-transport option
// we don't need a task to run so we exit main and keep DCE's "running"
vTaskDelay(portMAX_DELAY);
#endif
}
static void perform(void* ctx)
{
auto dce = static_cast<sock_dce::DCE*>(ctx);
char mqtt_client_id[] = "MQTT_CLIENT_0";
static int counter = 0;
const int id = counter++;
mqtt_client_id[sizeof(mqtt_client_id) - 2] += id; // assumes a different client id per each thread
esp_mqtt_client_config_t mqtt_config = {};
mqtt_config.session.message_retransmit_timeout = 10000;
mqtt_config.credentials.client_id = mqtt_client_id;
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
mqtt_config.broker.address.port = BROKER_PORT + id;
mqtt_config.broker.address.uri = BROKER_SCHEME "://127.0.0.1";
dce->start_listening(BROKER_PORT + id);
#else
mqtt_config.broker.address.port = BROKER_PORT;
mqtt_config.broker.address.uri = BROKER_URL;
esp_transport_handle_t at = esp_transport_at_init(dce);
#if USE_TLS
esp_transport_handle_t ssl = esp_transport_tls_init(at);
mqtt_config.network.transport = ssl;
#else
mqtt_config.network.transport = at;
#endif // USE_TLS
#endif // CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
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, nullptr);
esp_mqtt_client_start(mqtt_client);
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
if (!dce->connect(BROKER_URL, BROKER_PORT)) {
if (!dce->connect(BROKER_HOST, BROKER_PORT)) {
ESP_LOGE(TAG, "Failed to start DCE");
return;
}
@@ -135,18 +178,17 @@ extern "C" void app_main(void)
while (dce->perform_sock()) {
ESP_LOGV(TAG, "...performing");
}
ESP_LOGE(TAG, "Loop exit.. retrying");
ESP_LOGD(TAG, "Loop exit.. retrying");
// handle disconnections errors
if (!dce->init()) {
ESP_LOGE(TAG, "Failed to reinit network");
return;
}
if (!dce->connect(BROKER_URL, BROKER_PORT)) {
if (!dce->connect(BROKER_HOST, BROKER_PORT)) {
ESP_LOGI(TAG, "Network reinitialized, retrying");
}
}
#else
vTaskDelay(portMAX_DELAY);
#endif
xEventGroupSetBits(event_group, id ? DCE0_DONE : DCE1_DONE);
vTaskDelete(nullptr);
}

View File

@@ -109,17 +109,17 @@ void Responder::start_sending(size_t len)
{
data_to_send = len;
send_stat = 0;
send_cmd("AT+QISEND=0," + std::to_string(len) + "\r");
send_cmd("AT+QISEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r");
}
void Responder::start_receiving(size_t len)
{
send_cmd("AT+QIRD=0," + std::to_string(len) + "\r");
send_cmd("AT+QIRD=" + std::to_string(link_id) + "," + std::to_string(len) + "\r");
}
bool Responder::start_connecting(std::string host, int port)
{
send_cmd(R"(AT+QIOPEN=1,0,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
send_cmd(std::string("AT+QIOPEN=1,") + std::to_string(link_id) + R"(,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
return true;
}
@@ -130,16 +130,24 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
auto *recv_data = (char *)data;
if (data_to_recv == 0) {
const std::string_view head = "+QIRD: ";
again:
const std::string_view recv_data_view = std::string_view(recv_data, len);
auto head_pos_found = recv_data_view.find(head);
if (head_pos_found == std::string_view::npos) {
return ret::FAIL;
return ret::IN_PROGRESS;
}
auto *head_pos = recv_data + head_pos_found;
auto next_nl = (char *)memchr(head_pos + head.size(), '\n', MIN_MESSAGE);
if (next_nl == nullptr) {
if (head_pos + head.size() + 1 < recv_data + len) {
// might be that we misinterpreted the URC +QIRD: <>,<>,<> (notification) with the +QIRD: <> (read data)
// so we try to find the next +QIRD:
recv_data = head_pos + head.size() + 1;
goto again;
}
ESP_LOGD(TAG, "no new line found");
return ret::FAIL;
}
@@ -151,7 +159,9 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
ESP_LOGD(TAG, "Received: actual len=%d", actual_len);
if (actual_len == 0) {
ESP_LOGD(TAG, "no data received");
return ret::FAIL;
data_to_recv = 0;
// return OK here, as BG96 would keep unacked data and notifies us with +QIRD: 0
return ret::OK;
}
if (actual_len > buffer_size) {
@@ -182,6 +192,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
last_pos = (char *)memchr(recv_data + 1 + actual_len, 'O', MIN_MESSAGE);
if (last_pos == nullptr || last_pos[1] != 'K') {
data_to_recv = 0;
ESP_LOGD(TAG, "no OK after data");
return ret::FAIL;
}
}
@@ -222,7 +233,7 @@ Responder::ret Responder::send(std::string_view response)
{
if (send_stat == 3) {
if (response.find("SEND OK") != std::string::npos) {
send_cmd("AT+QISEND=0,0\r");
send_cmd(std::string("AT+QISEND=") + std::to_string(link_id) + ",0\r");
send_stat++;
return ret::IN_PROGRESS;
} else if (response.find("SEND FAIL") != std::string::npos) {
@@ -267,7 +278,7 @@ Responder::ret Responder::send(std::string_view response)
if (ack < total) {
ESP_LOGD(TAG, "all sending data are not ack (missing %d bytes acked)", (total - ack));
if (total - ack > 64) {
ESP_LOGW(TAG, "Need a pause: missing %d bytes acked", (total - ack));
ESP_LOGD(TAG, "Need a pause: missing %d bytes acked", (total - ack));
return ret::NEED_MORE_TIME;
}
}
@@ -284,7 +295,8 @@ Responder::ret Responder::send(std::string_view response)
Responder::ret Responder::connect(std::string_view response)
{
if (response.find("+QIOPEN: 0,0") != std::string::npos) {
std::string open_response = "+QIOPEN: " + std::to_string(link_id) + ",0";
if (response.find(open_response) != std::string::npos) {
ESP_LOGI(TAG, "Connected!");
return ret::OK;
}
@@ -295,10 +307,9 @@ Responder::ret Responder::connect(std::string_view response)
return Responder::ret::IN_PROGRESS;
}
Responder::ret Responder::check_async_replies(status state, std::string_view &response)
Responder::ret Responder::check_urc(status state, std::string_view &response)
{
ESP_LOGD(TAG, "response %.*s", static_cast<int>(response.size()), response.data());
if (response.find("+QIURC: \"recv\",0") != std::string::npos) {
if (response.find(std::string("+QIURC: \"recv\",") + std::to_string(link_id)) != std::string::npos) {
uint64_t data_ready = 1;
write(data_ready_fd, &data_ready, sizeof(data_ready));
ESP_LOGD(TAG, "Got data on modem!");
@@ -309,6 +320,9 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re
response = response.substr(head_pos + head.size());
int next_cr = response.find('\r');
if (next_cr != std::string::npos) {
if (next_cr < 2) {
return ret::IN_PROGRESS;
}
response = response.substr(next_cr - 2, next_cr);
if (response.find(",0") != std::string::npos) {
ESP_LOGV(TAG, "Receiving done");
@@ -318,12 +332,21 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re
ESP_LOGD(TAG, "Got data on modem!");
}
}
} else if (response.find("+QIURC: \"closed\",0") != std::string::npos) {
} else if (response.find(std::string("+QIURC: \"closed\",") + std::to_string(link_id)) != std::string::npos) {
ESP_LOGE(TAG, "Connection closed");
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());
if (state == status::SENDING) {
return send(response);
} else if (state == status::CONNECTING) {
}
if (state == status::CONNECTING) {
return connect(response);
}
return ret::IN_PROGRESS;
@@ -342,7 +365,7 @@ Responder::ret Responder::process_data(status state, uint8_t *data, size_t len)
status Responder::pending()
{
send_cmd("AT+QISEND=0,0\r");
send_cmd(std::string("AT+QISEND=") + std::to_string(link_id) + ",0\r");
return status::SENDING;
}

View File

@@ -58,12 +58,19 @@ command_result net_open(CommandableIf *t)
}
ESP_LOGI(TAG, "WiFi connected successfully");
// Set passive receive mode (1) for better control
ret = set_rx_mode(t, 1);
// Enable multiple connections mode
ret = dce_commands::generic_command(t, "AT+CIPMUX=1\r\n", "OK", "ERROR", 1000);
if (ret != command_result::OK) {
ESP_LOGE(TAG, "Failed to set preferred Rx mode");
ESP_LOGE(TAG, "Failed to enable multiple connections mode");
return ret;
}
ESP_LOGD(TAG, "Multiple connections mode enabled");
// Set passive receive mode (1) for better control
for (int i = 0; i < 2; i++) {
std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(i) + ",1\r\n";
dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000);
}
return command_result::OK;
}
@@ -78,49 +85,20 @@ command_result net_close(CommandableIf *t)
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)
{
return command_result::OK;
ESP_LOGV(TAG, "%s", __func__);
return dce_commands::generic_command(t, "AT+CIPCLOSE\r\n", "CLOSED", "ERROR", 5000);
// Use link ID 0 for closing connection
const int link_id = 0;
std::string close_cmd = "AT+CIPCLOSE=" + std::to_string(link_id) + "\r\n";
// In multiple connections mode, response format is: <link ID>,CLOSED
std::string expected_response = std::to_string(link_id) + ",CLOSED";
return dce_commands::generic_command(t, close_cmd, expected_response, "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)
{
@@ -150,9 +128,11 @@ command_result get_ip(CommandableIf *t, std::string &ip)
command_result set_rx_mode(CommandableIf *t, int mode)
{
ESP_LOGE(TAG, "%s", __func__);
ESP_LOGV(TAG, "%s", __func__);
// For multiple connections mode, set receive mode for link ID 0
const int link_id = 0;
// 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";
std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(link_id) + "," + std::to_string(mode) + "\r\n";
return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000);
}
@@ -164,17 +144,20 @@ void Responder::start_sending(size_t len)
{
data_to_send = len;
send_stat = 0;
send_cmd("AT+CIPSEND=" + std::to_string(len) + "\r\n");
// For multiple connections mode, include link ID
send_cmd("AT+CIPSEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r\n");
}
void Responder::start_receiving(size_t len)
{
send_cmd("AT+CIPRECVDATA=" + std::to_string(len) + "\r\n");
// For multiple connections mode, include link ID
send_cmd("AT+CIPRECVDATA=" + std::to_string(link_id) + "," + 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";
// For multiple connections mode, include link ID
std::string cmd = "AT+CIPSTART=" + std::to_string(link_id) + ",\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n";
send_cmd(cmd);
return true;
}
@@ -187,16 +170,12 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
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;
const std::string_view recv_data_view(recv_data, len);
const auto head_pos_found = recv_data_view.find(head);
if (head_pos_found == std::string_view::npos) {
return ret::IN_PROGRESS;
}
const auto *head_pos = recv_data + head_pos_found;
// Find the end of the length field
auto next_comma = (char *)memchr(head_pos + head.size(), ',', MIN_MESSAGE);
if (next_comma == nullptr) {
@@ -245,12 +224,25 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
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') {
if (ok_pos == nullptr) { // || ok_pos[1] != 'K') {
data_to_recv = 0;
ESP_LOGE(TAG, "Missed 'OK' marker");
return ret::OK;
return ret::FAIL;
}
if (ok_pos + 1 < recv_data + len && ok_pos[1] != 'K') {
// we ignore the condition when receiving 'O' as the last character in the last batch,
// don't wait for the 'K' in the next run, assume the data are valid and let higher layers deal with it.
data_to_recv = 0;
ESP_LOGE(TAG, "Missed 'OK' marker2");
return ret::FAIL;
}
}
if (ok_pos != nullptr && (char *)data + len - ok_pos - 2 > MIN_MESSAGE) {
// check for async replies after the Recv header
std::string_view response((char *)ok_pos + 2 /* OK */, (char *)data + len - ok_pos);
check_urc(status::RECEIVING, response);
}
// Reset and prepare for next receive
data_to_recv = 0;
return ret::OK;
@@ -299,7 +291,8 @@ Responder::ret Responder::send(std::string_view response)
Responder::ret Responder::connect(std::string_view response)
{
if (response.find("CONNECT") != std::string::npos) {
// In multiple connections mode, response format is: <link ID>,CONNECT
if (response.find(",CONNECT") != std::string::npos || response.find("CONNECT") != std::string::npos) {
ESP_LOGI(TAG, "TCP connected!");
return ret::OK;
}
@@ -309,6 +302,17 @@ Responder::ret Responder::connect(std::string_view response)
}
return ret::IN_PROGRESS;
}
Responder::ret Responder::check_urc(status state, std::string_view &response)
{
// Handle data notifications - in multiple connections mode, format is +IPD,<link ID>,<len>
std::string expected_urc = "+IPD," + std::to_string(link_id);
if (response.find(expected_urc) != std::string::npos) {
uint64_t data_ready = 1;
write(data_ready_fd, &data_ready, sizeof(data_ready));
ESP_LOGD(TAG, "Data available notification");
}
return ret::IN_PROGRESS;
}
Responder::ret Responder::check_async_replies(status state, std::string_view &response)
{
@@ -318,24 +322,17 @@ Responder::ret Responder::check_async_replies(status state, std::string_view &re
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");
ESP_LOGD(TAG, "WiFi disconnected");
}
// Handle TCP status messages
// Handle TCP status messages (multiple connections format: <link ID>,CONNECT or <link ID>,CLOSED)
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");
ESP_LOGD(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) {

View File

@@ -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: Apache-2.0
*/
@@ -18,13 +18,13 @@ using namespace esp_modem;
command_result net_open(CommandableIf *term)
{
ESP_LOGV(TAG, "%s", __func__ );
ESP_LOGV(TAG, "%s", __func__);
std::string response;
auto ret = dce_commands::generic_get_string(term, "AT+NETOPEN?\r", response, 1000);
if (ret != command_result::OK) {
return ret;
}
ESP_LOGV(TAG, "%s", response.data() );
ESP_LOGV(TAG, "%s", response.data());
if (response.find("+NETOPEN: 1") != std::string::npos) {
ESP_LOGD(TAG, "Already there");
ret = command_result::OK;
@@ -42,23 +42,23 @@ command_result net_open(CommandableIf *term)
command_result net_close(CommandableIf *term)
{
ESP_LOGV(TAG, "%s", __func__ );
ESP_LOGV(TAG, "%s", __func__);
return dce_commands::generic_command(term, "AT+NETCLOSE\r", "+NETCLOSE:", "ERROR", 30000);
}
command_result tcp_open(CommandableIf *term, const std::string &host, int port, int timeout)
{
ESP_LOGV(TAG, "%s", __func__ );
ESP_LOGV(TAG, "%s", __func__);
auto ret = dce_commands::generic_command(term, "AT+CIPRXGET=1\r", "OK", "ERROR", 50000);
if (ret != command_result::OK) {
ESP_LOGE(TAG, "Setting Rx mode failed!");
return ret;
}
ESP_LOGV(TAG, "%s", __func__ );
ESP_LOGV(TAG, "%s", __func__);
std::string ip_open = R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r";
ret = dce_commands::generic_command(term, ip_open, "+CIPOPEN: 0,0", "ERROR", timeout);
if (ret != command_result::OK) {
ESP_LOGE(TAG, "%s Failed", __func__ );
ESP_LOGE(TAG, "%s Failed", __func__);
return ret;
}
return command_result::OK;
@@ -66,13 +66,13 @@ command_result tcp_open(CommandableIf *term, const std::string &host, int port,
command_result tcp_close(CommandableIf *term)
{
ESP_LOGV(TAG, "%s", __func__ );
ESP_LOGV(TAG, "%s", __func__);
return dce_commands::generic_command(term, "AT+CIPCLOSE=0\r", "+CIPCLOSE:", "ERROR", 10000);
}
command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
{
ESP_LOGV(TAG, "%s", __func__ );
ESP_LOGV(TAG, "%s", __func__);
std::string send = "AT+CIPSEND=0," + std::to_string(len) + "\r";
auto ret = term->command(send, [&](uint8_t *data, size_t len) {
std::string_view response((char *)data, len);
@@ -86,10 +86,10 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
return ret;
}
ret = command_result::TIMEOUT;
ESP_LOGW(TAG, "Before setting...");
ESP_LOGD(TAG, "Before setting...");
term->on_read([&ret](uint8_t *cmd_data, size_t cmd_len) {
std::string_view response((char *)cmd_data, cmd_len);
ESP_LOGW(TAG, "CIPSEND response %.*s", static_cast<int>(response.size()), response.data());
ESP_LOGD(TAG, "CIPSEND response %.*s", static_cast<int>(response.size()), response.data());
if (response.find("+CIPSEND:") != std::string::npos) {
ret = command_result::OK;
@@ -98,7 +98,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
}
return ret;
});
ESP_LOGW(TAG, "Before writing...");
ESP_LOGD(TAG, "Before writing...");
auto written = term->write(data, len);
if (written != len) {
ESP_LOGE(TAG, "written %d (%d)...", written, len);
@@ -107,7 +107,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
uint8_t ctrl_z = '\x1A';
term->write(&ctrl_z, 1);
int count = 0;
while (ret == command_result::TIMEOUT && count++ < 1000 ) {
while (ret == command_result::TIMEOUT && count++ < 1000) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
term->on_read(nullptr);
@@ -116,7 +116,7 @@ command_result tcp_send(CommandableIf *term, uint8_t *data, size_t len)
command_result tcp_recv(CommandableIf *term, uint8_t *data, size_t len, size_t &out_len)
{
ESP_LOGV(TAG, "%s", __func__ );
ESP_LOGV(TAG, "%s", __func__);
std::string out;
auto ret = dce_commands::generic_get_string(term, "AT+CIPRXGET=4,0\r", out);
if (ret != command_result::OK) {
@@ -195,17 +195,17 @@ void Responder::start_sending(size_t len)
{
data_to_send = len;
send_stat = 0;
send_cmd("AT+CIPSEND=0," + std::to_string(len) + "\r");
send_cmd("AT+CIPSEND=" + std::to_string(link_id) + "," + std::to_string(len) + "\r");
}
void Responder::start_receiving(size_t len)
{
send_cmd("AT+CIPRXGET=2,0," + std::to_string(len) + "\r");
send_cmd("AT+CIPRXGET=2," + std::to_string(link_id) + "," + std::to_string(len) + "\r");
}
bool Responder::start_connecting(std::string host, int port)
{
send_cmd(R"(AT+CIPOPEN=0,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
send_cmd(std::string("AT+CIPOPEN=") + std::to_string(link_id) + R"(,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
return true;
}
@@ -215,7 +215,7 @@ Responder::ret Responder::recv(uint8_t *data, size_t len)
size_t actual_len = 0;
auto *recv_data = (char *)data;
if (data_to_recv == 0) {
static constexpr std::string_view head = "+CIPRXGET: 2,0,";
const std::string head = std::string("+CIPRXGET: 2,") + std::to_string(link_id) + ",";
auto head_pos = std::search(recv_data, recv_data + len, head.begin(), head.end());
if (head_pos == recv_data + len) {
return ret::FAIL;
@@ -329,7 +329,8 @@ Responder::ret Responder::send(std::string_view response)
Responder::ret Responder::connect(std::string_view response)
{
if (response.find("+CIPOPEN: 0,0") != std::string::npos) {
std::string open_response = "+CIPOPEN: " + std::to_string(link_id) + ",0";
if (response.find(open_response) != std::string::npos) {
ESP_LOGI(TAG, "Connected!");
return ret::OK;
}
@@ -340,14 +341,22 @@ Responder::ret Responder::connect(std::string_view response)
return Responder::ret::IN_PROGRESS;
}
Responder::ret Responder::check_async_replies(status state, std::string_view &response)
Responder::ret Responder::check_urc(status state, std::string_view &response)
{
ESP_LOGD(TAG, "response %.*s", static_cast<int>(response.size()), response.data());
if (response.find("+CIPRXGET: 1") != std::string::npos) {
// 1. When <mode> is set to 1 and the 2-4 mode will take effect.
// 2. If AT+CIPRXGET=1, it will report +CIPRXGET: 1,<cid>(multi client) when
const std::string expected = std::string("+CIPRXGET: 1,") + std::to_string(link_id);
if (response.find(expected) != std::string::npos) {
uint64_t data_ready = 1;
write(data_ready_fd, &data_ready, sizeof(data_ready));
ESP_LOGD(TAG, "Got data on modem!");
}
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());
if (state == status::SENDING) {
return send(response);
} else if (state == status::CONNECTING) {

View File

@@ -1,12 +1,14 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf: ">=4.1.0"
idf: '>=4.1.0'
espressif/esp_modem:
version: "^1.0.0"
override_path: "../../../"
version: ^2
override_path: ../../../
espressif/esp_modem_usb_dte:
version: "^1.2.0"
version: ^1.2.0
rules:
- if: "idf_version >=4.4"
- if: "target in [esp32s2, esp32s3, esp32p4]"
- if: idf_version >=4.4
- if: target in [esp32s2, esp32s3, esp32p4]
console_cmd_ping:
version: '*'

View File

@@ -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
*/
@@ -16,8 +16,10 @@
#include "freertos/event_groups.h"
#include "esp_netif.h"
#include "esp_netif_ppp.h"
#include "mqtt_client.h"
#include "esp_modem_api.h"
#include "esp_console.h"
#include "console_ping.h"
#include "esp_event.h"
#include "esp_log.h"
#include "sdkconfig.h"
@@ -35,7 +37,6 @@ static const char *TAG = "pppos_example";
static EventGroupHandle_t event_group = NULL;
static const int CONNECT_BIT = BIT0;
static const int DISCONNECT_BIT = BIT1;
static const int GOT_DATA_BIT = BIT2;
static const int USB_DISCONNECTED_BIT = BIT3; // Used only with USB DTE but we define it unconditionally, to avoid too many #ifdefs in the code
#ifdef CONFIG_EXAMPLE_MODEM_DEVICE_CUSTOM
@@ -64,47 +65,6 @@ if ((xEventGroupGetBits(event_group) & USB_DISCONNECTED_BIT) == USB_DISCONNECTED
#define CHECK_USB_DISCONNECTION(event_group)
#endif
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIu32, base, event_id);
esp_mqtt_event_handle_t event = event_data;
esp_mqtt_client_handle_t client = event->client;
int msg_id;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
msg_id = esp_mqtt_client_subscribe(client, CONFIG_EXAMPLE_MQTT_TEST_TOPIC, 0);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
msg_id = esp_mqtt_client_publish(client, CONFIG_EXAMPLE_MQTT_TEST_TOPIC, CONFIG_EXAMPLE_MQTT_TEST_DATA, 0, 0, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
xEventGroupSetBits(event_group, GOT_DATA_BIT);
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
break;
default:
ESP_LOGI(TAG, "MQTT other event id: %d", event->event_id);
break;
}
}
static void on_ppp_changed(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
@@ -160,6 +120,11 @@ void app_main(void)
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &on_ip_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, NULL));
// Initialize console REPL, register ping and start it
ESP_ERROR_CHECK(console_cmd_init());
ESP_ERROR_CHECK(console_cmd_ping_register());
ESP_ERROR_CHECK(console_cmd_start());
/* Configure the PPP netif */
esp_err_t err;
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_PPP_APN);
@@ -251,7 +216,7 @@ void app_main(void)
#endif
#if CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
xEventGroupClearBits(event_group, CONNECT_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_DETECT);
if (err != ESP_OK) {
@@ -270,7 +235,7 @@ void app_main(void)
}
#endif // CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
xEventGroupClearBits(event_group, CONNECT_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
/* Run the modem demo app */
#if CONFIG_EXAMPLE_NEED_SIM_PIN == 1
@@ -340,15 +305,11 @@ void app_main(void)
}
/* Config MQTT */
esp_mqtt_client_config_t mqtt_config = {
.broker.address.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
};
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(mqtt_client);
int ping_ret_val;
ESP_ERROR_CHECK(esp_console_run("ping www.espressif.com", &ping_ret_val));
ESP_LOGI(TAG, "Ping command finished with return value: %d", ping_ret_val);
#if CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
xEventGroupWaitBits(event_group, GOT_DATA_BIT, pdTRUE, pdFALSE, portMAX_DELAY);
esp_modem_pause_net(dce, true);
err = esp_modem_get_signal_quality(dce, &rssi, &ber);
if (err != ESP_OK) {
@@ -357,14 +318,15 @@ void app_main(void)
}
ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d", rssi, ber);
esp_modem_pause_net(dce, false);
esp_mqtt_client_publish(mqtt_client, CONFIG_EXAMPLE_MQTT_TEST_TOPIC, CONFIG_EXAMPLE_MQTT_TEST_DATA, 0, 0, 0);
ESP_ERROR_CHECK(esp_console_run("ping www.espressif.com", &ping_ret_val));
ESP_LOGI(TAG, "Ping command finished with return value: %d", ping_ret_val);
#endif // CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
ESP_LOGI(TAG, "Waiting for MQTT data");
xEventGroupWaitBits(event_group, GOT_DATA_BIT | USB_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
if (ping_ret_val != 0) {
ESP_LOGE(TAG, "Ping command failed with return value: %d", ping_ret_val);
}
CHECK_USB_DISCONNECTION(event_group);
esp_mqtt_client_destroy(mqtt_client);
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_COMMAND);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_modem_set_mode(ESP_MODEM_MODE_COMMAND) failed with %d", err);

View File

@@ -1,4 +1,4 @@
# 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
from __future__ import print_function, unicode_literals
@@ -8,16 +8,12 @@ def test_pppos_connect(dut):
steps:
1. initializes connection with SIM800
2. checks we get an IP
3. checks for the MQTT events
3. checks that the ping command works
4. checks that the client cleanly disconnects
"""
# Check the sequence of connecting, publishing, disconnecting
dut.expect('Modem Connect to PPP Server', timeout=90)
# Check for MQTT connection and the data event
dut.expect('MQTT_EVENT_CONNECTED')
dut.expect('MQTT_EVENT_DATA')
dut.expect('TOPIC=/ci/esp-modem/pppos-client')
dut.expect('DATA=esp32-pppos')
dut.expect('Ping command finished with return value: 0', timeout=30)
# Check that we have disconnected
dut.expect('User interrupted event')
# And can use commands again

View File

@@ -1,2 +1,2 @@
idf_component_register(SRCS "simple_cmux_client_main.cpp" "simple_mqtt_client.cpp"
idf_component_register(SRCS "simple_cmux_client_main.cpp"
INCLUDE_DIRS ".")

View File

@@ -1,4 +1,6 @@
dependencies:
espressif/esp_modem:
version: "^1.0.1"
version: "^2"
override_path: "../../../"
console_cmd_ping:
version: '*'

View File

@@ -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
*/
@@ -21,11 +21,13 @@
#include "cxx_include/esp_modem_dte.hpp"
#include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp"
#include "simple_mqtt_client.hpp"
#include "esp_vfs_dev.h" // For optional VFS support
#include "esp_https_ota.h" // For potential OTA configuration
#include "vfs_resource/vfs_create.hpp"
#include "SIM7070_gnss.hpp"
#include "esp_console.h"
#include "console_ping.h"
#include "esp_event.h"
#if defined(CONFIG_EXAMPLE_FLOW_CONTROL_NONE)
#define EXAMPLE_FLOW_CONTROL ESP_MODEM_FLOW_CONTROL_NONE
@@ -35,18 +37,13 @@
#define EXAMPLE_FLOW_CONTROL ESP_MODEM_FLOW_CONTROL_HW
#endif
#define BROKER_URL CONFIG_BROKER_URI
using namespace esp_modem;
static const char *TAG = "cmux_example";
class StatusHandler {
public:
static constexpr auto IP_Event = SignalGroup::bit0;
static constexpr auto MQTT_Connect = SignalGroup::bit1;
static constexpr auto MQTT_Data = SignalGroup::bit2;
static constexpr auto IP_Event = SignalGroup::bit0;
StatusHandler()
{
@@ -58,12 +55,6 @@ public:
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, on_event);
}
void handle_mqtt(MqttClient *client)
{
mqtt_client = client;
client->register_handler(ESP_EVENT_ANY_ID, on_event, this);
}
esp_err_t wait_for(decltype(IP_Event) event, int milliseconds)
{
return signal.wait_any(event, milliseconds);
@@ -80,8 +71,6 @@ private:
auto *handler = static_cast<StatusHandler *>(arg);
if (base == IP_EVENT) {
handler->ip_event(event, data);
} else {
handler->mqtt_event(event, data);
}
}
@@ -92,27 +81,16 @@ private:
ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask));
ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw));
ip_event_type = static_cast<ip_event_t>(id);
signal.set(IP_Event);
} else if (id == IP_EVENT_PPP_LOST_IP) {
ip_event_type = static_cast<ip_event_t>(id);
signal.set(IP_Event);
}
ip_event_type = static_cast<ip_event_t>(id);
}
void mqtt_event(int32_t event, void *data)
{
if (mqtt_client && event == mqtt_client->get_event(MqttClient::Event::CONNECT)) {
signal.set(MQTT_Connect);
} else if (mqtt_client && event == mqtt_client->get_event(MqttClient::Event::DATA)) {
ESP_LOGI(TAG, " TOPIC: %s", mqtt_client->get_topic(data).c_str());
ESP_LOGI(TAG, " DATA: %s", mqtt_client->get_data(data).c_str());
signal.set(MQTT_Data);
}
}
esp_modem::SignalGroup signal{};
MqttClient *mqtt_client{nullptr};
ip_event_t ip_event_type;
ip_event_t ip_event_type{};
};
@@ -122,6 +100,11 @@ extern "C" void app_main(void)
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
// Initialize console REPL, register ping and start it
ESP_ERROR_CHECK(console_cmd_init());
ESP_ERROR_CHECK(console_cmd_ping_register());
ESP_ERROR_CHECK(console_cmd_start());
/* Configure and create the DTE */
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
/* setup UART specific configuration based on kconfig options */
@@ -175,7 +158,7 @@ extern "C" void app_main(void)
#endif
assert(dce);
/* Try to connect to the network and publish an mqtt topic */
/* Try to connect to the network */
StatusHandler handler;
if (dte_config.uart_config.flow_control == ESP_MODEM_FLOW_CONTROL_HW) {
@@ -224,24 +207,14 @@ extern "C" void app_main(void)
} else if (handler.get_ip_event_type() == IP_EVENT_PPP_GOT_IP) {
std::cout << "Got IP address" << std::endl;
/* When connected to network, subscribe and publish some MQTT data */
MqttClient mqtt(BROKER_URL);
handler.handle_mqtt(&mqtt);
mqtt.connect();
if (!handler.wait_for(StatusHandler::MQTT_Connect, 60000)) {
ESP_LOGE(TAG, "Cannot connect to %s within specified timeout... exiting", BROKER_URL);
/* When connected to network, we can ping the internet */
int ping_ret_val;
ESP_ERROR_CHECK(esp_console_run("ping www.espressif.com", &ping_ret_val));
ESP_LOGI(TAG, "Ping command finished with return value: %d", ping_ret_val);
if (ping_ret_val != 0) {
ESP_LOGE(TAG, "Ping command failed with return value: %d", ping_ret_val);
return;
}
std::cout << "Connected" << std::endl;
mqtt.subscribe(CONFIG_EXAMPLE_MQTT_TEST_TOPIC);
mqtt.publish(CONFIG_EXAMPLE_MQTT_TEST_TOPIC, CONFIG_EXAMPLE_MQTT_TEST_DATA);
if (!handler.wait_for(StatusHandler::MQTT_Data, 60000)) {
ESP_LOGE(TAG, "Didn't receive published data within specified timeout... exiting");
return;
}
std::cout << "Received MQTT data" << std::endl;
} else if (handler.get_ip_event_type() == IP_EVENT_PPP_LOST_IP) {
ESP_LOGE(TAG, "PPP client has lost connection... exiting");
return;

View File

@@ -1,100 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* PPPoS Client Example
*/
#include <memory>
#include "mqtt_client.h"
#include "simple_mqtt_client.hpp"
/**
* Reference to the MQTT event base
*/
ESP_EVENT_DECLARE_BASE(MQTT_EVENTS);
/**
* Thin wrapper around C mqtt_client
*/
struct MqttClientHandle {
explicit MqttClientHandle(const std::string &uri)
{
esp_mqtt_client_config_t config = { };
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
config.broker.address.uri = uri.c_str();
#else
config.uri = uri.c_str();
#endif
client = esp_mqtt_client_init(&config);
}
~MqttClientHandle()
{
esp_mqtt_client_destroy(client);
}
esp_mqtt_client_handle_t client;
};
/**
* @brief Definitions of MqttClient methods
*/
MqttClient::MqttClient(const std::string &uri):
h(std::unique_ptr<MqttClientHandle>(new MqttClientHandle(uri)))
{}
void MqttClient::connect()
{
esp_mqtt_client_start(h->client);
}
int32_t MqttClient::get_event(MqttClient::Event ev)
{
switch (ev) {
case Event::CONNECT: {
return MQTT_EVENT_CONNECTED;
}
case Event::DATA:
return MQTT_EVENT_DATA;
}
return -1;
}
int MqttClient::publish(const std::string &topic, const std::string &data, int qos)
{
return esp_mqtt_client_publish(h->client, topic.c_str(), data.c_str(), 0, qos, 0);
}
int MqttClient::subscribe(const std::string &topic, int qos)
{
return esp_mqtt_client_subscribe(h->client, topic.c_str(), qos);
}
std::string MqttClient::get_topic(void *event_data)
{
auto event = (esp_mqtt_event_handle_t)event_data;
if (event == nullptr || event->client != h->client)
return {};
return std::string(event->topic, event->topic_len);
}
std::string MqttClient::get_data(void *event_data)
{
auto event = (esp_mqtt_event_handle_t)event_data;
if (event == nullptr || event->client != h->client)
return {};
return std::string(event->data, event->data_len);
}
void MqttClient::register_handler(int32_t event_id, esp_event_handler_t event_handler, void *arg)
{
ESP_ERROR_CHECK(esp_mqtt_client_register_event(h->client, MQTT_EVENT_ANY, event_handler, arg));
}
MqttClient::~MqttClient() = default;

View File

@@ -1,81 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/*
* PPPoS Client Example
*/
#pragma once
#include <string>
#include <memory>
struct MqttClientHandle;
/**
* @brief Simple MQTT client wrapper
*/
class MqttClient {
public:
enum class Event {
CONNECT,
DATA,
};
explicit MqttClient(const std::string &uri);
~MqttClient();
/**
* @brief Start the mqtt-client
*/
void connect();
/**
* @brief Publish to topic
* @param topic Topic to publish
* @param data Data to publish
* @param qos QoS (0 by default)
* @return message id
*/
int publish(const std::string &topic, const std::string &data, int qos = 0);
/**
* @brief Subscribe to a topic
* @param topic Topic to subscribe
* @param qos QoS (0 by default)
* @return message id
*/
int subscribe(const std::string &topic, int qos = 0);
/**
* @brief Get topic from event data
* @return String topic
*/
std::string get_topic(void *);
/**
* @brief Get published data from event
* @return String representation of the data
*/
std::string get_data(void *);
/**
* @brief Register MQTT event
* @param id Event id
* @param event_handler Event handler
* @param event_handler_arg Event handler parameters
*/
void register_handler(int32_t id, esp_event_handler_t event_handler, void *event_handler_arg);
/**
* @brief Convert internal MQTT event to standard ESPEvent
* @param ev internal mqtt event
* @return corresponding esp_event id
*/
static int32_t get_event(Event ev);
private:
std::unique_ptr<MqttClientHandle> h;
};

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
from __future__ import print_function, unicode_literals
@@ -6,21 +6,10 @@ from __future__ import print_function, unicode_literals
def test_cmux_connection(dut):
"""
steps:
1. initializes connection with SIM800
2. checks we get an IP
3. checks for the MQTT events
1. checks we're in CMUX mode and get an IP
2. checks for ping command
"""
# Get topic and data from Kconfig
topic = ''
data = ''
try:
topic = dut.app.sdkconfig.get('EXAMPLE_MQTT_TEST_TOPIC')
data = dut.app.sdkconfig.get('EXAMPLE_MQTT_TEST_DATA')
except Exception:
print('ENV_TEST_FAILURE: Cannot find broker url in sdkconfig')
raise
# Check the sequence of connecting, publishing, disconnecting
dut.expect('Modem has correctly entered multiplexed')
# Check for MQTT connection and the data event
dut.expect(f'TOPIC: {topic}')
dut.expect(f'DATA: {data}')
# Check we're in CMUX mode and get an IP
dut.expect('Modem has correctly entered multiplexed command/data mode', timeout=60)
# Check for ping command
dut.expect('Ping command finished with return value: 0', timeout=30)

View File

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

View File

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

View File

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

View File

@@ -163,6 +163,15 @@ extern "C" esp_err_t esp_modem_read_pin(esp_modem_dce_t *dce_wrap, bool *pin)
return command_response_to_esp_err(dce_wrap->dce->read_pin(*pin));
}
extern "C" esp_err_t esp_modem_set_echo(esp_modem_dce_t *dce_wrap, const bool echo_on)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
return command_response_to_esp_err(dce_wrap->dce->set_echo(echo_on));
}
extern "C" esp_err_t esp_modem_sms_txt_mode(esp_modem_dce_t *dce_wrap, bool txt)
{
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {

View File

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

View File

@@ -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));
}
}

View File

@@ -1,6 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_modem:
version: "^1.1.0"
version: "^2"
override_path: "../../.."
espressif/iperf-cmd: "^0.1.1"

View File

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

View File

@@ -0,0 +1,14 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_modem:
version: '^2'
override_path: ../../..
espressif/mbedtls_cxx:
version: '*'
override_path: ../../../../mbedtls_cxx
idf:
version: '>=4.1.0'
espressif/mqtt:
rules:
- if: idf_version >=6.0
version: ^1.0.0

View File

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

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

View File

@@ -3,5 +3,5 @@ dependencies:
## Required IDF version
idf: ">=4.1.0"
espressif/esp_modem:
version: "^1.0.0"
version: "^2"
override_path: "../../../"

View File

@@ -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");
}

View File

@@ -9,3 +9,7 @@ dependencies:
# Required IDF version
idf:
version: ">=5.0"
espressif/mqtt:
rules:
- if: idf_version >=6.0
version: ^1.0.0

View File

@@ -2,4 +2,4 @@ version: "4.3.3"
url: https://github.com/espressif/esp-protocols/tree/master/components/libwebsockets
description: The component provides a simple ESP-IDF port of libwebsockets client.
dependencies:
idf: '>=5.3'
idf: '>=5.3,<6.0'

View File

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

View File

@@ -1,5 +1,12 @@
# 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

View File

@@ -1,4 +1,4 @@
version: "1.9.0"
version: "1.9.1"
description: "Multicast UDP service used to provide local network service and host discovery."
url: "https://github.com/espressif/esp-protocols/tree/master/components/mdns"
issues: "https://github.com/espressif/esp-protocols/issues"

View File

@@ -33,7 +33,7 @@ static void _mdns_browse_send(mdns_browse_t *browse, mdns_if_t interface);
#if ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(5, 1, 0)
#define MDNS_ESP_WIFI_ENABLED CONFIG_SOC_WIFI_SUPPORTED
#else
#define MDNS_ESP_WIFI_ENABLED CONFIG_ESP_WIFI_ENABLED
#define MDNS_ESP_WIFI_ENABLED (CONFIG_ESP_WIFI_ENABLED || CONFIG_ESP_WIFI_REMOTE_ENABLED)
#endif
#if MDNS_ESP_WIFI_ENABLED && (CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP)
@@ -4493,9 +4493,9 @@ void mdns_preset_if_handle_system_event(void *arg, esp_event_base_t event_base,
return;
}
esp_netif_dhcp_status_t dcst;
#if MDNS_ESP_WIFI_ENABLED && (CONFIG_MDNS_PREDEF_NETIF_STA || CONFIG_MDNS_PREDEF_NETIF_AP)
if (event_base == WIFI_EVENT) {
esp_netif_dhcp_status_t dcst;
switch (event_id) {
case WIFI_EVENT_STA_CONNECTED:
if (!esp_netif_dhcpc_get_status(esp_netif_from_preset_if(MDNS_IF_STA), &dcst)) {
@@ -4522,6 +4522,7 @@ void mdns_preset_if_handle_system_event(void *arg, esp_event_base_t event_base,
#endif
#if CONFIG_ETH_ENABLED && CONFIG_MDNS_PREDEF_NETIF_ETH
if (event_base == ETH_EVENT) {
esp_netif_dhcp_status_t dcst;
switch (event_id) {
case ETHERNET_EVENT_CONNECTED:
if (!esp_netif_dhcpc_get_status(esp_netif_from_preset_if(MDNS_IF_ETH), &dcst)) {

View File

@@ -90,6 +90,10 @@ if (CONFIG_MOSQ_ENABLE_SYS)
endif()
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
# Enable linker wrapping for mosquitto_unpwd_check to allow connection callback interception
# without modifying upstream code
target_link_options(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=mosquitto_unpwd_check")
# Some mosquitto source unconditionally define `_GNU_SOURCE` which collides with IDF build system
# producing warning: "_GNU_SOURCE" redefined
# This workarounds this issue by undefining the macro for the selected files

View File

@@ -15,6 +15,7 @@
| Type | Name |
| ---: | :--- |
| struct | [**mosq\_broker\_config**](#struct-mosq_broker_config) <br>_Mosquitto configuration structure._ |
| typedef int(\* | [**mosq\_connect\_cb\_t**](#typedef-mosq_connect_cb_t) <br> |
| typedef void(\* | [**mosq\_message\_cb\_t**](#typedef-mosq_message_cb_t) <br> |
## Functions
@@ -35,6 +36,8 @@ ESP port of mosquittto supports only the options in this configuration structure
Variables:
- mosq\_connect\_cb\_t handle_connect_cb <br>On connect callback. If configured, user function is called whenever a client attempts to connect. The callback receives client\_id, username, password, and password length. Return 0 to accept the connection, non-zero to reject it.
- void(\* handle_message_cb <br>On message callback. If configured, user function is called whenever mosquitto processes a message.
- const char \* host <br>Address on which the broker is listening for connections
@@ -43,6 +46,12 @@ Variables:
- esp\_tls\_cfg\_server\_t \* tls_cfg <br>ESP-TLS configuration (if TLS transport used) Please refer to the ESP-TLS official documentation for more details on configuring the TLS options. You can open the respective docs with this idf.py command: `idf.py docs -sp api-reference/protocols/esp_tls.html`
### typedef `mosq_connect_cb_t`
```c
typedef int(* mosq_connect_cb_t) (const char *client_id, const char *username, const char *password, int password_len);
```
### typedef `mosq_message_cb_t`
```c

View File

@@ -19,6 +19,15 @@ menu "Example Configuration"
If enabled, it runs a local mqtt client connecting
to the same endpoint ans the broker listens to
config EXAMPLE_BROKER_USE_BASIC_AUTH
bool "Use basic authentication (username/password)"
default n
help
If enabled, the broker will require username and password
authentication. The example uses "testuser" / "testpass" as
credentials. The client will also use these credentials when
connecting to the broker.
config EXAMPLE_BROKER_WITH_TLS
bool "Use TLS"
default y

View File

@@ -1,9 +1,10 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
@@ -14,6 +15,45 @@
const static char *TAG = "mqtt_broker";
/* Basic auth credentials for the example */
#define EXAMPLE_USERNAME "testuser"
#define EXAMPLE_PASSWORD "testpass"
#if CONFIG_EXAMPLE_BROKER_USE_BASIC_AUTH
/* Connection callback to validate username/password */
static int example_connect_callback(const char *client_id, const char *username, const char *password, int password_len)
{
ESP_LOGI(TAG, "Connection attempt from client_id='%s', username='%s'", client_id, username ? username : "(none)");
/* Check if username is provided */
if (!username) {
ESP_LOGW(TAG, "Connection rejected: no username provided");
return 1; /* Reject connection */
}
/* Check if password is provided */
if (!password) {
ESP_LOGW(TAG, "Connection rejected: no password provided");
return 1; /* Reject connection */
}
/* Validate username */
if (strcmp(username, EXAMPLE_USERNAME) != 0) {
ESP_LOGW(TAG, "Connection rejected: invalid username '%s'", username);
return 1; /* Reject connection */
}
/* Validate password */
if (strcmp(password, EXAMPLE_PASSWORD) != 0) {
ESP_LOGW(TAG, "Connection rejected: invalid password");
return 1; /* Reject connection */
}
ESP_LOGI(TAG, "Connection accepted for client_id='%s', username='%s'", client_id, username);
return 0; /* Accept connection */
}
#endif /* CONFIG_EXAMPLE_BROKER_USE_BASIC_AUTH */
#if CONFIG_EXAMPLE_BROKER_WITH_TLS
extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start");
extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end");
@@ -81,6 +121,10 @@ static void mqtt_app_start(struct mosq_broker_config *config)
.broker.address.transport = MQTT_TRANSPORT_OVER_TCP,
#endif
.broker.address.port = config->port,
#if CONFIG_EXAMPLE_BROKER_USE_BASIC_AUTH
.credentials.username = "EXAMPLE_USERNAME",
.credentials.authentication.password = EXAMPLE_PASSWORD,
#endif
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
@@ -95,7 +139,16 @@ void app_main(void)
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect());
struct mosq_broker_config config = { .host = CONFIG_EXAMPLE_BROKER_HOST, .port = CONFIG_EXAMPLE_BROKER_PORT, .tls_cfg = NULL };
struct mosq_broker_config config = {
.host = CONFIG_EXAMPLE_BROKER_HOST,
.port = CONFIG_EXAMPLE_BROKER_PORT,
.tls_cfg = NULL,
#if CONFIG_EXAMPLE_BROKER_USE_BASIC_AUTH
.handle_connect_cb = example_connect_callback,
#else
.handle_connect_cb = NULL,
#endif
};
#if CONFIG_EXAMPLE_BROKER_RUN_LOCAL_MQTT_CLIENT
mqtt_app_start(&config);

View File

@@ -4,3 +4,7 @@ dependencies:
override_path: "../../.."
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
espressif/mqtt:
rules:
- if: idf_version >=6.0
version: ^1.0.0

View File

@@ -3,3 +3,7 @@ dependencies:
espressif/mosquitto:
override_path: ../../..
espressif/sock_utils: "*"
espressif/mqtt:
rules:
- if: idf_version >=6.0
version: ^1.0.0

View File

@@ -102,6 +102,7 @@ void mosq_broker_stop(void)
}
extern mosq_message_cb_t g_mosq_message_callback;
extern mosq_connect_cb_t g_mosq_connect_callback;
int mosq_broker_run(struct mosq_broker_config *broker_config)
{
@@ -130,6 +131,9 @@ int mosq_broker_run(struct mosq_broker_config *broker_config)
if (broker_config->handle_message_cb) {
g_mosq_message_callback = broker_config->handle_message_cb;
}
if (broker_config->handle_connect_cb) {
g_mosq_connect_callback = broker_config->handle_connect_cb;
}
db.config = &config;

View File

@@ -3,8 +3,9 @@
*
* SPDX-License-Identifier: EPL-2.0
*
* SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileContributor: 2024-2025 Espressif Systems (Shanghai) CO LTD
*/
#include <string.h>
#include "mosquitto_internal.h"
#include "mosquitto_broker.h"
#include "memory_mosq.h"
@@ -16,6 +17,7 @@
#include "mosq_broker.h"
mosq_message_cb_t g_mosq_message_callback = NULL;
mosq_connect_cb_t g_mosq_connect_callback = NULL;
int mosquitto_callback_register(
mosquitto_plugin_id_t *identifier,
@@ -51,3 +53,39 @@ int plugin__handle_message(struct mosquitto *context, struct mosquitto_msg_store
}
return MOSQ_ERR_SUCCESS;
}
int __real_mosquitto_unpwd_check(struct mosquitto *context);
/* Wrapper function to intercept mosquitto_unpwd_check calls via linker wrapping */
int __wrap_mosquitto_unpwd_check(struct mosquitto *context)
{
int rc;
int password_len = 0;
/* Call user's connect callback if set */
if (g_mosq_connect_callback) {
/* Extract password length if password is present.
* Note: MQTT passwords are binary data, but mosquitto stores them as null-terminated strings.
* If password contains null bytes, strlen() will not return the full length.
* This matches how mosquitto itself handles passwords in some security functions. */
if (context->password) {
password_len = (int)strlen(context->password);
}
/* Call user callback */
rc = g_mosq_connect_callback(
context->id ? context->id : "",
context->username ? context->username : NULL,
context->password ? context->password : NULL,
password_len
);
/* If callback rejects (returns non-zero), return AUTH error immediately */
if (rc != 0) {
return MOSQ_ERR_AUTH;
}
}
/* Call the original function */
return __real_mosquitto_unpwd_check(context);
}

View File

@@ -14,6 +14,8 @@ extern "C" {
struct mosquitto__config;
typedef void (*mosq_message_cb_t)(char *client, char *topic, char *data, int len, int qos, int retain);
typedef int (*mosq_connect_cb_t)(const char *client_id, const char *username, const char *password, int password_len);
/**
* @brief Mosquitto configuration structure
*
@@ -33,6 +35,11 @@ struct mosq_broker_config {
* On message callback. If configured, user function is called
* whenever mosquitto processes a message.
*/
mosq_connect_cb_t handle_connect_cb; /*!< On connect callback. If configured, user function is called
* whenever a client attempts to connect. The callback receives
* client_id, username, password, and password length. Return 0 to
* accept the connection, non-zero to reject it.
*/
};
/**

View File

@@ -176,6 +176,41 @@ Component Kconfig
Compile-time configuration is provided using menuconfig. Please check
the description for the CMUX mode configuration options.
Development Mode
~~~~~~~~~~~~~~~~
ESP-MODEM supports two different modes for handling AT command definitions:
**Production Mode (Default)**
Uses pre-generated headers and sources with common AT commands from the ``command/`` directory.
This mode provides better IDE navigation and code completion, making it ideal for:
- Application development using existing AT commands
- Better code navigation and IntelliSense support
- Faster compilation times
- Stable, tested command implementations
**Development Mode**
Uses in-place macro expansion with AT commands from the ``generate/`` directory.
This mode is designed for ESP-MODEM library developers who need to:
- Modify or add new AT command definitions in ``esp_modem_command_declare.inc``
- Work directly with the source definitions of AT commands
- Debug command implementations
- Contribute to the core ESP-MODEM library development
.. note::
For adding support for new modem modules, you typically don't need development mode.
Instead, create a custom module class inheriting from ``GenericModule`` and add your
commands directly in your project's headers/sources. See the ``pppos_client`` example
for a demonstration of this approach.
To enable development mode, set ``CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE=y`` in your project configuration.
.. note::
Development mode requires the C preprocessor to expand command definitions at compile time,
which may result in longer compilation times and larger binary sizes compared to production mode.
Runtime configuration
~~~~~~~~~~~~~~~~~~~~~

View File

@@ -38,6 +38,15 @@ work with commands differently. This might be useful to add some custom preproce
Please check the ``modem_console`` example with ``CONFIG_EXAMPLE_MODEM_DEVICE_SHINY=y`` configuration which demonstrates
overriding default ``command()`` method to implement URC processing in user space.
Enhanced URC (Unsolicited Result Code) Handling
------------------------------------------------
The ESP modem library provides two interfaces for handling URCs: a legacy callback-based interface and an enhanced interface with granular buffer consumption control. The enhanced interface, available through :cpp:func:`esp_modem::DCE_T::set_enhanced_urc`, provides complete buffer visibility and allows URC handlers to make precise decisions about buffer consumption. This is particularly useful for processing multi-part responses or when you need to consume only specific portions of the buffer while preserving other data for command processing.
The enhanced URC handler receives a :cpp:struct:`esp_modem::DTE::UrcBufferInfo` structure containing the complete buffer context, including what data has been processed, what's new, and whether a command is currently active. The handler returns a :cpp:struct:`esp_modem::DTE::UrcConsumeInfo` structure specifying how much of the buffer to consume: none (wait for more data), partial (consume specific amount), or all (consume entire buffer). This granular control enables sophisticated URC processing scenarios such as line-by-line parsing of chunked HTTP responses or selective processing based on command state.
For applications migrating from the legacy URC interface, the enhanced interface maintains backward compatibility while providing significantly more control over buffer management. The legacy :cpp:func:`esp_modem::DCE_T::set_urc` method continues to work as before, but new applications should consider using the enhanced interface for better buffer control and processing flexibility.
Create new communication interface
----------------------------------

View File

@@ -0,0 +1,202 @@
Customization
=============
This chapter covers how to customize ESP-MODEM for your specific requirements by creating custom modules and adding new commands.
Custom Module Development
-------------------------
For most customization needs, you don't need development mode. Instead, you can create custom modules that inherit from existing ESP-MODEM classes.
Creating Custom Modules
~~~~~~~~~~~~~~~~~~~~~~~
The recommended approach for adding support for new modem modules or custom commands is to create a custom module class. This approach:
- **Doesn't require development mode**
- **Keeps your changes separate** from the core library
- **Allows easy updates** of the ESP-MODEM library
- **Provides full flexibility** for custom commands
Basic Custom Module Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here's a simple example of creating a custom module:
.. code-block:: cpp
#include "cxx_include/esp_modem_api.hpp"
#include "cxx_include/esp_modem_command_library_utils.hpp"
class MyCustomModule: public GenericModule {
using GenericModule::GenericModule;
public:
// Add a new command
command_result get_custom_info(std::string &info) {
return esp_modem::dce_commands::generic_get_string(
dte.get(), "AT+CUSTOM?\r", info);
}
// Override an existing command
command_result get_signal_quality(int &rssi, int &ber) override {
// Custom implementation
return esp_modem::dce_commands::generic_get_string(
dte.get(), "AT+CSQ\r", rssi, ber);
}
};
Using Custom Modules with C++ API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
With the C++ API, you can use your custom module directly:
.. code-block:: cpp
// Create DCE with custom module
auto dce = dce_factory::Factory::create_unique_dce_from<MyCustomModule>(
dce_config, std::move(dte), netif);
// Use custom commands
std::string info;
auto result = dce->get_custom_info(info);
Using Custom Modules with C API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To use custom modules with the C API, you need to:
1. **Enable custom module support** in Kconfig:
.. code-block:: bash
idf.py menuconfig
# Navigate to: Component config → ESP-MODEM
# Enable: "Add support for custom module in C-API"
2. **Create a custom module header** (e.g., ``custom_module.hpp``) in your main component
3. **Implement the required functions**:
.. code-block:: cpp
// Create custom DCE function
DCE *esp_modem_create_custom_dce(
const esp_modem_dce_config_t *dce_config,
std::shared_ptr<DTE> dte,
esp_netif_t *netif) {
return dce_factory::Factory::create_unique_dce_from<MyCustomModule, DCE *>(
dce_config, std::move(dte), netif);
}
// Add C API wrappers for custom commands
extern "C" esp_err_t esp_modem_get_custom_info(esp_modem_dce_t *dce_wrap, char *info) {
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
return ESP_ERR_INVALID_ARG;
}
std::string info_str{CONFIG_ESP_MODEM_C_API_STR_MAX};
auto ret = command_response_to_esp_err(
static_cast<MyCustomModule *>(dce_wrap->dce->get_module())->get_custom_info(info_str));
if (ret == ESP_OK && !info_str.empty()) {
strlcpy(info, info_str.c_str(), CONFIG_ESP_MODEM_C_API_STR_MAX);
}
return ret;
}
4. **Use the custom commands** in your C code:
.. code-block:: c
char info[128];
esp_err_t ret = esp_modem_get_custom_info(dce, info);
Complete Example
~~~~~~~~~~~~~~~~
See the ``examples/pppos_client`` example for a complete demonstration of custom module development. This example shows:
- Creating a custom module that inherits from ``GenericModule``
- Adding new commands (``get_time()``)
- Overriding existing commands (``get_signal_quality()``)
- Integration with both C++ and C APIs
Available Base Classes
----------------------
You can inherit from several base classes depending on your needs:
**GenericModule**
The most general implementation of a common modem. Use this when:
- Your modem supports most standard AT commands
- You need to add a few custom commands
- You want to override some existing commands
**Specific Module Classes**
Inherit from existing module classes (e.g., ``SIM800``, ``BG96``, ``SIM7600``) when:
- Your modem is very similar to an existing one
- You only need minor modifications
- You want to leverage existing device-specific optimizations
**ModuleIf**
Use this minimal interface when:
- You only need basic AT command functionality
- You don't need network interface features
- You want to implement a custom DTE without DCE
Command Utilities
-----------------
ESP-MODEM provides utility functions to help implement custom commands:
**Generic Command Helpers**
- ``generic_get_string()`` - Parse string responses
- ``generic_get_int()`` - Parse integer responses
- ``generic_set_string()`` - Send string commands
- ``generic_set_int()`` - Send integer commands
**Response Parsing**
- ``get_number_from_string()`` - Extract numbers from responses
- ``get_string_from_response()`` - Extract strings from responses
- ``get_urc()`` - Handle unsolicited result codes
Example Usage:
.. code-block:: cpp
// Get a string value
command_result get_imei(std::string &imei) {
return esp_modem::dce_commands::generic_get_string(
dte.get(), "AT+CGSN\r", imei);
}
// Get an integer value
command_result get_signal_strength(int &rssi) {
return esp_modem::dce_commands::generic_get_int(
dte.get(), "AT+CSQ\r", rssi);
}
Best Practices
--------------
**For Application Developers:**
- Use production mode for better IDE support and faster builds
- Create custom modules for new modem support
- Inherit from ``GenericModule`` or other existing modules
- Keep customizations in your project, not in the ESP-MODEM library
**Module Design:**
- Choose the most appropriate base class for your needs
- Override only the commands you need to modify
- Use the provided utility functions for common operations
- Follow the existing command naming conventions
**Testing:**
- Test your custom module with real hardware
- Verify compatibility with existing ESP-MODEM features
- Test both C++ and C API usage if applicable
- Consider edge cases and error handling
**Documentation:**
- Document your custom commands clearly
- Provide usage examples
- Explain any device-specific requirements
- Note any limitations or known issues

View File

@@ -0,0 +1,203 @@
Development
===========
This chapter covers development mode, build system, and workflow for ESP-MODEM library developers.
Development Mode
----------------
ESP-MODEM supports two different modes for handling AT command definitions, each optimized for different use cases.
Production Mode (Default)
~~~~~~~~~~~~~~~~~~~~~~~~~~
Production mode uses pre-generated headers and sources from the ``command/`` directory. This mode is ideal for:
- **Application development** using existing AT commands
- **Better IDE navigation** and code completion
- **Faster compilation times** with stable, tested implementations
- **End users** who don't need to modify the core library
Development Mode
~~~~~~~~~~~~~~~~
Development mode uses in-place macro expansion from the ``generate/`` directory. This mode is designed for:
- **ESP-MODEM library developers** contributing to the core library
- **Modifying core AT command definitions** in ``esp_modem_command_declare.inc``
- **Debugging command implementations** at the source level
- **Working directly with command source definitions**
To enable development mode:
.. code-block:: bash
idf.py menuconfig
# Navigate to: Component config → ESP-MODEM
# Enable: "Use development mode"
Or set the configuration directly:
.. code-block:: bash
idf.py -D CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE=y build
.. note::
Development mode requires C preprocessor expansion at compile time, which may result in longer compilation times and larger binary sizes.
When to Use Development Mode
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Use Development Mode when:**
- Contributing to the ESP-MODEM library itself
- Modifying core AT command definitions in ``esp_modem_command_declare.inc``
- Debugging issues in the command library
- Adding new commands to the core library for all users
**Use Production Mode when:**
- Developing applications with ESP-MODEM
- Adding support for new modem modules (see :ref:`customization`)
- Creating custom commands for specific projects
- General application development
Build System
------------
The ESP-MODEM build system automatically handles the differences between production and development modes:
- **Production mode**: Uses pre-generated sources from ``command/`` directory
- **Development mode**: Uses macro expansion from ``generate/`` directory
The build system selects the appropriate source directory based on the ``CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE`` configuration.
Directory Structure
~~~~~~~~~~~~~~~~~~~
ESP-MODEM uses two parallel directory structures:
**``generate/`` Directory (Development Mode)**
Contains source files with macro definitions that get expanded at compile time:
- ``generate/include/esp_modem_command_declare.inc`` - Core AT command definitions
- ``generate/include/cxx_include/`` - C++ header templates
- ``generate/src/`` - Source file templates
**``command/`` Directory (Production Mode)**
Contains pre-generated, expanded source files:
- ``command/include/esp_modem_api.h`` - Generated C API
- ``command/include/cxx_include/`` - Generated C++ headers
- ``command/src/`` - Generated source files
Code Generation
~~~~~~~~~~~~~~~
ESP-MODEM uses a sophisticated code generation system to create the pre-generated sources. The ``generate.sh`` script:
- Processes AT command definitions from ``esp_modem_command_declare.inc``
- Uses C preprocessor metaprogramming to expand command prototypes
- Generates both C and C++ API headers
- Formats the generated code for consistency
Generate Script Usage
~~~~~~~~~~~~~~~~~~~~~
The ``generate.sh`` script is located in ``components/esp_modem/scripts/`` and can be used to:
**Generate all default files:**
.. code-block:: bash
./scripts/generate.sh
**Generate specific files:**
.. code-block:: bash
./scripts/generate.sh generate/include/cxx_include/esp_modem_command_library.hpp
**Generate files for documentation:**
.. code-block:: bash
./scripts/generate.sh ../../docs/esp_modem/generate/dce.rst
The script automatically:
- Determines the correct compiler (clang/clang++) based on file extension
- Expands macros using C preprocessor
- Formats generated code with astyle
- Handles different file types (.hpp, .cpp, .h, .rst)
Developer Workflow
------------------
Adding New AT Commands
~~~~~~~~~~~~~~~~~~~~~~
To add new AT commands to the core ESP-MODEM library:
1. **Enable development mode** in your project
2. **Edit** ``components/esp_modem/generate/include/esp_modem_command_declare.inc``
3. **Add your command definition** using the ``ESP_MODEM_DECLARE_DCE_COMMAND`` macro:
.. code-block:: c
/**
* @brief Your new command description
* @param[in] param1 Description of parameter 1
* @param[out] param2 Description of parameter 2
* @return OK, FAIL or TIMEOUT
*/
ESP_MODEM_DECLARE_DCE_COMMAND(your_new_command, command_result,
STR_IN(param1), INT_OUT(param2))
4. **Test your changes** in development mode
5. **Generate production files** using ``generate.sh``
6. **Test in production mode** to ensure compatibility
7. **Submit your changes** with appropriate tests
Command Definition Macros
~~~~~~~~~~~~~~~~~~~~~~~~~~
ESP-MODEM provides several macros for defining commands:
- ``ESP_MODEM_DECLARE_DCE_COMMAND`` - Standard command declaration
- ``STR_IN(param)`` - String input parameter
- ``STR_OUT(param)`` - String output parameter
- ``INT_IN(param)`` - Integer input parameter
- ``INT_OUT(param)`` - Integer output parameter
Testing and Validation
~~~~~~~~~~~~~~~~~~~~~~
When developing ESP-MODEM library changes:
1. **Test in both modes** - Ensure your changes work in both development and production modes
2. **Run existing tests** - Verify you don't break existing functionality
3. **Test with multiple modules** - Ensure compatibility across different modem modules
4. **Validate generated code** - Check that generated files are correct and properly formatted
5. **Update documentation** - Add documentation for new commands or features
CI/CD Integration
~~~~~~~~~~~~~~~~~
The ESP-MODEM project includes automated testing that:
- **Validates generated files** - Ensures generated sources are up-to-date
- **Tests both modes** - Runs tests in both development and production modes
- **Checks formatting** - Validates code formatting and style
- **Builds examples** - Ensures examples work with changes
Best Practices
--------------
**For Library Developers:**
- Use development mode when modifying core library files
- Test changes in both production and development modes
- Follow the existing code generation patterns
- Update documentation when adding new commands
- Include appropriate tests for new functionality
**For Contributors:**
- Submit changes that work in both modes
- Include appropriate tests
- Update relevant documentation
- Consider backward compatibility
- Follow the existing coding style and patterns

View File

@@ -4,8 +4,11 @@ ESP-MODEM Programmers manual
.. toctree::
Brief intro <README>
Migration Guide <migration_guide>
C interface <api_docs>
C++ interface <cxx_api_docs>
Advanced use cases <advanced_api>
Development <development>
Customization <customization>
Internal design <internal_design>
Internal implementation <internal_docs>

View File

@@ -0,0 +1,64 @@
Migration Guide
===============
ESP-MODEM v2.0 introduces production mode (default) with pre-generated sources for better IDE navigation. Previous behavior (development mode) requires explicit configuration.
Breaking Changes
----------------
**Production Mode (Default)**
- Uses pre-generated sources from ``command/`` directory
- Better IDE navigation and code completion
- Faster compilation
**Development Mode (Optional)**
- Uses macro expansion from ``generate/`` directory
- Enable with ``CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE=y``
- Required for modifying core ESP-MODEM files
Migration Steps
---------------
**Application Developers:**
No changes required. Production mode is default.
**Library Developers:**
Enable development mode:
.. code-block:: bash
idf.py -D CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE=y build
**Custom ``*.inc`` Files:**
Use generation script:
.. code-block:: bash
./components/esp_modem/scripts/generate.sh your_file.inc
**Build:**
.. code-block:: bash
idf.py fullclean
idf.py build
New Features (Coming Soon)
--------------------------
**Better URC Handling**
- Enhanced unsolicited result code processing
- Existing URC code remains compatible
**AT-based Networking**
- AT command networking examples now supports multiple connections
Troubleshooting
---------------
**Build errors:** ``idf.py fullclean && idf.py build``
**No IDE completion:** Use production mode (default)
**Custom .inc files:** Use ``./components/esp_modem/scripts/generate.sh your_file.inc``
**Modify core files:** Enable ``CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE=y``

View File

@@ -18,6 +18,7 @@
#include "esp_log.h"
#include "sdkconfig.h"
#include "iface_info.h"
#include "esp_idf_version.h"
static const char *TAG = "ethernet_connect";
@@ -109,7 +110,11 @@ iface_info_t *example_eth_init(int prio)
// Use internal ESP32's ethernet
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
eth_info->mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)
eth_info->phy = esp_eth_phy_new_generic(&phy_config);
#else
eth_info->phy = esp_eth_phy_new_ip101(&phy_config);
#endif
// Init Ethernet driver to default and install it
esp_eth_config_t config = ETH_DEFAULT_CONFIG(eth_info->mac, eth_info->phy);
ESP_ERROR_CHECK(esp_eth_driver_install(&config, &eth_info->eth_handle));

View File

@@ -1,4 +1,8 @@
dependencies:
espressif/esp_modem:
version: "^1.0.0"
version: "^2"
override_path: "../../../../components/esp_modem"
espressif/mqtt:
rules:
- if: idf_version >=6.0
version: ^1.0.0

View File

@@ -1,7 +1,12 @@
# SLIP Modem Component
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3")
set(dependencies esp_driver_uart)
else()
set(dependencies driver)
endif()
idf_component_register(
SRCS "library/slip_modem.c" "library/slip_modem_netif.c"
INCLUDE_DIRS "include"
REQUIRES esp_netif driver
REQUIRES esp_netif ${dependencies}
)

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -53,10 +53,18 @@ esp_err_t slip_modem_netif_start(esp_netif_t *esp_netif, esp_ip6_addr_t *addr)
return ESP_OK;
}
#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
/**
* @brief Write incoming serial data to the SLIP interface
*/
void esp_netif_lwip_slip_input(void *h, void *buffer, unsigned int len, void *eb)
esp_netif_recv_ret_t esp_netif_lwip_slip_input(void *h, void *buffer, unsigned int len, void *eb)
{
struct netif *netif = h;
@@ -76,6 +84,7 @@ void esp_netif_lwip_slip_input(void *h, void *buffer, unsigned int len, void *eb
for (int i = 0; i < len; i++) {
slipif_process_rxqueue(netif);
}
return ESP_NETIF_OPTIONAL_RETURN_CODE(ESP_OK);
}
/**

View File

@@ -1,3 +1,7 @@
dependencies:
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
espressif/mqtt:
rules:
- if: idf_version >=6.0
version: ^1.0.0