Compare commits

..

38 Commits

Author SHA1 Message Date
b992ef6914 Merge pull request #346 from gabsuren/ci/update_websocket_1.1.0
bump(websocket): 1.0.1 -> 1.1.0
2023-08-28 12:52:37 +04:00
9cc594afaa Merge pull request #312 from david-cermak/fix/minor_modem_refactor
fix(modem): Refactor of DTE callbacks (and other fixes)
2023-08-28 10:49:18 +02:00
4cf9e505e1 feat(modem): Add factory method for simple creation of custom DCEs
This allows calling directly:
auto dce = dce_factory::Factory::create_unique_dce_from<SIM7600>(&dce_config, uart_dte, esp_netif);
instead of:
auto dce = create_SIM7600_dce(&dce_config, uart_dte, esp_netif);
Which is very useful when adding a custom module, so we won't need to
update factory layers and add the new device to enums, etc.
To add a new module, you just:
1) Define the module class NewModule: public GenericModule { };
2) Implement the specific commands:
command_result do_work_new_module(CommandableIf *t, params) {}
3) Connect 1) and 2)
command_result NewModule::do_work_new_module(params...)
{
    return dce_commands::do_work_new_module(dte.get(), params...);
}
2023-08-28 10:27:16 +02:00
c8c05075fb fix(modem): Fix netif data race causing PPP startup delays
Removes PPP_started signal on reception, since lwip handles data
reception if the netif is up/down (which we correctly set in start()
stop() methods)

Closes https://github.com/espressif/esp-protocols/issues/308
2023-08-28 10:27:16 +02:00
cb6e03ac62 fix(modem): Added support for inflatable buffer
As a configurable option, if disabled we report an error.

Closes https://github.com/espressif/esp-protocols/issues/272
2023-08-28 10:27:10 +02:00
2e42b9bb49 Merge pull request #347 from gabsuren/fix/websocket_fix_ci
fix(websocket): fix of websocket coverage report generation
2023-08-28 11:30:20 +04:00
86f7a8bbe3 Merge pull request #343 from victorrar/dev/IDFGH-10845
Fix LoadProhibited after failed CMUX initialization
2023-08-28 09:14:45 +02:00
7b5a41deea fix(websocket): fix of websocket coverage report generation 2023-08-22 19:42:14 +04:00
38d50eede0 bump(websocket): 1.0.1 -> 1.1.0
1.1.0
Features
- Added linux port for websocket (a22391a)
Bug Fixes
- added idf_component.yml for examples (d273e10)
2023-08-22 14:26:12 +04:00
27303d28b2 Merge pull request #345 from gabsuren/fix/example_component_manager
fix(common): added idf_component.yml for examples
2023-08-22 14:04:32 +04:00
d273e10819 fix(common): added idf_component.yml for examples 2023-08-22 13:55:10 +04:00
60c87ddf26 fix(modem): Fix LoadProhibited after failed CMUX initialization (IDFGH-10845) 2023-08-18 12:44:37 +03:00
a95891e211 fix(common): Bump espressif/check-copyright version for PyYAML fix 2023-08-18 12:42:23 +03:00
411dced3e2 Merge pull request #334 from gabsuren/mdns/fix_null_pointer
fix(mdns): added guard check for null pointer (IDF-7449)
2023-08-17 21:01:52 +04:00
42fe60828f Merge pull request #342 from gabsuren/ci/fix_workflow_run
ci(common): start coverage publish action only against master branch
2023-08-16 11:36:30 +04:00
36899f1135 Merge pull request #340 from gabsuren/docs/updated_doc
docs(common): updated README regarding esp-idf integration
2023-08-15 17:32:55 +04:00
0714e100ee ci(common): start coverage publish action only against master branch 2023-08-12 17:54:55 +04:00
cbfee945a0 docs(common): updated README regarding esp-idf integration 2023-08-11 15:46:05 +04:00
71bb461ed8 fix(mdns): added guard check for null pointer 2023-08-03 13:53:17 +04:00
5143f5ac01 Merge pull request #310 from gabsuren/feat/websocket_linux_port_component
feat(websocket): Added linux port for websocket (IDF-7097)
2023-07-27 11:07:06 +04:00
7e83741615 Merge pull request #330 from gabsuren/websocket/release_1.0.1
bump(websocket): 1.0.0 -> 1.0.1
2023-07-25 14:29:08 +04:00
b880fc0367 bump(websocket): 1.0.0 -> 1.0.1
1.0.1
Bug Fixes
- esp_websocket_client client allow sending 0 byte packets (b5177cb)
- Cleaned up printf/format warnings (-Wno-format) (e085826)
- Added unit tests to CI + minor fix to pass it (c974c14)
- Reintroduce missing CHANGELOGs (200cbb3, #235)
Updated
- docs(common): updated component and example links (f48d9b2)
- docs(common): improving documentation (ca3fce0)
- Fix weird error message spacings (8bb207e)
2023-07-25 13:55:59 +04:00
f6d5186e5b Merge pull request #325 from gabsuren/docs/link_fix_esp_modem
docs(modem): Update Doxyfile to generate correct docs links
2023-07-19 12:05:56 +04:00
8eb3a0feea Merge pull request #324 from gytxxsy/feature/set_ation_queue_len_in_kconfig
feat(mdns): Allow setting length of mDNS action queue in menuconfig
2023-07-18 13:40:36 +02:00
28cd898eca feat(mdns): Allow setting length of mDNS action queue in menuconfig 2023-07-18 19:09:16 +08:00
a22391ae2c feat(websocket): Added linux port for websocket 2023-07-18 14:18:39 +04:00
f5a0d5fb40 docs(modem): Update Doxyfile to generate correct docs links 2023-07-17 11:18:51 +04:00
ecc465daa3 Merge pull request #321 from gabsuren/mdns/release_1.2.0
bump(mdns): 1.1.0 -> 1.2.0
2023-07-04 12:24:13 +04:00
181b6e4013 bump(mdns): 1.1.0 -> 1.2.0
1.2.0
Features
- add an API for setting address to a delegated host (ddc3eb6)
- Add support for lwip build under linux (588465d)
- Allow for adding a delegated host with no address (c562461)
- Add APIs for looking up self hosted services and getting the self hostname (f0df12d)
Bug Fixes
- Refactor freertos linux compat layers (79a0e57)
- Fix delegated service PTR response (cab0e1d)
- Added unit tests to CI + minor fix to pass it (c974c14)
Updated
- docs: update documentation links (4de5298)
2023-07-04 11:56:33 +04:00
40e3875f76 Merge pull request #315 from zwx1995esp/feature/add_delegate_host_addr_set
feat(mdns): add an API for setting address to a delegated host
2023-07-04 10:54:35 +04:00
ddc3eb62d0 feat(mdns): add an API for setting address to a delegated host 2023-06-29 21:57:39 +08:00
3625889049 Merge pull request #286 from david-cermak/feat/example_multiple_netifs
feat(examples): Add multiple netif demo: eth+wifi+PPP
2023-06-28 17:49:32 +02:00
2bd6163ec8 ci(common): Introduce global warning list file 2023-06-28 16:49:37 +02:00
f1eb46580e feat(examples): Add build/host tests to examples 2023-06-28 12:59:48 +02:00
1c20328dcf feat(examples): Support simple PPP connect in multinet example 2023-06-28 12:59:44 +02:00
6de22f3893 Merge pull request #314 from david-cermak/fix/issue_template
Update issue template with correct docs links
2023-06-27 17:40:36 +02:00
e280b3b541 docs(common): Update issue template with correct docs links 2023-06-26 10:33:16 +02:00
00d7c40848 feat(examples): Add multiple netif demo: eth+wifi+PPP 2023-06-22 11:49:59 +02:00
79 changed files with 1916 additions and 89 deletions

View File

@ -8,7 +8,7 @@ body:
label: Answers checklist.
description: Before submitting a new issue, please follow the checklist and try to find the answer.
options:
- label: I have read the documentation for [esp-protocols components](https://espressif.github.io/esp-protocols/) and the issue is not addressed there.
- label: I have read the documentation for esp-protocols [components](https://github.com/espressif/esp-protocols#readme) and the issue is not addressed there.
required: true
- label: I have updated my esp-protocols branch (master or release) to the latest version and checked that the issue is present there.
required: true

View File

@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: esp-protocols documentation
url: https://espressif.github.io/esp-protocols/
- name: esp-protocols documentation links
url: https://github.com/espressif/esp-protocols#readme
about: Documenation for esp-protocols components.
- name: ESP-IDF Programming Guide
url: https://docs.espressif.com/projects/esp-idf/en/latest/

View File

@ -7,7 +7,7 @@ body:
label: Answers checklist.
description: Before submitting a new issue, please follow the checklist and try to find the answer.
options:
- label: I have read the documentation for [esp-protocols components](https://espressif.github.io/esp-protocols/) and the issue is not addressed there.
- label: I have read the documentation for esp-protocols [components](https://github.com/espressif/esp-protocols#readme) and the issue is not addressed there.
required: true
- label: I have updated my esp-protocols branch (master or release) to the latest version and checked that the issue is present there.
required: true

View File

@ -0,0 +1,48 @@
name: "examples: build/host-tests"
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
build_all_examples:
if: contains(github.event.pull_request.labels.*.name, 'examples') || github.event_name == 'push'
name: Build examples
strategy:
matrix:
idf_ver: ["latest", "release-v5.1"]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
- name: Build with IDF-${{ matrix.idf_ver }}
shell: bash
run: |
. ${IDF_PATH}/export.sh
python -m pip install idf-build-apps
# Build default configs for all targets
python ./ci/build_apps.py examples -m examples/.build-test-rules.yml -d -c
build_and_run_on_host:
if: contains(github.event.pull_request.labels.*.name, 'examples') || github.event_name == 'push'
name: Build and run examples on linux
strategy:
matrix:
idf_ver: ["latest"]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
- name: Build with IDF-${{ matrix.idf_ver }}
shell: bash
run: |
. ${IDF_PATH}/export.sh
python -m pip install idf-build-apps
python ./ci/build_apps.py examples/mqtt -l -t linux
timeout 5 ./examples/mqtt/build_linux_default/esp_mqtt_demo.elf | tee test.log || true
grep 'MQTT_EVENT_DATA' test.log

View File

@ -109,7 +109,6 @@ jobs:
- name: Build and Test
shell: bash
run: |
apt-get update
apt-get update && apt-get install -y gcc-8 g++-8 python3-pip
apt-get install -y rsync
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8
@ -129,9 +128,9 @@ jobs:
cd $GITHUB_WORKSPACE/${{ env.COMP_DIR }}
gcov-8 `find . -name "esp_modem*gcda" -printf '%h\n' | head -n 1`/*
gcovr --gcov-ignore-parse-errors -g -k -r . --html index.html -x esp_modem_coverage.xml
mkdir docs_gcovr
cp $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/index.html docs_gcovr
cp -rf docs_gcovr $GITHUB_WORKSPACE
mkdir modem_coverage_report
cp $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/index.html modem_coverage_report
cp -rf modem_coverage_report $GITHUB_WORKSPACE
- name: Code Coverage Summary Report
uses: irongut/CodeCoverageSummary@v1.3.0
with:
@ -151,13 +150,7 @@ jobs:
uses: actions/upload-artifact@v3
if: always()
with:
name: docs_gcovr
name: modem_coverage_report
path: |
${{ env.COMP_DIR }}/docs_gcovr
${{ env.COMP_DIR }}/modem_coverage_report
if-no-files-found: error
- name: Deploy code coverage results
if: github.ref == 'refs/heads/master'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs_gcovr

View File

@ -0,0 +1,45 @@
name: Publish coverage report to Github Pages
on:
workflow_run:
workflows: ["websocket: build/host-tests", "esp-modem: build/host-tests"]
types:
- completed
branches:
- master
jobs:
publish_github_pages:
runs-on: ubuntu-latest
if: github.repository == 'espressif/esp-protocols'
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Download Websocket Artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: websocket__build-host-tests.yml
workflow_conclusion: success
name: websocket_coverage_report
path: websocket_coverage_report_artifact
- name: Download Modem Artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: modem__build-host-tests.yml
workflow_conclusion: success
name: modem_coverage_report
path: modem_coverage_report_artifact
- name: Merge HTML files
run: |
echo "<html><body>" > index.html
cat modem_coverage_report_artifact/index.html >> index.html
cat websocket_coverage_report_artifact/index.html >> index.html
echo "</body></html>" >> index.html
mkdir coverage_report
mv index.html coverage_report
- name: Deploy generated docs
uses: JamesIves/github-pages-deploy-action@4.1.5
with:
branch: gh-pages
folder: coverage_report

86
.github/workflows/run-host-tests.yml vendored Normal file
View File

@ -0,0 +1,86 @@
name: Run on host
on:
workflow_call:
inputs:
idf_version:
required: true
type: string
app_name:
type: string
required: true
app_path:
type: string
required: true
component_path:
type: string
required: true
upload_artifacts:
type: boolean
required: true
jobs:
build:
name: Build App
runs-on: ubuntu-20.04
permissions:
contents: write
container: espressif/idf:${{inputs.idf_version}}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
with:
path: esp-protocols
- name: Build ${{ inputs.app_name }} with IDF-${{ inputs.idf_version }}
shell: bash
run: |
. ${IDF_PATH}/export.sh
cd $GITHUB_WORKSPACE/${{inputs.app_path}}
rm -rf sdkconfig sdkconfig.defaults build
cp sdkconfig.ci.linux sdkconfig.defaults
idf.py build
./build/${{inputs.app_name}}.elf
- name: Build with Coverage Enabled
shell: bash
run: |
. ${IDF_PATH}/export.sh
cd $GITHUB_WORKSPACE/${{inputs.app_path}}
rm -rf build sdkconfig sdkconfig.defaults
cp sdkconfig.ci.coverage sdkconfig.defaults
idf.py fullclean
idf.py build
./build/${{inputs.app_name}}.elf
- name: Run Coverage
shell: bash
run: |
apt-get update && apt-get install -y python3-pip rsync
python -m pip install gcovr
cd $GITHUB_WORKSPACE/${{inputs.component_path}}
gcov `find . -name "*gcda"`
gcovr --gcov-ignore-parse-errors -g -k -r . --html index.html -x ${{inputs.app_name}}_coverage.xml
mkdir ${{inputs.app_name}}_coverage_report
touch ${{inputs.app_name}}_coverage_report/.nojekyll
cp index.html ${{inputs.app_name}}_coverage_report
cp -rf ${{inputs.app_name}}_coverage_report ${{inputs.app_name}}_coverage.xml $GITHUB_WORKSPACE
- name: Code Coverage Summary Report
uses: irongut/CodeCoverageSummary@v1.3.0
with:
filename: esp-protocols/**/${{inputs.app_name}}_coverage.xml
badge: true
fail_below_min: false
format: markdown
hide_branch_rate: false
hide_complexity: false
indicators: true
output: both
thresholds: '60 80'
- name: Write to Job Summary
run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY
- name: Upload files to artifacts for run-target job
uses: actions/upload-artifact@v3
if: ${{inputs.upload_artifacts}}
with:
name: ${{inputs.app_name}}_coverage_report
path: |
${{inputs.component_path}}/${{inputs.app_name}}_coverage_report
if-no-files-found: error

View File

@ -0,0 +1,20 @@
name: "websocket: build/host-tests"
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
host_test_websocket:
if: contains(github.event.pull_request.labels.*.name, 'websocket') || github.event_name == 'push'
uses: "./.github/workflows/run-host-tests.yml"
with:
idf_version: "latest"
app_name: "websocket"
app_path: "esp-protocols/components/esp_websocket_client/examples/linux"
component_path: "esp-protocols/components/esp_websocket_client"
upload_artifacts: true

View File

@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
idf_ver: ["release-v5.0", "release-v5.1", "latest"]
test: [ { app: example, path: "examples" }, { app: unit_test, path: "test" } ]
test: [ { app: example, path: "examples/target" }, { app: unit_test, path: "test" } ]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
env:
@ -53,7 +53,7 @@ jobs:
matrix:
idf_ver: ["release-v5.0", "release-v5.1", "latest"]
idf_target: ["esp32"]
test: [ { app: example, path: "examples" }, { app: unit_test, path: "test" } ]
test: [ { app: example, path: "examples/target" }, { app: unit_test, path: "test" } ]
runs-on:
- self-hosted
- ESP32-ETHERNET-KIT

View File

@ -43,7 +43,7 @@ repos:
hooks:
- id: eradicate
- repo: https://github.com/espressif/check-copyright/
rev: v1.0.1
rev: v1.0.3
hooks:
- id: check-copyright
args: ['--ignore', 'ci/check_copyright_ignore.txt', '--config', 'ci/check_copyright_config.yaml']

View File

@ -1,5 +1,11 @@
# Collection of protocol components for ESP-IDF
## How to use
The [ESP-Protocols](https://github.com/espressif/esp-protocols) repository contains a collection of protocol components for [ESP-IDF](https://github.com/espressif/esp-idf).
Additionally, each component is available in [IDF Component Registry](https://components.espressif.com).
Please refer to instructions in [ESP-IDF](https://github.com/espressif/esp-idf)
## Components
### esp_modem

View File

@ -26,20 +26,29 @@ if __name__ == '__main__':
parser.add_argument('-r', '--rules', nargs='*', default=['sdkconfig.ci=default', 'sdkconfig.ci.*=', '=default'], help='Rules how to treat configs')
parser.add_argument('-m', '--manifests', nargs='*', default=[], help='list of manifest files')
parser.add_argument('-d', '--delete', action='store_true', help='Delete build artifacts')
parser.add_argument('-c', '--recursive', action='store_true', help='Build recursively')
parser.add_argument('-l', '--linux', action='store_true', help='Include linux build (dont check warnings)')
args = parser.parse_args()
IDF_PATH = os.environ['IDF_PATH']
print(args.paths)
# Compose the ignore warning strings from the global list and from the environment
ignore_warning_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),'ignore_build_warnings.txt')
ignore_warning = open(ignore_warning_file).read().rstrip('\n').split('\n')
if 'EXPECTED_WARNING' in os.environ:
ignore_warning += os.environ['EXPECTED_WARNING'].split('\n')
if args.linux:
SUPPORTED_TARGETS.append('linux')
ignore_warning = 'warning: ' # Ignore all common warnings on linux builds
setup_logging(2)
apps = find_apps(
args.paths,
recursive=False,
recursive=args.recursive,
target=args.target,
build_dir='build_@t_@w',
config_rules_str=args.rules,
build_log_path='build_log.txt',
size_json_path='size.json',
size_json_path='size.json' if not args.linux else None,
check_warnings=True,
preserve=not args.delete,
manifest_files=args.manifests,
@ -54,5 +63,5 @@ if __name__ == '__main__':
build_apps(apps,
dry_run=False,
keep_going=False,
ignore_warning_strs=os.environ['EXPECTED_WARNING']
if 'EXPECTED_WARNING' in os.environ else None))
ignore_warning_strs=ignore_warning)
)

View File

@ -0,0 +1 @@
DeprecationWarning: pkg_resources is deprecated as an API

View File

@ -0,0 +1,4 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/asio: ">=1.0.1"

View File

@ -0,0 +1,4 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/asio: ">=1.0.1"

View File

@ -0,0 +1,4 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/asio: ">=1.0.1"

View File

@ -0,0 +1,4 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/asio: ">=1.0.1"

View File

@ -0,0 +1,4 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/asio: ">=1.0.1"

View File

@ -0,0 +1,4 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/asio: ">=1.0.1"

View File

@ -16,6 +16,17 @@ menu "esp-modem"
in command mode might come fragmented in rare cases so might need to retry
AT commands.
config ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
bool "Use inflatable buffer in DCE"
default n
help
If enabled we will process the ongoing AT command by growing the current
buffer (if we've run out the preconfigured buffer).
If disabled, we simply report a failure.
Use this if additional allocation is not a problem and you need to reliably process
all commands, usually with sporadically longer responses than the configured buffer.
Could be also used to defragment AT replies in CMUX mode if CMUX_DEFRAGMENT_PAYLOAD=n
config ESP_MODEM_CMUX_DELAY_AFTER_DLCI_SETUP
int "Delay in ms to wait before creating another virtual terminal"
default 0

View File

@ -175,6 +175,9 @@ extern "C" void app_main(void)
#endif
assert(dce);
/* Try to connect to the network and publish an mqtt topic */
StatusHandler handler;
if (dte_config.uart_config.flow_control == ESP_MODEM_FLOW_CONTROL_HW) {
if (command_result::OK != dce->set_flow_control(2, 2)) {
ESP_LOGE(TAG, "Failed to set the set_flow_control mode");
@ -215,8 +218,6 @@ extern "C" void app_main(void)
}
#endif
/* Try to connect to the network and publish an mqtt topic */
StatusHandler handler;
if (!handler.wait_for(StatusHandler::IP_Event, 60000)) {
ESP_LOGE(TAG, "Cannot get IP within specified timeout... exiting");
return;

View File

@ -236,6 +236,14 @@ public:
return nullptr;
}
template <typename T_Module>
static std::unique_ptr<DCE> create_unique_dce_from(const esp_modem::dce_config *config,
std::shared_ptr<esp_modem::DTE> dte,
esp_netif_t *netif)
{
return build_generic_DCE<T_Module, DCE, std::unique_ptr<DCE>>(config, std::move(dte), netif);
}
private:
ModemType m;

View File

@ -79,6 +79,13 @@ public:
*/
void set_read_cb(std::function<bool(uint8_t *data, size_t len)> f);
/**
* @brief Sets read callback for manual command processing
* Note that this API also locks the command API, which can only be used
* after you remove the callback by dte->on_read(nullptr)
*
* @param on_data Function to be called when a command response is available
*/
void on_read(got_line_cb on_data) override;
/**
@ -122,10 +129,10 @@ protected:
}
friend class Scoped<DTE>; /*!< Declaring "Scoped<DTE> lock(dte)" locks this instance */
private:
static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */
[[nodiscard]] bool setup_cmux(); /*!< Internal setup of CMUX mode */
[[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode */
[[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode and cleanup */
void exit_cmux_internal(); /*!< Cleanup CMUX */
Lock internal_lock{}; /*!< Locks DTE operations */
unique_buffer buffer; /*!< DTE buffer */
@ -133,9 +140,73 @@ private:
std::shared_ptr<Terminal> primary_term; /*!< Reference to the primary terminal (mostly for sending commands) */
std::shared_ptr<Terminal> secondary_term; /*!< Secondary terminal for this DTE */
modem_mode mode; /*!< DTE operation mode */
SignalGroup signal; /*!< Event group used to signal request-response operations */
command_result result; /*!< Command result of the currently exectuted command */
std::function<bool(uint8_t *data, size_t len)> on_data; /*!< on data callback for current terminal */
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
/**
* @brief Implements an extra buffer that is used to capture partial reads from underlying terminals
* when we run out of the standard buffer
*/
struct extra_buffer {
extra_buffer(): buffer(nullptr) {}
~extra_buffer()
{
delete buffer;
}
std::vector<uint8_t> *buffer;
size_t consumed{0};
void grow(size_t need_size);
void deflate()
{
grow(0);
consumed = 0;
}
[[nodiscard]] uint8_t *begin() const
{
return &buffer->at(0);
}
[[nodiscard]] uint8_t *current() const
{
return &buffer->at(0) + consumed;
}
} inflatable;
#endif // CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
/**
* @brief Set internal command callbacks to the underlying terminal.
* Here we capture command replies to be processed by supplied command callbacks in struct command_cb.
*/
void set_command_callbacks();
/**
* @brief This abstracts command callback processing and implements its locking, signaling of completion and timeouts.
*/
struct command_cb {
static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */
got_line_cb got_line; /*!< Supplied command callback */
Lock line_lock{}; /*!< Command callback locking mechanism */
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 wait_for_line(uint32_t time_ms); /*!< Waiting for command processing */
void set(got_line_cb l, char s = '\n') /*!< Sets the command callback atomically */
{
Scoped<Lock> lock(line_lock);
if (l) {
// if we set the line callback, we have to reset the signal and the result
signal.clear(GOT_LINE);
result = command_result::TIMEOUT;
}
got_line = std::move(l);
separator = s;
}
void give_up() /*!< Reports other than timeout error when processing replies (out of buffer) */
{
result = command_result::FAIL;
signal.set(GOT_LINE);
}
} command_cb; /*!< Command callback utility class */
};
/**

View File

@ -54,9 +54,10 @@ public:
*/
void stop();
private:
void receive(uint8_t *data, size_t len);
private:
static esp_err_t esp_modem_dte_transmit(void *h, void *buffer, size_t len);
static esp_err_t esp_modem_post_attach(esp_netif_t *esp_netif, void *args);

View File

@ -21,10 +21,17 @@ static bool exit_data(DTE &dte, ModuleIf &device, Netif &netif)
netif.stop();
auto signal = std::make_shared<SignalGroup>();
std::weak_ptr<SignalGroup> weak_signal = signal;
dte.set_read_cb([weak_signal](uint8_t *data, size_t len) -> bool {
dte.set_read_cb([&netif, weak_signal](uint8_t *data, size_t len) -> bool {
// post the transitioning data to the network layers if it contains PPP SOF marker
if (memchr(data, 0x7E, len))
{
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data (PPP)", data, len, ESP_LOG_DEBUG);
netif.receive(data, len);
}
// treat the transitioning data as a textual message if it contains a newline char
if (memchr(data, '\n', len))
{
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", data, len, ESP_LOG_DEBUG);
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data (CMD)", data, len, ESP_LOG_DEBUG);
const auto pass = std::list<std::string_view>({"NO CARRIER", "DISCONNECTED"});
std::string_view response((char *) data, len);
for (auto &it : pass)
@ -39,6 +46,7 @@ static bool exit_data(DTE &dte, ModuleIf &device, Netif &netif)
});
netif.wait_until_ppp_exits();
if (!signal->wait(1, 2000)) {
dte.set_read_cb(nullptr);
if (!device.set_mode(modem_mode::COMMAND_MODE)) {
return false;
}

View File

@ -17,53 +17,118 @@ static const size_t dte_default_buffer_size = 1000;
DTE::DTE(const esp_modem_dte_config *config, std::unique_ptr<Terminal> terminal):
buffer(config->dte_buffer_size),
cmux_term(nullptr), primary_term(std::move(terminal)), secondary_term(primary_term),
mode(modem_mode::UNDEF) {}
mode(modem_mode::UNDEF)
{
set_command_callbacks();
}
DTE::DTE(std::unique_ptr<Terminal> terminal):
buffer(dte_default_buffer_size),
cmux_term(nullptr), primary_term(std::move(terminal)), secondary_term(primary_term),
mode(modem_mode::UNDEF) {}
mode(modem_mode::UNDEF)
{
set_command_callbacks();
}
DTE::DTE(const esp_modem_dte_config *config, std::unique_ptr<Terminal> t, std::unique_ptr<Terminal> s):
buffer(config->dte_buffer_size),
cmux_term(nullptr), primary_term(std::move(t)), secondary_term(std::move(s)),
mode(modem_mode::DUAL_MODE) {}
mode(modem_mode::UNDEF)
{
set_command_callbacks();
}
DTE::DTE(std::unique_ptr<Terminal> t, std::unique_ptr<Terminal> s):
buffer(dte_default_buffer_size),
cmux_term(nullptr), primary_term(std::move(t)), secondary_term(std::move(s)),
mode(modem_mode::DUAL_MODE) {}
mode(modem_mode::UNDEF)
{
set_command_callbacks();
}
void DTE::set_command_callbacks()
{
primary_term->set_read_cb([this](uint8_t *data, size_t len) {
Scoped<Lock> l(command_cb.line_lock);
if (command_cb.got_line == nullptr) {
return false;
}
if (data) {
// For terminals which post data directly with the callback (CMUX)
// we cannot defragment unless we allocate, but
// we'll try to process the data on the actual buffer
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
if (inflatable.consumed != 0) {
inflatable.grow(inflatable.consumed + len);
std::memcpy(inflatable.current(), data, len);
data = inflatable.begin();
}
if (command_cb.process_line(data, inflatable.consumed, len)) {
return true;
}
// at this point we're sure that the data processing hasn't finished,
// and we have to grow the inflatable buffer (if enabled) or give up
if (inflatable.consumed == 0) {
inflatable.grow(len);
std::memcpy(inflatable.begin(), data, len);
}
inflatable.consumed += len;
return false;
#else
if (command_cb.process_line(data, 0, len)) {
return true;
}
// cannot inflate and the processing hasn't finishes in the first iteration -> report a failure
command_cb.give_up();
return true;
#endif
}
// data == nullptr: Terminals which request users to read current data
// we're able to use DTE's buffer to defragment it; as long as we consume less that the buffer size
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)) {
return true;
}
buffer.consumed += len;
return false;
}
// we have used the entire DTE's buffer, need to use the inflatable buffer to continue
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
if (inflatable.consumed == 0) {
inflatable.grow(buffer.size + len);
std::memcpy(inflatable.begin(), buffer.get(), buffer.size);
inflatable.consumed = buffer.size;
} else {
inflatable.grow(inflatable.consumed + len);
}
len = primary_term->read(inflatable.current(), len);
if (command_cb.process_line(inflatable.begin(), inflatable.consumed, len)) {
return true;
}
inflatable.consumed += len;
return false;
#else
// cannot inflate -> report a failure
command_cb.give_up();
return true;
#endif
});
}
command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator)
{
Scoped<Lock> l(internal_lock);
result = command_result::TIMEOUT;
signal.clear(GOT_LINE);
primary_term->set_read_cb([this, got_line, separator](uint8_t *data, size_t len) {
if (!data) {
data = buffer.get();
len = primary_term->read(data + buffer.consumed, buffer.size - buffer.consumed);
} else {
buffer.consumed = 0; // if the underlying terminal contains data, we cannot fragment
}
if (memchr(data + buffer.consumed, separator, len)) {
result = got_line(data, buffer.consumed + len);
if (result == command_result::OK || result == command_result::FAIL) {
signal.set(GOT_LINE);
return true;
}
}
buffer.consumed += len;
return false;
});
Scoped<Lock> l1(internal_lock);
command_cb.set(got_line, separator);
primary_term->write((uint8_t *)command.c_str(), command.length());
auto got_lf = signal.wait(GOT_LINE, time_ms);
if (got_lf && result == command_result::TIMEOUT) {
ESP_MODEM_THROW_IF_ERROR(ESP_ERR_INVALID_STATE);
}
command_cb.wait_for_line(time_ms);
command_cb.set(nullptr);
buffer.consumed = 0;
primary_term->set_read_cb(nullptr);
return result;
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
inflatable.deflate();
#endif
return command_cb.result;
}
command_result DTE::command(const std::string &cmd, got_line_cb got_line, uint32_t time_ms)
@ -76,12 +141,22 @@ bool DTE::exit_cmux()
if (!cmux_term->deinit()) {
return false;
}
exit_cmux_internal();
return true;
}
void DTE::exit_cmux_internal()
{
if (!cmux_term) {
return;
}
auto ejected = cmux_term->detach();
// return the ejected terminal and buffer back to this DTE
primary_term = std::move(ejected.first);
buffer = std::move(ejected.second);
secondary_term = primary_term;
return true;
set_command_callbacks();
}
bool DTE::setup_cmux()
@ -90,14 +165,21 @@ bool DTE::setup_cmux()
if (cmux_term == nullptr) {
return false;
}
if (!cmux_term->init()) {
exit_cmux_internal();
cmux_term = nullptr;
return false;
}
primary_term = std::make_unique<CMuxInstance>(cmux_term, 0);
if (primary_term == nullptr) {
return false;
}
primary_term = std::make_unique<CMuxInstance>(cmux_term, 0);
secondary_term = std::make_unique<CMuxInstance>(cmux_term, 1);
if (primary_term == nullptr || secondary_term == nullptr) {
exit_cmux_internal();
cmux_term = nullptr;
return false;
}
set_command_callbacks();
return true;
}
@ -119,6 +201,7 @@ bool DTE::set_mode(modem_mode m)
if (mode == modem_mode::CMUX_MODE || mode == modem_mode::CMUX_MANUAL_MODE || mode == modem_mode::DUAL_MODE) {
// mode stays the same, but need to swap terminals (as command has been switched)
secondary_term.swap(primary_term);
set_command_callbacks();
} else {
mode = m;
}
@ -161,6 +244,7 @@ bool DTE::set_mode(modem_mode m)
// manual CMUX transitions: Swap terminals
if (m == modem_mode::CMUX_MANUAL_SWAP && mode == modem_mode::CMUX_MANUAL_MODE) {
secondary_term.swap(primary_term);
set_command_callbacks();
return true;
}
mode = modem_mode::UNDEF;
@ -169,6 +253,10 @@ bool DTE::set_mode(modem_mode m)
void DTE::set_read_cb(std::function<bool(uint8_t *, size_t)> f)
{
if (f == nullptr) {
set_command_callbacks();
return;
}
on_data = std::move(f);
secondary_term->set_read_cb([this](uint8_t *data, size_t len) {
if (!data) { // if no data available from terminal callback -> need to explicitly read some
@ -230,6 +318,41 @@ void DTE::on_read(got_line_cb on_read_cb)
});
}
bool DTE::command_cb::process_line(uint8_t *data, size_t consumed, size_t len)
{
if (memchr(data + consumed, separator, len)) {
result = got_line(data, consumed + len);
if (result == command_result::OK || result == command_result::FAIL) {
signal.set(GOT_LINE);
return true;
}
}
return false;
}
bool DTE::command_cb::wait_for_line(uint32_t time_ms)
{
auto got_lf = signal.wait(command_cb::GOT_LINE, time_ms);
if (got_lf && result == command_result::TIMEOUT) {
ESP_MODEM_THROW_IF_ERROR(ESP_ERR_INVALID_STATE);
}
return got_lf;
}
#ifdef CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
void DTE::extra_buffer::grow(size_t need_size)
{
if (need_size == 0) {
delete buffer;
buffer = nullptr;
} else if (buffer == nullptr) {
buffer = new std::vector<uint8_t>(need_size);
} else {
buffer->resize(need_size);
}
}
#endif
/**
* Implemented here to keep all headers C++11 compliant
*/

View File

@ -65,9 +65,7 @@ esp_err_t Netif::esp_modem_post_attach(esp_netif_t *esp_netif, void *args)
void Netif::receive(uint8_t *data, size_t len)
{
if (signal.is_any(PPP_STARTED)) {
esp_netif_receive(driver.base.netif, data, len, nullptr);
}
esp_netif_receive(driver.base.netif, data, len, nullptr);
}
Netif::Netif(std::shared_ptr<DTE> e, esp_netif_t *ppp_netif) :
@ -89,8 +87,8 @@ void Netif::start()
receive(data, len);
return true;
});
esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr);
signal.set(PPP_STARTED);
esp_netif_action_start(driver.base.netif, nullptr, 0, nullptr);
}
void Netif::stop()

View File

@ -64,7 +64,6 @@ public:
void set_read_cb(std::function<bool(uint8_t *data, size_t len)> f) override
{
ESP_MODEM_THROW_IF_FALSE(signal.wait(TASK_PARAMS, 1000), "Failed to set UART task params");
on_read = std::move(f);
}
@ -91,7 +90,6 @@ private:
static const size_t TASK_INIT = BIT0;
static const size_t TASK_START = BIT1;
static const size_t TASK_STOP = BIT2;
static const size_t TASK_PARAMS = BIT3;
QueueHandle_t event_queue;
uart_resource uart;
@ -118,9 +116,7 @@ void UartTerminal::task()
return; // exits to the static method where the task gets deleted
}
while (signal.is_any(TASK_START)) {
signal.set(TASK_PARAMS);
if (get_event(event, 100)) {
signal.clear(TASK_PARAMS);
switch (event.type) {
case UART_DATA:
uart_get_buffered_data_len(uart.port, &len);

View File

@ -0,0 +1,5 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/esp_mqtt_cxx:
version: "^0.1.0"

View File

@ -0,0 +1,5 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/esp_mqtt_cxx:
version: "^0.1.0"

View File

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

View File

@ -1,5 +1,30 @@
# Changelog
## [1.1.0](https://github.com/espressif/esp-protocols/commits/websocket-v1.1.0)
### Features
- Added linux port for websocket ([a22391a](https://github.com/espressif/esp-protocols/commit/a22391a))
### Bug Fixes
- added idf_component.yml for examples ([d273e10](https://github.com/espressif/esp-protocols/commit/d273e10))
## [1.0.1](https://github.com/espressif/esp-protocols/commits/websocket-v1.0.1)
### Bug Fixes
- esp_websocket_client client allow sending 0 byte packets ([b5177cb](https://github.com/espressif/esp-protocols/commit/b5177cb))
- Cleaned up printf/format warnings (-Wno-format) ([e085826](https://github.com/espressif/esp-protocols/commit/e085826))
- Added unit tests to CI + minor fix to pass it ([c974c14](https://github.com/espressif/esp-protocols/commit/c974c14))
- Reintroduce missing CHANGELOGs ([200cbb3](https://github.com/espressif/esp-protocols/commit/200cbb3), [#235](https://github.com/espressif/esp-protocols/issues/235))
### Updated
- docs(common): updated component and example links ([f48d9b2](https://github.com/espressif/esp-protocols/commit/f48d9b2))
- docs(common): improving documentation ([ca3fce0](https://github.com/espressif/esp-protocols/commit/ca3fce0))
- Fix weird error message spacings ([8bb207e](https://github.com/espressif/esp-protocols/commit/8bb207e))
## [1.0.0](https://github.com/espressif/esp-protocols/commits/996fef7)
### Updated

View File

@ -1,3 +1,5 @@
idf_build_get_property(target IDF_TARGET)
if(NOT CONFIG_WS_TRANSPORT AND NOT CMAKE_BUILD_EARLY_EXPANSION)
message(STATUS "Websocket transport is disabled so the esp_websocket_client component will not be built")
# note: the component is still included in the build so it can become visible again in config
@ -6,7 +8,14 @@ if(NOT CONFIG_WS_TRANSPORT AND NOT CMAKE_BUILD_EARLY_EXPANSION)
return()
endif()
idf_component_register(SRCS "esp_websocket_client.c"
if(${IDF_TARGET} STREQUAL "linux")
idf_component_register(SRCS "esp_websocket_client.c"
INCLUDE_DIRS "include"
REQUIRES esp-tls tcp_transport http_parser esp_event nvs_flash esp_stubs json
PRIV_REQUIRES esp_timer)
else()
idf_component_register(SRCS "esp_websocket_client.c"
INCLUDE_DIRS "include"
REQUIRES lwip esp-tls tcp_transport http_parser
PRIV_REQUIRES esp_timer esp_event)
endif()

View File

@ -19,6 +19,9 @@
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_tls_crypto.h"
#include "esp_system.h"
#include <errno.h>
#include <arpa/inet.h>
static const char *TAG = "websocket_client";

View File

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.5)
set(COMPONENTS esp_websocket_client main)
set(common_component_dir ../../../../common_components)
set(EXTRA_COMPONENT_DIRS
../../..
"${common_component_dir}/linux_compat/esp_timer"
"${common_component_dir}/linux_compat"
"${common_component_dir}/linux_compat/freertos")
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/linux_stubs/esp_stubs)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(websocket)

View File

@ -0,0 +1,14 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS
"."
REQUIRES esp_websocket_client protocol_examples_common)
if(CONFIG_GCOV_ENABLED)
target_compile_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
target_link_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
idf_component_get_property(esp_websocket_client esp_websocket_client COMPONENT_LIB)
target_compile_options(${esp_websocket_client} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
target_link_options(${esp_websocket_client} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
endif()

View File

@ -0,0 +1,15 @@
menu "Host-test config"
config GCOV_ENABLED
bool "Coverage analyzer"
default n
help
Enables coverage analyzing for host tests.
config WEBSOCKET_URI
string "Websocket endpoint URI"
default "ws://echo.websocket.events"
help
URL of websocket endpoint this example connects to and sends echo
endmenu

View File

@ -0,0 +1,122 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include "esp_websocket_client.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
static const char *TAG = "websocket";
static void log_error_if_nonzero(const char *message, int error_code)
{
if (error_code != 0) {
ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
}
}
static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
switch (event_id) {
case WEBSOCKET_EVENT_CONNECTED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
break;
case WEBSOCKET_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
}
break;
case WEBSOCKET_EVENT_DATA:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
if (data->op_code == 0x08 && data->data_len == 2) {
ESP_LOGW(TAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]);
} else {
ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr);
}
// If received data contains json structure it succeed to parse
ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);
break;
case WEBSOCKET_EVENT_ERROR:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
}
break;
}
}
static void websocket_app_start(void)
{
esp_websocket_client_config_t websocket_cfg = {};
websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
esp_websocket_client_start(client);
char data[32];
int i = 0;
while (i < 1) {
if (esp_websocket_client_is_connected(client)) {
int len = sprintf(data, "hello %04d", i++);
ESP_LOGI(TAG, "Sending %s", data);
esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
esp_websocket_client_destroy(client);
}
int main(void)
{
ESP_LOGI(TAG, "[APP] Startup..");
ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set("websocket_client", ESP_LOG_DEBUG);
esp_log_level_set("transport_ws", ESP_LOG_DEBUG);
esp_log_level_set("trans_tcp", ESP_LOG_DEBUG);
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
websocket_app_start();
return 0;
}

View File

@ -0,0 +1,8 @@
CONFIG_GCOV_ENABLED=y
CONFIG_IDF_TARGET="linux"
CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
CONFIG_WEBSOCKET_URI_FROM_STRING=y
CONFIG_WEBSOCKET_URI_FROM_STDIN=n

View File

@ -0,0 +1,8 @@
CONFIG_IDF_TARGET="linux"
CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
CONFIG_WEBSOCKET_URI_FROM_STRING=y
CONFIG_WEBSOCKET_URI_FROM_STDIN=n
CONFIG_EXAMPLE_CONNECT_WIFI=n

View File

@ -2,9 +2,9 @@
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS "..")
set(EXTRA_COMPONENT_DIRS "../..")
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
list(APPEND EXTRA_COMPONENT_DIRS "../../../common_components/protocol_examples_common")
list(APPEND EXTRA_COMPONENT_DIRS "../../../../common_components/protocol_examples_common")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(websocket_example)

View File

@ -20,4 +20,11 @@ menu "Example Configuration"
help
URL of websocket endpoint this example connects to and sends echo
if CONFIG_IDF_TARGET = "linux"
config GCOV_ENABLED
bool "Coverage analyzer"
default n
help
Enables coverage analyzing for host tests.
endif
endmenu

View File

@ -0,0 +1,5 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/esp_websocket_client:
version: "^1.0.0"

View File

@ -1,3 +1,5 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_TARGET_LINUX=n
CONFIG_WEBSOCKET_URI_FROM_STDIN=y
CONFIG_WEBSOCKET_URI_FROM_STRING=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y

View File

@ -1,4 +1,4 @@
version: "1.0.0"
version: "1.1.0"
description: esp websocket client
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_websocket_client
dependencies:

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.1.0
version: 1.2.0
version_files:
- idf_component.yml

View File

@ -1,5 +1,24 @@
# Changelog
## [1.2.0](https://github.com/espressif/esp-protocols/commits/mdns-v1.2.0)
### Features
- add an API for setting address to a delegated host ([ddc3eb6](https://github.com/espressif/esp-protocols/commit/ddc3eb6))
- Add support for lwip build under linux ([588465d](https://github.com/espressif/esp-protocols/commit/588465d))
- Allow for adding a delegated host with no address ([c562461](https://github.com/espressif/esp-protocols/commit/c562461))
- Add APIs for looking up self hosted services and getting the self hostname ([f0df12d](https://github.com/espressif/esp-protocols/commit/f0df12d))
### Bug Fixes
- Refactor freertos linux compat layers ([79a0e57](https://github.com/espressif/esp-protocols/commit/79a0e57))
- Fix delegated service PTR response ([cab0e1d](https://github.com/espressif/esp-protocols/commit/cab0e1d))
- Added unit tests to CI + minor fix to pass it ([c974c14](https://github.com/espressif/esp-protocols/commit/c974c14))
### Updated
- docs: update documentation links ([4de5298](https://github.com/espressif/esp-protocols/commit/4de5298))
## [1.1.0](https://github.com/espressif/esp-protocols/commits/mdns-v1.1.0)
### Features

View File

@ -27,6 +27,13 @@ menu "mDNS"
higher than priorities of system tasks. Compile time warning/error
would be emitted if the chosen task priority were too high.
config MDNS_ACTION_QUEUE_LEN
int "Maximum actions pending to the server"
range 8 64
default 16
help
Allows setting the length of mDNS action queue.
config MDNS_TASK_STACK_SIZE
int "mDNS task stack size"
default 4096

View File

@ -0,0 +1,5 @@
dependencies:
## Required IDF version
idf: ">=5.0"
espressif/mdns:
version: "^1.0.0"

View File

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

View File

@ -158,6 +158,21 @@ esp_err_t mdns_hostname_get(char *hostname);
*/
esp_err_t mdns_delegate_hostname_add(const char *hostname, const mdns_ip_addr_t *address_list);
/**
* @brief Set the address to a delegated hostname
*
* @param hostname Hostname to set
* @param address_list The IP address list of the host
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_STATE mDNS is not running
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NO_MEM memory error
*
*/
esp_err_t mdns_delegate_hostname_set_address(const char *hostname, const mdns_ip_addr_t *address_list);
/**
* @brief Remove a delegated hostname
* All the services added to this host will also be removed.

View File

@ -2993,6 +2993,27 @@ static void free_address_list(mdns_ip_addr_t *address_list)
}
}
static bool _mdns_delegate_hostname_set_address(const char *hostname, mdns_ip_addr_t *address_list)
{
if (!_str_null_or_empty(_mdns_server->hostname) &&
strcasecmp(hostname, _mdns_server->hostname) == 0) {
return false;
}
mdns_host_item_t *host = _mdns_host_list;
while (host != NULL) {
if (strcasecmp(hostname, host->hostname) == 0) {
// free previous address list
free_address_list(host->address_list);
// set current address list to the host
host->address_list = address_list;
return true;
}
host = host->next;
}
return false;
}
static mdns_ip_addr_t *copy_address_list(const mdns_ip_addr_t *address_list)
{
mdns_ip_addr_t *head = NULL;
@ -4845,6 +4866,7 @@ static void _mdns_free_action(mdns_action_t *action)
case ACTION_RX_HANDLE:
_mdns_packet_free(action->data.rx_handle.packet);
break;
case ACTION_DELEGATE_HOSTNAME_SET_ADDR:
case ACTION_DELEGATE_HOSTNAME_ADD:
free((char *)action->data.delegate_hostname.hostname);
free_address_list(action->data.delegate_hostname.address_list);
@ -5065,6 +5087,13 @@ static void _mdns_execute_action(mdns_action_t *action)
free_address_list(action->data.delegate_hostname.address_list);
}
break;
case ACTION_DELEGATE_HOSTNAME_SET_ADDR:
if (!_mdns_delegate_hostname_set_address(action->data.delegate_hostname.hostname,
action->data.delegate_hostname.address_list)) {
free_address_list(action->data.delegate_hostname.address_list);
}
free((char *)action->data.delegate_hostname.hostname);
break;
case ACTION_DELEGATE_HOSTNAME_REMOVE:
_mdns_delegate_hostname_remove(action->data.delegate_hostname.hostname);
free((char *)action->data.delegate_hostname.hostname);
@ -5634,6 +5663,36 @@ esp_err_t mdns_delegate_hostname_remove(const char *hostname)
return ESP_OK;
}
esp_err_t mdns_delegate_hostname_set_address(const char *hostname, const mdns_ip_addr_t *address_list)
{
if (!_mdns_server) {
return ESP_ERR_INVALID_STATE;
}
if (_str_null_or_empty(hostname) || strlen(hostname) > (MDNS_NAME_BUF_LEN - 1)) {
return ESP_ERR_INVALID_ARG;
}
char *new_hostname = strndup(hostname, MDNS_NAME_BUF_LEN - 1);
if (!new_hostname) {
return ESP_ERR_NO_MEM;
}
mdns_action_t *action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
free(new_hostname);
return ESP_ERR_NO_MEM;
}
action->type = ACTION_DELEGATE_HOSTNAME_SET_ADDR;
action->data.delegate_hostname.hostname = new_hostname;
action->data.delegate_hostname.address_list = copy_address_list(address_list);
if (xQueueSend(_mdns_server->action_queue, &action, (TickType_t)0) != pdPASS) {
free(new_hostname);
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
bool mdns_hostname_exists(const char *hostname)
{
return _hostname_is_ours(hostname);
@ -5791,7 +5850,7 @@ static mdns_txt_item_t *_copy_mdns_txt_items(mdns_txt_linked_item_t *items, uint
return ret;
handle_error:
for (size_t y = 0; y < ret_index + 1; y++) {
for (size_t y = 0; y < ret_index + 1 && ret != NULL; y++) {
mdns_txt_item_t *t = &ret[y];
free((char *)t->key);
free((char *)t->value);

View File

@ -93,7 +93,7 @@
#define MDNS_SERVICE_ADD_TIMEOUT_MS CONFIG_MDNS_SERVICE_ADD_TIMEOUT_MS
#define MDNS_PACKET_QUEUE_LEN 16 // Maximum packets that can be queued for parsing
#define MDNS_ACTION_QUEUE_LEN 16 // Maximum actions pending to the server
#define MDNS_ACTION_QUEUE_LEN CONFIG_MDNS_ACTION_QUEUE_LEN // Maximum actions pending to the server
#define MDNS_TXT_MAX_LEN 1024 // Maximum string length of text data in TXT record
#if defined(CONFIG_LWIP_IPV6) && defined(CONFIG_MDNS_RESPOND_REVERSE_QUERIES)
#define MDNS_NAME_MAX_LEN (64+4) // Need to account for IPv6 reverse queries (64 char address + ".ip6" )
@ -196,6 +196,7 @@ typedef enum {
ACTION_TASK_STOP,
ACTION_DELEGATE_HOSTNAME_ADD,
ACTION_DELEGATE_HOSTNAME_REMOVE,
ACTION_DELEGATE_HOSTNAME_SET_ADDR,
ACTION_MAX
} mdns_action_type_t;

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -290,6 +290,7 @@
#define CONFIG_MDNS_MAX_SERVICES 25
#define CONFIG_MDNS_MAX_INTERFACES 3
#define CONFIG_MDNS_TASK_PRIORITY 1
#define CONFIG_MDNS_ACTION_QUEUE_LEN 16
#define CONFIG_MDNS_TASK_STACK_SIZE 4096
#define CONFIG_MDNS_TASK_AFFINITY_CPU0 1
#define CONFIG_MDNS_TASK_AFFINITY 0x0

View File

@ -828,7 +828,9 @@ INPUT = \
$(PROJECT_PATH)/../components/esp_modem/include/cxx_include/esp_modem_types.hpp \
$(PROJECT_PATH)/../components/esp_modem/include/cxx_include/esp_modem_terminal.hpp \
$(PROJECT_PATH)/../components/esp_modem/include/cxx_include/esp_modem_cmux.hpp \
$(PROJECT_PATH)/../components/esp_modem/include/cxx_include/esp_modem_dce.hpp
$(PROJECT_PATH)/../components/esp_modem/include/cxx_include/esp_modem_dce.hpp \
$(PROJECT_PATH)/../docs/esp_modem/en/esp_modem_api_commands.h \
$(PROJECT_PATH)/../docs/esp_modem/en/esp_modem_dce.hpp
# The last two are generated

View File

@ -0,0 +1,3 @@
examples/esp_netif/multiple_netifs:
disable:
- if: IDF_TARGET != "esp32"

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(multiple_netifs)

View File

@ -0,0 +1,25 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
# Multiple Interface example
## Overview
This example demonstrates working with multiple different interfaces with different priorities. It creates these interfaces and tries to connect:
* WiFi Station
* Ethernet using ESP32 internal ethernet driver
* PPPoS over cellular modem
## How to use example
* Set the priorities and the host name for the example to ICMP ping.
* The example will initialize all interfaces
* The example will start looping and checking connectivity to the host name
* It prints the default interface and ping output
* It tries to reconfigure DNS server if host name resolution fails
* It tries to manually change the default interface if connection fails
### Hardware Required
To run this example, it's recommended that you have an official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html).
You would also need a modem connected to the board using UART interface.

View File

@ -0,0 +1,13 @@
set(ppp_connect_srcs ppp_connect.c)
if(CONFIG_EXAMPLE_PPP_CONNECT_ESP_MODEM)
list(APPEND ppp_connect_srcs ppp_connect_esp_modem.c)
else()
list(APPEND ppp_connect_srcs ppp_connect_simple.c)
endif()
idf_component_register(SRCS multi_netif_main.c
check_connection.c
wifi_connect.c
ethernet_connect.c
${ppp_connect_srcs}
INCLUDE_DIRS ".")

View File

@ -0,0 +1,57 @@
menu "Example Configuration"
config ESP_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
config ESP_MAXIMUM_RETRY
int "Maximum retry"
default 0
help
Set the Maximum retry to avoid station reconnecting. Set to 0 to keep retrying indefinitely.
config EXAMPLE_MODEM_PPP_APN
string "Set MODEM APN"
default "default.apn"
help
Set APN (Access Point Name), a logical name to choose data network
config EXAMPLE_PPP_UART_TX_PIN
int "TXD Pin Number"
default 15
range 0 31
help
Pin number of UART TX.
config EXAMPLE_PPP_UART_RX_PIN
int "RXD Pin Number"
default 14
range 0 31
help
Pin number of UART RX.
choice EXAMPLE_PPP_CONNECT
prompt "Connect to PPP server"
default EXAMPLE_PPP_CONNECT_ESP_MODEM
help
Choose modem interface library.
We use esp_modem by default, but in some
simple cases (and a very constrained environment)
we could simply connect UART directly to lwIP.
To experiment with this option, choose EXAMPLE_PPP_CONNECT_SIMPLE
config EXAMPLE_PPP_CONNECT_ESP_MODEM
bool "Using esp_modem library"
config EXAMPLE_PPP_CONNECT_SIMPLE
bool "Using simple UART-PPP driver"
endchoice
endmenu

View File

@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Checks network connectivity by pinging configured host
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "ping/ping_sock.h"
#include "lwip/netdb.h"
#include "esp_log.h"
#define SUCCESS (1)
#define FAIL (2)
static const char *TAG = "check_connection";
static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args)
{
uint8_t ttl;
uint16_t seqno;
uint32_t elapsed_time, recv_len;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
ESP_LOGI(TAG, "%" PRIu32 " bytes from %s icmp_seq=%d ttl=%d time=%" PRIu32 " ms",
recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time);
}
static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args)
{
uint16_t seqno;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
ESP_LOGE(TAG, "From %s icmp_seq=%d timeout", inet_ntoa(target_addr.u_addr.ip4), seqno);
}
static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
{
EventGroupHandle_t events = args;
ip_addr_t target_addr;
uint32_t transmitted;
uint32_t received;
uint32_t total_time_ms;
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
uint32_t loss = (uint32_t)((1 - ((float)received) / transmitted) * 100);
if (IP_IS_V4(&target_addr)) {
ESP_LOGI(TAG, "\n--- %s ping statistics ---", inet_ntoa(*ip_2_ip4(&target_addr)));
} else {
ESP_LOGI(TAG, "\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
}
ESP_LOGI(TAG, "%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %"PRIu32"ms\n",
transmitted, received, loss, total_time_ms);
xEventGroupSetBits(events, received == 0 ? FAIL : SUCCESS);
}
esp_err_t check_connectivity(const char *host)
{
EventGroupHandle_t events = xEventGroupCreate();
ip_addr_t target_addr;
struct addrinfo hint;
struct addrinfo *res = NULL;
memset(&hint, 0, sizeof(hint));
memset(&target_addr, 0, sizeof(target_addr));
/* convert domain name to IP address */
if (getaddrinfo(host, NULL, &hint, &res) != 0) {
ESP_LOGE(TAG, "ping: unknown host %s\n", host);
return ESP_ERR_NOT_FOUND;
}
if (res->ai_family == AF_INET) {
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
} else {
struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr;
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
}
freeaddrinfo(res);
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
config.target_addr = target_addr;
esp_ping_callbacks_t cbs = {
.on_ping_success = cmd_ping_on_ping_success,
.on_ping_timeout = cmd_ping_on_ping_timeout,
.on_ping_end = cmd_ping_on_ping_end,
.cb_args = events
};
esp_ping_handle_t ping;
esp_ping_new_session(&config, &cbs, &ping);
esp_ping_start(ping);
vTaskDelay(pdMS_TO_TICKS(config.count * config.interval_ms));
EventBits_t bits = xEventGroupWaitBits(events, FAIL | SUCCESS, pdFALSE, pdFALSE, portMAX_DELAY);
vEventGroupDelete(events);
esp_ping_delete_session(ping);
return bits == SUCCESS ? ESP_OK : ESP_FAIL;
}

View File

@ -0,0 +1,137 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Ethernet Basic Initialization
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "esp_netif.h"
#include "esp_eth.h"
#include "esp_event.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "iface_info.h"
static const char *TAG = "ethernet_connect";
struct eth_info_t {
iface_info_t parent;
esp_eth_handle_t eth_handle;
esp_eth_netif_glue_handle_t glue;
esp_eth_mac_t *mac;
esp_eth_phy_t *phy;
};
static void eth_event_handler(void *args, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
uint8_t mac_addr[6] = {0};
/* we can get the ethernet driver handle from event data */
esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
struct eth_info_t *eth_info = args;
switch (event_id) {
case ETHERNET_EVENT_CONNECTED:
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
ESP_LOGI(TAG, "Ethernet Link Up");
ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
eth_info->parent.connected = true;
break;
case ETHERNET_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "Ethernet Link Down");
eth_info->parent.connected = false;
break;
case ETHERNET_EVENT_START:
ESP_LOGI(TAG, "Ethernet Started");
break;
case ETHERNET_EVENT_STOP:
ESP_LOGI(TAG, "Ethernet Stopped");
break;
default:
break;
}
}
static void got_ip_event_handler(void *args, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
const esp_netif_ip_info_t *ip_info = &event->ip_info;
struct eth_info_t *eth_info = args;
ESP_LOGI(TAG, "Ethernet Got IP Address");
ESP_LOGI(TAG, "~~~~~~~~~~~");
ESP_LOGI(TAG, "IP:" IPSTR, IP2STR(&ip_info->ip));
ESP_LOGI(TAG, "MASK:" IPSTR, IP2STR(&ip_info->netmask));
ESP_LOGI(TAG, "GW:" IPSTR, IP2STR(&ip_info->gw));
ESP_LOGI(TAG, "~~~~~~~~~~~");
for (int i = 0; i < 2; ++i) {
esp_netif_get_dns_info(eth_info->parent.netif, i, &eth_info->parent.dns[i]);
ESP_LOGI(TAG, "DNS %i:" IPSTR, i, IP2STR(&eth_info->parent.dns[i].ip.u_addr.ip4));
}
ESP_LOGI(TAG, "~~~~~~~~~~~");
}
static void eth_destroy(iface_info_t *info)
{
struct eth_info_t *eth_info = __containerof(info, struct eth_info_t, parent);
esp_eth_stop(eth_info->eth_handle);
esp_eth_del_netif_glue(eth_info->glue);
esp_eth_driver_uninstall(eth_info->eth_handle);
eth_info->phy->del(eth_info->phy);
eth_info->mac->del(eth_info->mac);
esp_netif_destroy(eth_info->parent.netif);
free(eth_info);
}
iface_info_t *eth_init(int prio)
{
struct eth_info_t *eth_info = malloc(sizeof(struct eth_info_t));
assert(eth_info);
eth_info->parent.destroy = eth_destroy;
eth_info->parent.name = "Ethernet";
// Init common MAC and PHY configs to default
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
// 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);
eth_info->phy = esp_eth_phy_new_ip101(&phy_config);
// 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));
// Create an instance of esp-netif for Ethernet
esp_netif_inherent_config_t base_netif_cfg = ESP_NETIF_INHERENT_DEFAULT_ETH();
base_netif_cfg.route_prio = prio;
esp_netif_config_t cfg = {
.base = &base_netif_cfg,
.stack = ESP_NETIF_NETSTACK_DEFAULT_ETH,
};
eth_info->parent.netif = esp_netif_new(&cfg);
eth_info->glue = esp_eth_new_netif_glue(eth_info->eth_handle);
// Attach Ethernet driver to TCP/IP stack
ESP_ERROR_CHECK(esp_netif_attach(eth_info->parent.netif, eth_info->glue ));
// Register user defined event handers
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, eth_info));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, eth_info));
// Start Ethernet driver state machine
ESP_ERROR_CHECK(esp_eth_start(eth_info->eth_handle));
return &eth_info->parent;
}

View File

@ -0,0 +1,4 @@
dependencies:
espressif/esp_modem:
version: "^1.0.0"
override_path: "../../../../components/esp_modem"

View File

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Common interface info
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include "esp_netif.h"
struct iface_info_t {
esp_netif_t *netif;
esp_netif_dns_info_t dns[2];
void (*destroy)(struct iface_info_t *);
const char *name;
bool connected;
};
typedef struct iface_info_t iface_info_t;

View File

@ -0,0 +1,129 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Multiple Network Interface Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <lwip/dns.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "nvs_flash.h"
#include "iface_info.h"
iface_info_t *eth_init(int prio);
iface_info_t *wifi_init(int prio);
iface_info_t *ppp_init(int prio);
esp_err_t check_connectivity(const char *host);
#define HOST "www.espressif.com"
#define ETH_PRIO 200
#define WIFI_PRIO 100
#define PPP_PRIO 50
static const char *TAG = "app_main";
static ssize_t get_default(iface_info_t *list[], size_t num)
{
esp_netif_t *default_netif = esp_netif_get_default_netif();
if (default_netif == NULL) {
ESP_LOGE(TAG, "default netif is NULL!");
return -1;
}
ESP_LOGI(TAG, "Default netif: %s", esp_netif_get_desc(default_netif));
for (int i = 0; i < num; ++i) {
if (list[i] && list[i]->netif == default_netif) {
ESP_LOGI(TAG, "Default interface: %s", list[i]->name);
return i;
}
}
// not found
return -2;
}
void app_main(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_netif_init());
// Create default event loop that running in background
ESP_ERROR_CHECK(esp_event_loop_create_default());
// all interfaces
iface_info_t *ifaces[] = {
eth_init(ETH_PRIO),
wifi_init(WIFI_PRIO),
ppp_init(PPP_PRIO),
};
size_t num_of_ifaces = sizeof(ifaces) / sizeof(ifaces[0]);
while (true) {
dns_clear_cache();
vTaskDelay(pdMS_TO_TICKS(2000));
ssize_t i = get_default(ifaces, num_of_ifaces);
if (i == -1) { // default netif is NULL, probably all interfaces are down -> retry
continue;
} else if (i < 0) {
break; // some other error, exit
}
esp_err_t connect_status = check_connectivity(HOST);
if (connect_status == ESP_OK) {
// connectivity ok
continue;
}
if (connect_status == ESP_ERR_NOT_FOUND) {
// set the default DNS info to global DNS server list
for (int j = 0; j < 2; ++j) {
esp_netif_dns_info_t dns_info;
esp_netif_get_dns_info(ifaces[i]->netif, j, &dns_info);
if (memcmp(&dns_info.ip, &ifaces[i]->dns[j].ip, sizeof(esp_ip_addr_t)) == 0) {
connect_status = ESP_FAIL;
} else {
esp_netif_set_dns_info(ifaces[i]->netif, j, &ifaces[i]->dns[j]);
ESP_LOGI(TAG, "Reconfigured DNS%i=" IPSTR, j, IP2STR(&ifaces[i]->dns[j].ip.u_addr.ip4));
}
}
}
if (connect_status == ESP_FAIL) {
ESP_LOGE(TAG, "No connection via the default netif!");
// try to switch interfaces manually
// WARNING: Once we set_default_netif() manually, we disable the automatic prio-routing
int next = (i + 1) % num_of_ifaces;
while (ifaces[i] != ifaces[next]) {
if (ifaces[next]->connected) {
ESP_LOGE(TAG, "Trying another interface: %s", ifaces[next]->name);
esp_netif_set_default_netif(ifaces[next]->netif);
break;
}
++next;
next = next % num_of_ifaces;
}
}
}
ESP_LOGI(TAG, "Stop and cleanup all interfaces");
for (int i = 0; i < num_of_ifaces; ++i) {
if (ifaces[i]) {
ifaces[i]->destroy(ifaces[i]);
}
}
}

View File

@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* PPPoS Initialization
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_netif.h"
#include "esp_netif_ppp.h"
#include "mqtt_client.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "iface_info.h"
#include "ppp_connect.h"
static const int CONNECT_BIT = BIT0;
static const char *TAG = "pppos_connect";
static EventGroupHandle_t event_group = NULL;
static void on_ppp_changed(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
ESP_LOGI(TAG, "PPP state changed event %" PRIu32, event_id);
if (event_id == NETIF_PPP_ERRORUSER) {
struct ppp_info_t *ppp_info = arg;
esp_netif_t *netif = event_data;
ESP_LOGI(TAG, "User interrupted event from netif:%p", netif);
ppp_info->parent.connected = false;
}
}
static void on_ip_event(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
ESP_LOGD(TAG, "IP event! %" PRIu32, event_id);
struct ppp_info_t *ppp_info = arg;
if (event_id == IP_EVENT_PPP_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Modem Connect to PPP Server");
ESP_LOGI(TAG, "~~~~~~~~~~~~~~");
ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask));
ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw));
for (int i = 0; i < 2; ++i) {
esp_netif_get_dns_info(ppp_info->parent.netif, i, &ppp_info->parent.dns[i]);
ESP_LOGI(TAG, "DNS %i:" IPSTR, i, IP2STR(&ppp_info->parent.dns[i].ip.u_addr.ip4));
}
ESP_LOGI(TAG, "~~~~~~~~~~~~~~");
xEventGroupSetBits(event_group, CONNECT_BIT);
ppp_info->parent.connected = true;
ESP_LOGI(TAG, "GOT ip event!!!");
} else if (event_id == IP_EVENT_PPP_LOST_IP) {
ESP_LOGI(TAG, "Modem Disconnect from PPP Server");
ppp_info->parent.connected = false;
} else if (event_id == IP_EVENT_GOT_IP6) {
ESP_LOGI(TAG, "GOT IPv6 event!");
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
ESP_LOGI(TAG, "Got IPv6 address " IPV6STR, IPV62STR(event->ip6_info.ip));
}
}
static void ppp_destroy(iface_info_t *info)
{
struct ppp_info_t *ppp_info = __containerof(info, struct ppp_info_t, parent);
esp_netif_action_disconnected(ppp_info->parent.netif, 0, 0, 0);
esp_netif_action_stop(ppp_info->parent.netif, 0, 0, 0);
ppp_destroy_context(ppp_info);
vEventGroupDelete(event_group);
ppp_info->stop_task = true;
free(info);
}
iface_info_t *init_ppp(int prio)
{
struct ppp_info_t *ppp_info = calloc(1, sizeof(struct ppp_info_t));
assert(ppp_info);
ppp_info->parent.destroy = ppp_destroy;
ppp_info->parent.name = "Modem";
event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &on_ip_event, ppp_info));
ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, ppp_info));
esp_netif_inherent_config_t base_netif_cfg = ESP_NETIF_INHERENT_DEFAULT_PPP();
base_netif_cfg.route_prio = prio;
esp_netif_config_t netif_ppp_config = { .base = &base_netif_cfg,
.driver = ppp_driver_cfg,
.stack = ESP_NETIF_NETSTACK_DEFAULT_PPP
};
ppp_info->parent.netif = esp_netif_new(&netif_ppp_config);
if (ppp_info->parent.netif == NULL) {
goto err;
}
if (xTaskCreate(ppp_task, "ppp_retry_task", 4096, ppp_info, 5, NULL) != pdTRUE) {
goto err;
}
ESP_LOGI(TAG, "Waiting for IP address");
xEventGroupWaitBits(event_group, CONNECT_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));
return &ppp_info->parent;
err:
ppp_destroy(&ppp_info->parent);
return NULL;
}

View File

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
struct ppp_info_t {
iface_info_t parent;
void *context;
bool stop_task;
};
extern const esp_netif_driver_ifconfig_t *ppp_driver_cfg;
void ppp_task(void *args);
void ppp_destroy_context(struct ppp_info_t *ppp_info);

View File

@ -0,0 +1,109 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#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_log.h"
#include "sdkconfig.h"
#include "iface_info.h"
#include "ppp_connect.h"
static const char *TAG = "ppp_esp_modem";
const esp_netif_driver_ifconfig_t *ppp_driver_cfg = NULL;
void ppp_task(void *args)
{
struct ppp_info_t *ppp_info = args;
int backoff_time = 15000;
const int max_backoff = 60000;
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_PPP_APN);
esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
dte_config.uart_config.tx_io_num = CONFIG_EXAMPLE_PPP_UART_TX_PIN;
dte_config.uart_config.rx_io_num = CONFIG_EXAMPLE_PPP_UART_RX_PIN;
esp_modem_dce_t *dce = esp_modem_new(&dte_config, &dce_config, ppp_info->parent.netif);
ppp_info->context = dce;
int rssi, ber;
esp_err_t ret = esp_modem_get_signal_quality(dce, &rssi, &ber);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_modem_get_signal_quality failed with %d %s", ret, esp_err_to_name(ret));
goto failed;
}
ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d", rssi, ber);
ret = esp_modem_set_mode(dce, ESP_MODEM_MODE_DATA);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_modem_set_mode(ESP_MODEM_MODE_DATA) failed with %d", ret);
goto failed;
}
failed:
#define CONTINUE_LATER() backoff_time *= 2; \
if (backoff_time > max_backoff) { backoff_time = max_backoff; } \
continue;
// now let's keep retrying
while (!ppp_info->stop_task) {
vTaskDelay(pdMS_TO_TICKS(backoff_time));
if (ppp_info->parent.connected) {
backoff_time = 5000;
continue;
}
// try if the modem got stuck in data mode
ESP_LOGI(TAG, "Trying to Sync with modem");
ret = esp_modem_sync(dce);
if (ret != ESP_OK) {
ESP_LOGI(TAG, "Switching to command mode");
esp_modem_set_mode(dce, ESP_MODEM_MODE_COMMAND);
ESP_LOGI(TAG, "Retry sync 3 times");
for (int i = 0; i < 3; ++i) {
ret = esp_modem_sync(dce);
if (ret == ESP_OK) {
break;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
if (ret != ESP_OK) {
CONTINUE_LATER();
}
}
ESP_LOGI(TAG, "Manual hang-up before reconnecting");
ret = esp_modem_at(dce, "ATH", NULL, 2000);
if (ret != ESP_OK) {
CONTINUE_LATER();
}
ret = esp_modem_get_signal_quality(dce, &rssi, &ber);
if (ret != ESP_OK) {
CONTINUE_LATER();
}
ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d", rssi, ber);
ret = esp_modem_set_mode(dce, ESP_MODEM_MODE_DATA);
if (ret != ESP_OK) {
CONTINUE_LATER();
}
}
#undef CONTINUE_LATER
vTaskDelete(NULL);
}
void ppp_destroy_context(struct ppp_info_t *ppp_info)
{
esp_modem_dce_t *dce = ppp_info->context;
esp_err_t 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);
return;
}
esp_modem_destroy(dce);
}

View File

@ -0,0 +1,122 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "esp_netif.h"
#include "esp_log.h"
#include "esp_event.h"
#include "sdkconfig.h"
#include "iface_info.h"
#include "ppp_connect.h"
#include "driver/uart.h"
static const char *TAG = "ppp_connect_simple";
static esp_err_t transmit(void *h, void *buffer, size_t len)
{
ESP_LOG_BUFFER_HEXDUMP("ppp_connect_tx", buffer, len, ESP_LOG_VERBOSE);
uart_write_bytes(UART_NUM_1, buffer, len);
return ESP_OK;
}
static esp_netif_driver_ifconfig_t driver_cfg = {
.handle = (void *)1, // singleton driver, just to != NULL
.transmit = transmit,
};
const esp_netif_driver_ifconfig_t *ppp_driver_cfg = &driver_cfg;
#define BUF_SIZE (1024)
#define CONNECTED "CONNECT 115200"
void ppp_task(void *args)
{
struct ppp_info_t *ppp_info = args;
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
QueueHandle_t event_queue;
ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, BUF_SIZE, 0, 16, &event_queue, 0));
ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, CONFIG_EXAMPLE_PPP_UART_TX_PIN, CONFIG_EXAMPLE_PPP_UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(uart_set_rx_timeout(UART_NUM_1, 1));
char *buffer = malloc(BUF_SIZE);
ppp_info->context = buffer;
const struct seq_t {
const char *cmd;
const char *expect;
bool allow_fail;
} init_sequence[] = {
{ .cmd = "AT\r\n", .expect = "OK" },
{ .cmd = "AT+CGDCONT=1,\"IP\",\"" CONFIG_EXAMPLE_MODEM_PPP_APN "\"\r\n", .expect = "OK" },
{ .cmd = "ATD*99##\r\n", .expect = "CONNECT", .allow_fail = true },
{ .cmd = "ATO\r\n", .expect = "CONNECT" },
};
int cmd_i = 0;
int retry = 0;
char *reply = buffer;
const int max_retries = 3;
uart_event_t event;
uart_write_bytes(UART_NUM_1, "+++", 3);
vTaskDelay(pdMS_TO_TICKS(1000));
while (retry < max_retries) {
ESP_LOGD(TAG, "Sending command: %s", init_sequence[cmd_i].cmd);
uart_write_bytes(UART_NUM_1, init_sequence[cmd_i].cmd, strlen(init_sequence[cmd_i].cmd));
xQueueReceive(event_queue, &event, pdMS_TO_TICKS(pdMS_TO_TICKS(1000)));
size_t len;
uart_get_buffered_data_len(UART_NUM_1, &len);
if (!len) {
continue;
}
len = uart_read_bytes(UART_NUM_1, reply, BUF_SIZE, 0);
ESP_LOGD(TAG, "Received: %.*s", len, reply);
if (strstr(reply, init_sequence[cmd_i].expect) || init_sequence[cmd_i].allow_fail) {
if (strstr(reply, CONNECTED)) { // are we connected already?
break;
}
cmd_i++;
continue;
}
++retry;
vTaskDelay(pdMS_TO_TICKS(retry * 1000));
}
if (retry >= max_retries) {
ESP_LOGE(TAG, "Failed to perform initial modem connection");
vTaskDelete(NULL);
}
ESP_LOGI(TAG, "Modem configured correctly, switching to PPP protocol");
esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, ppp_info->parent.netif);
esp_netif_action_start(ppp_info->parent.netif, 0, 0, 0);
while (!ppp_info->stop_task) {
xQueueReceive(event_queue, &event, pdMS_TO_TICKS(pdMS_TO_TICKS(1000)));
if (event.type == UART_DATA) {
size_t len;
uart_get_buffered_data_len(UART_NUM_1, &len);
if (len) {
len = uart_read_bytes(UART_NUM_1, buffer, BUF_SIZE, 0);
ESP_LOG_BUFFER_HEXDUMP("ppp_uart_recv", buffer, len, ESP_LOG_VERBOSE);
esp_netif_receive(ppp_info->parent.netif, buffer, len, NULL);
}
} else {
ESP_LOGW(TAG, "Received UART event: %d", event.type);
}
}
}
void ppp_destroy_context(struct ppp_info_t *ppp_info)
{
char *buffer = ppp_info->context;
ppp_info->stop_task = true;
vTaskDelay(pdMS_TO_TICKS(1000));
free(buffer);
uart_driver_delete(UART_NUM_1);
}

View File

@ -0,0 +1,128 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* WiFi connection
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "iface_info.h"
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "wifi_connect";
static int s_retry_num = 0;
static EventGroupHandle_t s_wifi_event_group;
static void event_handler(void *args, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
struct iface_info_t *wifi_info = args;
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
wifi_info->connected = false;
if (s_retry_num < CONFIG_ESP_MAXIMUM_RETRY || CONFIG_ESP_MAXIMUM_RETRY == 0) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG, "connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
const esp_netif_ip_info_t *ip_info = &event->ip_info;
ESP_LOGI(TAG, "WiFi station Got IP Address");
ESP_LOGI(TAG, "~~~~~~~~~~~");
ESP_LOGI(TAG, "IP:" IPSTR, IP2STR(&ip_info->ip));
ESP_LOGI(TAG, "MASK:" IPSTR, IP2STR(&ip_info->netmask));
ESP_LOGI(TAG, "GW:" IPSTR, IP2STR(&ip_info->gw));
ESP_LOGI(TAG, "~~~~~~~~~~~");
for (int i = 0; i < 2; ++i) {
esp_netif_get_dns_info(wifi_info->netif, i, &wifi_info->dns[i]);
ESP_LOGI(TAG, "DNS %i:" IPSTR, i, IP2STR(&wifi_info->dns[i].ip.u_addr.ip4));
}
ESP_LOGI(TAG, "~~~~~~~~~~~");
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
wifi_info->connected = true;
}
}
static void wifi_destroy(iface_info_t *info)
{
esp_netif_action_disconnected(info->netif, 0, 0, 0);
esp_netif_action_stop(info->netif, 0, 0, 0);
esp_wifi_stop();
esp_wifi_deinit();
free(info);
}
iface_info_t *wifi_init(int prio)
{
struct iface_info_t *wifi_info = malloc(sizeof(iface_info_t));
assert(wifi_info);
wifi_info->destroy = wifi_destroy;
wifi_info->name = "WiFi station";
s_wifi_event_group = xEventGroupCreate();
esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA();
esp_netif_config.route_prio = prio;
wifi_info->netif = esp_netif_create_wifi(WIFI_IF_STA, &esp_netif_config);
esp_wifi_set_default_wifi_sta_handlers();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, event_handler, wifi_info));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, event_handler, wifi_info));
wifi_config_t wifi_config = {
.sta = {
.ssid = CONFIG_ESP_WIFI_SSID,
.password = CONFIG_ESP_WIFI_PASSWORD,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection or a failure (connection failed or a timeout) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, (WIFI_CONNECTED_BIT | WIFI_FAIL_BIT), pdFALSE, pdFALSE, pdMS_TO_TICKS(5000));
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
wifi_destroy(wifi_info);
wifi_info = NULL;
} else if (CONFIG_ESP_MAXIMUM_RETRY == 0) {
ESP_LOGI(TAG, "No connection at the moment, will keep retrying...");
} else {
ESP_LOGE(TAG, "Failed to connect withing specified timeout");
wifi_destroy(wifi_info);
wifi_info = NULL;
}
return wifi_info;
}

View File

@ -0,0 +1,2 @@
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_NOTIFY_PHASE_SUPPORT=y

View File

@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.16)
# For ESP32 platform target
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
if(${IDF_TARGET} STREQUAL "linux")
if("${IDF_TARGET}" STREQUAL "linux")
# For linux-target we have two options:
# - With lwIP (must be defined on command line, e.g. idf.py -DWITH_LWIP=1)
# access networking from linux `tap` interface (TAP networking mode)

View File

@ -1,2 +0,0 @@
CONFIG_IDF_TARGET="linux"
# CONFIG_ESP_EVENT_POST_FROM_ISR is not set

View File

@ -0,0 +1,3 @@
CONFIG_IDF_TARGET="esp32h2"
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y

View File

@ -0,0 +1,2 @@
CONFIG_IDF_TARGET="linux"
# CONFIG_ESP_EVENT_POST_FROM_ISR is not set