├── .clang-format ├── .clang-format copy ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── question.md ├── dependabot.yml ├── stale.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── Kconfig.projbuild ├── LICENSE ├── README.ESP32Async.md ├── README.md ├── arduino-cli-dev.yaml ├── arduino-cli.yaml ├── component.mk ├── examples └── Client │ └── Client.ino ├── library.json ├── library.properties ├── platformio.ini └── src ├── AsyncTCP.cpp └── AsyncTCP.h /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: LLVM 3 | 4 | AccessModifierOffset: -2 5 | AlignConsecutiveMacros: true 6 | AllowAllArgumentsOnNextLine: false 7 | AllowAllParametersOfDeclarationOnNextLine: false 8 | AllowShortIfStatementsOnASingleLine: false 9 | AllowShortLambdasOnASingleLine: Inline 10 | BinPackArguments: false 11 | ColumnLimit: 0 12 | ContinuationIndentWidth: 2 13 | FixNamespaceComments: false 14 | IndentAccessModifiers: true 15 | IndentCaseLabels: true 16 | IndentPPDirectives: BeforeHash 17 | IndentWidth: 2 18 | NamespaceIndentation: All 19 | PointerAlignment: Left 20 | ReferenceAlignment: Left 21 | TabWidth: 2 22 | UseTab: Never 23 | -------------------------------------------------------------------------------- /.clang-format copy: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: LLVM 3 | 4 | AccessModifierOffset: -2 5 | AlignConsecutiveMacros: true 6 | AllowAllArgumentsOnNextLine: false 7 | AllowAllParametersOfDeclarationOnNextLine: false 8 | AllowShortIfStatementsOnASingleLine: false 9 | AllowShortLambdasOnASingleLine: Inline 10 | BinPackArguments: false 11 | ColumnLimit: 0 12 | ContinuationIndentWidth: 2 13 | FixNamespaceComments: false 14 | IndentAccessModifiers: true 15 | IndentCaseLabels: true 16 | IndentPPDirectives: BeforeHash 17 | IndentWidth: 2 18 | NamespaceIndentation: All 19 | PointerAlignment: Left 20 | ReferenceAlignment: Left 21 | TabWidth: 2 22 | UseTab: Never 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Please make sure to go through the recommendations before opening a bug report:** 11 | 12 | [https://github.com/ESP32Async/AsyncTCP?tab=readme-ov-file#important-recommendations](https://github.com/ESP32Async/AsyncTCP?tab=readme-ov-file#important-recommendations) 13 | 14 | **Description** 15 | 16 | A clear and concise description of what the bug is. 17 | 18 | **Board** 19 | 20 | esp32dev, esp32s3, etc 21 | 22 | **Ethernet adapter used ?** 23 | 24 | If yes, please specify which one 25 | 26 | **Stack trace** 27 | 28 | Please provide the stack trace here taken with `monitor_filters = esp32_exception_decoder`. 29 | **Any issue opened with a non readable stack trace will be ignored because not helpful at all.** 30 | 31 | As an alternative, you can use [https://maximeborges.github.io/esp-stacktrace-decoder/](https://maximeborges.github.io/esp-stacktrace-decoder/). 32 | 33 | **Additional notes** 34 | 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Describe your question 4 | title: "[Q]" 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | # Check for updates to GitHub Actions every week 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | daysUntilStale: 60 4 | daysUntilClose: 14 5 | limitPerRun: 30 6 | staleLabel: stale 7 | exemptLabels: 8 | - pinned 9 | - security 10 | - "to be implemented" 11 | - "for reference" 12 | - "move to PR" 13 | - "enhancement" 14 | 15 | only: issues 16 | onlyLabels: [] 17 | exemptProjects: false 18 | exemptMilestones: false 19 | exemptAssignees: false 20 | 21 | markComment: > 22 | [STALE_SET] This issue has been automatically marked as stale because it has not had 23 | recent activity. It will be closed in 14 days if no further activity occurs. Thank you 24 | for your contributions. 25 | 26 | unmarkComment: > 27 | [STALE_CLR] This issue has been removed from the stale queue. Please ensure activity to keep it openin the future. 28 | 29 | closeComment: > 30 | [STALE_DEL] This stale issue has been automatically closed. Thank you for your contributions. 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Async TCP CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | build-arduino: 14 | name: ${{ matrix.config }} 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | config: [arduino-cli.yaml, arduino-cli-dev.yaml] 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: arduino/setup-arduino-cli@v1 23 | - name: Download board 24 | run: | 25 | arduino-cli --config-file ${{ matrix.config }} core update-index 26 | arduino-cli --config-file ${{ matrix.config }} board listall 27 | arduino-cli --config-file ${{ matrix.config }} core install esp32:esp32 28 | - name: Compile Sketch 29 | run: arduino-cli --config-file ${{ matrix.config }} --library ./src/ compile --fqbn esp32:esp32:esp32 ./examples/Client/Client.ino 30 | - name: Compile Sketch with IPv6 31 | env: 32 | LWIP_IPV6: true 33 | run: arduino-cli --config-file ${{ matrix.config }} --library ./src/ compile --fqbn esp32:esp32:esp32 ./examples/Client/Client.ino 34 | 35 | platformio: 36 | name: "pio:${{ matrix.env }}:${{ matrix.board }}" 37 | runs-on: ubuntu-latest 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | include: 42 | - env: ci-arduino-2 43 | board: esp32dev 44 | - env: ci-arduino-2 45 | board: esp32-s2-saola-1 46 | - env: ci-arduino-2 47 | board: esp32-s3-devkitc-1 48 | - env: ci-arduino-2 49 | board: esp32-c3-devkitc-02 50 | 51 | - env: ci-arduino-3 52 | board: esp32dev 53 | - env: ci-arduino-3 54 | board: esp32-s2-saola-1 55 | - env: ci-arduino-3 56 | board: esp32-s3-devkitc-1 57 | - env: ci-arduino-3 58 | board: esp32-c3-devkitc-02 59 | - env: ci-arduino-3 60 | board: esp32-c6-devkitc-1 61 | 62 | - env: ci-arduino-311 63 | board: esp32dev 64 | - env: ci-arduino-311 65 | board: esp32-s2-saola-1 66 | - env: ci-arduino-311 67 | board: esp32-s3-devkitc-1 68 | - env: ci-arduino-311 69 | board: esp32-c3-devkitc-02 70 | - env: ci-arduino-311 71 | board: esp32-c6-devkitc-1 72 | 73 | steps: 74 | - name: Checkout 75 | uses: actions/checkout@v4 76 | 77 | - name: Cache PlatformIO 78 | uses: actions/cache@v4 79 | with: 80 | key: ${{ runner.os }}-pio 81 | path: | 82 | ~/.cache/pip 83 | ~/.platformio 84 | 85 | - name: Python 86 | uses: actions/setup-python@v5 87 | with: 88 | python-version: "3.x" 89 | 90 | - name: Build 91 | run: | 92 | python -m pip install --upgrade pip 93 | pip install --upgrade platformio 94 | 95 | - run: PLATFORMIO_SRC_DIR=examples/Client PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }} 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .lh 3 | /.pio 4 | /.vscode 5 | 6 | /logs 7 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-python-3.11 2 | USER gitpod 3 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - command: pip install --upgrade pip && pip install -U platformio && platformio run 3 | 4 | image: 5 | file: .gitpod.Dockerfile 6 | 7 | vscode: 8 | extensions: 9 | - shardulm94.trailing-spaces 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(COMPONENT_SRCDIRS 2 | "src" 3 | ) 4 | 5 | set(COMPONENT_ADD_INCLUDEDIRS 6 | "src" 7 | ) 8 | 9 | set(COMPONENT_REQUIRES 10 | "arduino-esp32" 11 | ) 12 | 13 | register_component() 14 | 15 | target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti) 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | https://sidweb.nl/cms3/en/contact. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 121 | 122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 123 | enforcement ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | https://www.contributor-covenant.org/faq. Translations are available at 129 | https://www.contributor-covenant.org/translations. 130 | -------------------------------------------------------------------------------- /Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "AsyncTCP Configuration" 2 | 3 | choice ASYNC_TCP_RUNNING_CORE 4 | bool "Core on which AsyncTCP's thread is running" 5 | default ASYNC_TCP_RUN_CORE1 6 | help 7 | Select on which core AsyncTCP is running 8 | 9 | config ASYNC_TCP_RUN_CORE0 10 | bool "CORE 0" 11 | config ASYNC_TCP_RUN_CORE1 12 | bool "CORE 1" 13 | config ASYNC_TCP_RUN_NO_AFFINITY 14 | bool "BOTH" 15 | 16 | endchoice 17 | 18 | config ASYNC_TCP_RUNNING_CORE 19 | int 20 | default 0 if ASYNC_TCP_RUN_CORE0 21 | default 1 if ASYNC_TCP_RUN_CORE1 22 | default -1 if ASYNC_TCP_RUN_NO_AFFINITY 23 | 24 | config ASYNC_TCP_USE_WDT 25 | bool "Enable WDT for the AsyncTCP task" 26 | default "y" 27 | help 28 | Enable WDT for the AsyncTCP task, so it will trigger if a handler is locking the thread. 29 | 30 | endmenu 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.ESP32Async.md: -------------------------------------------------------------------------------- 1 | # AsyncTCP 2 | 3 | [![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) 4 | [![Continuous Integration](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml/badge.svg)](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml) 5 | [![PlatformIO Registry](https://badges.registry.platformio.org/packages/ESP32Async/library/AsyncTCP.svg)](https://registry.platformio.org/libraries/ESP32Async/AsyncTCP) 6 | 7 | Discord Server: [https://discord.gg/X7zpGdyUcY](https://discord.gg/X7zpGdyUcY) 8 | 9 | ### Async TCP Library for ESP32 Arduino 10 | 11 | This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs. 12 | 13 | This library is the base for [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) 14 | 15 | ## AsyncClient and AsyncServer 16 | 17 | The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use. 18 | 19 | ## Changes 20 | 21 | - `library.properties` for Arduino IDE users 22 | - Add `CONFIG_ASYNC_TCP_MAX_ACK_TIME` 23 | - Add `CONFIG_ASYNC_TCP_PRIORITY` 24 | - Add `CONFIG_ASYNC_TCP_QUEUE_SIZE` 25 | - Add `setKeepAlive()` 26 | - Arduino 3 / ESP-IDF 5 compatibility 27 | - Better CI 28 | - Better example 29 | - Customizable macros 30 | - Fix for "Required to lock TCPIP core functionality". Ref: https://github.com/ESP32Async/AsyncTCP/issues/27 and https://github.com/espressif/arduino-esp32/issues/10526 31 | - Fix for "ack timeout 4" client disconnects. 32 | - Fix from https://github.com/me-no-dev/AsyncTCP/pull/173 (partially applied) 33 | - Fix from https://github.com/me-no-dev/AsyncTCP/pull/184 34 | - IPv6 35 | - LIBRETINY support 36 | - LibreTuya 37 | - Reduce logging of non critical messages 38 | - Use IPADDR6_INIT() macro to set connecting IPv6 address 39 | - xTaskCreateUniversal function 40 | 41 | ## Coordinates 42 | 43 | ``` 44 | ESP32Async/AsyncTCP @ ^3.3.2 45 | ``` 46 | 47 | ## Important recommendations 48 | 49 | Most of the crashes are caused by improper configuration of the library for the project. 50 | Here are some recommendations to avoid them. 51 | 52 | I personally use the following configuration in my projects: 53 | 54 | ```c++ 55 | -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default) 56 | -D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default) 57 | -D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default) 58 | -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as the app (default is core 0) 59 | -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K) 60 | ``` 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![https://avatars.githubusercontent.com/u/195753706?s=96&v=4](https://avatars.githubusercontent.com/u/195753706?s=96&v=4) 2 | 3 | # Project moved to [ESP32Async](https://github.com/organizations/ESP32Async) organization at [https://github.com/ESP32Async/AsyncTCP](https://github.com/ESP32Async/AsyncTCP) 4 | 5 | Discord Server: [https://discord.gg/X7zpGdyUcY](https://discord.gg/X7zpGdyUcY) 6 | 7 | Please see the new links: 8 | 9 | - `ESP32Async/ESPAsyncWebServer @ 3.6.0` (ESP32, ESP8266, RP2040) 10 | - `ESP32Async/AsyncTCP @ 3.3.2` (ESP32) 11 | - `ESP32Async/ESPAsyncTCP @ 2.0.0` (ESP8266) 12 | - `https://github.com/ESP32Async/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip` (AsyncTCP alternative for ESP32) 13 | - `khoih-prog/AsyncTCP_RP2040W @ 1.2.0` (RP2040) 14 | -------------------------------------------------------------------------------- /arduino-cli-dev.yaml: -------------------------------------------------------------------------------- 1 | board_manager: 2 | additional_urls: 3 | - https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json 4 | directories: 5 | builtin.libraries: ./src/ 6 | build_cache: 7 | compilations_before_purge: 10 8 | ttl: 720h0m0s 9 | daemon: 10 | port: "50051" 11 | library: 12 | enable_unsafe_install: false 13 | logging: 14 | file: "" 15 | format: text 16 | level: info 17 | metrics: 18 | addr: :9090 19 | enabled: true 20 | output: 21 | no_color: false 22 | sketch: 23 | always_export_binaries: false 24 | updater: 25 | enable_notification: true 26 | -------------------------------------------------------------------------------- /arduino-cli.yaml: -------------------------------------------------------------------------------- 1 | board_manager: 2 | additional_urls: 3 | - https://espressif.github.io/arduino-esp32/package_esp32_index.json 4 | directories: 5 | builtin.libraries: ./src/ 6 | build_cache: 7 | compilations_before_purge: 10 8 | ttl: 720h0m0s 9 | daemon: 10 | port: "50051" 11 | library: 12 | enable_unsafe_install: false 13 | logging: 14 | file: "" 15 | format: text 16 | level: info 17 | metrics: 18 | addr: :9090 19 | enabled: true 20 | output: 21 | no_color: false 22 | sketch: 23 | always_export_binaries: false 24 | updater: 25 | enable_notification: true 26 | -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | COMPONENT_ADD_INCLUDEDIRS := src 2 | COMPONENT_SRCDIRS := src 3 | CXXFLAGS += -fno-rtti 4 | -------------------------------------------------------------------------------- /examples/Client/Client.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Run a server at the root of the project with: 6 | // > python3 -m http.server 3333 7 | // Now you can open a browser and test it works by visiting http://192.168.125.122:3333/ or http://192.168.125.122:3333/README.md 8 | #define HOST "192.168.125.122" 9 | #define PORT 3333 10 | 11 | // WiFi SSID to connect to 12 | #define WIFI_SSID "IoT" 13 | 14 | // 16 slots on esp32 (CONFIG_LWIP_MAX_ACTIVE_TCP) 15 | #define MAX_CLIENTS CONFIG_LWIP_MAX_ACTIVE_TCP 16 | // #define MAX_CLIENTS 1 17 | 18 | size_t permits = MAX_CLIENTS; 19 | 20 | void makeRequest() { 21 | if (!permits) 22 | return; 23 | 24 | Serial.printf("** permits: %d\n", permits); 25 | 26 | AsyncClient* client = new AsyncClient; 27 | 28 | client->onError([](void* arg, AsyncClient* client, int8_t error) { 29 | Serial.printf("** error occurred %s \n", client->errorToString(error)); 30 | client->close(true); 31 | delete client; 32 | }); 33 | 34 | client->onConnect([](void* arg, AsyncClient* client) { 35 | permits--; 36 | Serial.printf("** client has been connected: %" PRIu16 "\n", client->localPort()); 37 | 38 | client->onDisconnect([](void* arg, AsyncClient* client) { 39 | Serial.printf("** client has been disconnected: %" PRIu16 "\n", client->localPort()); 40 | client->close(true); 41 | delete client; 42 | 43 | permits++; 44 | makeRequest(); 45 | }); 46 | 47 | client->onData([](void* arg, AsyncClient* client, void* data, size_t len) { 48 | Serial.printf("** data received by client: %" PRIu16 ": len=%u\n", client->localPort(), len); 49 | }); 50 | 51 | client->write("GET /README.md HTTP/1.1\r\nHost: " HOST "\r\nUser-Agent: ESP\r\nConnection: close\r\n\r\n"); 52 | }); 53 | 54 | if (client->connect(HOST, PORT)) { 55 | } else { 56 | Serial.println("** connection failed"); 57 | } 58 | } 59 | 60 | void setup() { 61 | Serial.begin(115200); 62 | while (!Serial) 63 | continue; 64 | 65 | WiFi.mode(WIFI_STA); 66 | WiFi.begin(WIFI_SSID); 67 | while (WiFi.status() != WL_CONNECTED) { 68 | delay(500); 69 | Serial.print("."); 70 | } 71 | Serial.println("** connected to WiFi"); 72 | Serial.println(WiFi.localIP()); 73 | 74 | for (size_t i = 0; i < MAX_CLIENTS; i++) 75 | makeRequest(); 76 | } 77 | 78 | void loop() { 79 | delay(1000); 80 | Serial.printf("** free heap: %" PRIu32 "\n", ESP.getFreeHeap()); 81 | } 82 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AsyncTCP", 3 | "version": "3.3.2", 4 | "description": "Asynchronous TCP Library for ESP32", 5 | "keywords": "async,tcp", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/ESP32Async/AsyncTCP.git" 9 | }, 10 | "authors": 11 | { 12 | "name": "ESP32Async", 13 | "maintainer": true 14 | }, 15 | "license": "LGPL-3.0", 16 | "frameworks": "arduino", 17 | "platforms": [ 18 | "espressif32", 19 | "libretiny" 20 | ], 21 | "export": { 22 | "include": [ 23 | "examples", 24 | "src", 25 | "library.json", 26 | "library.properties", 27 | "LICENSE", 28 | "README.md" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Async TCP 2 | includes=AsyncTCP.h 3 | version=3.3.2 4 | author=ESP32Async 5 | maintainer=ESP32Async 6 | sentence=Async TCP Library for ESP32 7 | paragraph=Async TCP Library for ESP32 8 | category=Other 9 | url=https://github.com/ESP32Async/AsyncTCP.git 10 | architectures=* 11 | license=LGPL-3.0 12 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | [platformio] 2 | default_envs = arduino-2, arduino-3, arduino-311 3 | lib_dir = . 4 | src_dir = examples/Client 5 | 6 | [env] 7 | framework = arduino 8 | build_flags = 9 | -Wall -Wextra 10 | -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 11 | -D CONFIG_ASYNC_TCP_PRIORITY=10 12 | -D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 13 | -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 14 | -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 15 | -D CONFIG_ARDUHAL_LOG_COLORS 16 | -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG 17 | upload_protocol = esptool 18 | monitor_speed = 115200 19 | monitor_filters = esp32_exception_decoder, log2file 20 | board = esp32dev 21 | 22 | [env:arduino-2] 23 | platform = espressif32@6.9.0 24 | 25 | [env:arduino-3] 26 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip 27 | 28 | [env:arduino-311] 29 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip 30 | 31 | ; CI 32 | 33 | [env:ci-arduino-2] 34 | platform = espressif32@6.9.0 35 | board = ${sysenv.PIO_BOARD} 36 | 37 | [env:ci-arduino-3] 38 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip 39 | board = ${sysenv.PIO_BOARD} 40 | 41 | [env:ci-arduino-311] 42 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip 43 | board = ${sysenv.PIO_BOARD} 44 | -------------------------------------------------------------------------------- /src/AsyncTCP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous TCP library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | 22 | #include "Arduino.h" 23 | 24 | #include "AsyncTCP.h" 25 | 26 | extern "C" { 27 | #include "lwip/dns.h" 28 | #include "lwip/err.h" 29 | #include "lwip/inet.h" 30 | #include "lwip/opt.h" 31 | #include "lwip/tcp.h" 32 | } 33 | 34 | #if CONFIG_ASYNC_TCP_USE_WDT 35 | #include "esp_task_wdt.h" 36 | #endif 37 | 38 | // Required for: 39 | // https://github.com/espressif/arduino-esp32/blob/3.0.3/libraries/Network/src/NetworkInterface.cpp#L37-L47 40 | #if ESP_IDF_VERSION_MAJOR >= 5 41 | #include 42 | #endif 43 | 44 | #define TAG "AsyncTCP" 45 | 46 | // https://github.com/espressif/arduino-esp32/issues/10526 47 | #ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING 48 | #define TCP_MUTEX_LOCK() \ 49 | if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \ 50 | LOCK_TCPIP_CORE(); \ 51 | } 52 | 53 | #define TCP_MUTEX_UNLOCK() \ 54 | if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \ 55 | UNLOCK_TCPIP_CORE(); \ 56 | } 57 | #else // CONFIG_LWIP_TCPIP_CORE_LOCKING 58 | #define TCP_MUTEX_LOCK() 59 | #define TCP_MUTEX_UNLOCK() 60 | #endif // CONFIG_LWIP_TCPIP_CORE_LOCKING 61 | 62 | #define INVALID_CLOSED_SLOT -1 63 | 64 | /* 65 | TCP poll interval is specified in terms of the TCP coarse timer interval, which is called twice a second 66 | https://github.com/espressif/esp-lwip/blob/2acf959a2bb559313cd2bf9306c24612ba3d0e19/src/core/tcp.c#L1895 67 | */ 68 | #define CONFIG_ASYNC_TCP_POLL_TIMER 1 69 | 70 | /* 71 | * TCP/IP Event Task 72 | * */ 73 | 74 | typedef enum { 75 | LWIP_TCP_SENT, 76 | LWIP_TCP_RECV, 77 | LWIP_TCP_FIN, 78 | LWIP_TCP_ERROR, 79 | LWIP_TCP_POLL, 80 | LWIP_TCP_CLEAR, 81 | LWIP_TCP_ACCEPT, 82 | LWIP_TCP_CONNECTED, 83 | LWIP_TCP_DNS 84 | } lwip_event_t; 85 | 86 | typedef struct { 87 | lwip_event_t event; 88 | void* arg; 89 | union { 90 | struct { 91 | tcp_pcb* pcb; 92 | int8_t err; 93 | } connected; 94 | struct { 95 | int8_t err; 96 | } error; 97 | struct { 98 | tcp_pcb* pcb; 99 | uint16_t len; 100 | } sent; 101 | struct { 102 | tcp_pcb* pcb; 103 | pbuf* pb; 104 | int8_t err; 105 | } recv; 106 | struct { 107 | tcp_pcb* pcb; 108 | int8_t err; 109 | } fin; 110 | struct { 111 | tcp_pcb* pcb; 112 | } poll; 113 | struct { 114 | AsyncClient* client; 115 | } accept; 116 | struct { 117 | const char* name; 118 | ip_addr_t addr; 119 | } dns; 120 | }; 121 | } lwip_event_packet_t; 122 | 123 | static QueueHandle_t _async_queue; 124 | static TaskHandle_t _async_service_task_handle = NULL; 125 | 126 | SemaphoreHandle_t _slots_lock; 127 | const int _number_of_closed_slots = CONFIG_LWIP_MAX_ACTIVE_TCP; 128 | static uint32_t _closed_slots[_number_of_closed_slots]; 129 | static uint32_t _closed_index = []() { 130 | _slots_lock = xSemaphoreCreateBinary(); 131 | xSemaphoreGive(_slots_lock); 132 | for (int i = 0; i < _number_of_closed_slots; ++i) { 133 | _closed_slots[i] = 1; 134 | } 135 | return 1; 136 | }(); 137 | 138 | static inline bool _init_async_event_queue() { 139 | if (!_async_queue) { 140 | _async_queue = xQueueCreate(CONFIG_ASYNC_TCP_QUEUE_SIZE, sizeof(lwip_event_packet_t*)); 141 | if (!_async_queue) { 142 | return false; 143 | } 144 | } 145 | return true; 146 | } 147 | 148 | static inline bool _send_async_event(lwip_event_packet_t** e, TickType_t wait = portMAX_DELAY) { 149 | return _async_queue && xQueueSend(_async_queue, e, wait) == pdPASS; 150 | } 151 | 152 | static inline bool _prepend_async_event(lwip_event_packet_t** e, TickType_t wait = portMAX_DELAY) { 153 | return _async_queue && xQueueSendToFront(_async_queue, e, wait) == pdPASS; 154 | } 155 | 156 | static inline bool _get_async_event(lwip_event_packet_t** e) { 157 | if (!_async_queue) { 158 | return false; 159 | } 160 | 161 | #if CONFIG_ASYNC_TCP_USE_WDT 162 | // need to return periodically to feed the dog 163 | if (xQueueReceive(_async_queue, e, pdMS_TO_TICKS(1000)) != pdPASS) 164 | return false; 165 | #else 166 | if (xQueueReceive(_async_queue, e, portMAX_DELAY) != pdPASS) 167 | return false; 168 | #endif 169 | 170 | if ((*e)->event != LWIP_TCP_POLL) 171 | return true; 172 | 173 | /* 174 | Let's try to coalesce two (or more) consecutive poll events into one 175 | this usually happens with poor implemented user-callbacks that are runs too long and makes poll events to stack in the queue 176 | if consecutive user callback for a same connection runs longer that poll time then it will fill the queue with events until it deadlocks. 177 | This is a workaround to mitigate such poor designs and won't let other events/connections to starve the task time. 178 | It won't be effective if user would run multiple simultaneous long running callbacks due to message interleaving. 179 | todo: implement some kind of fair dequeing or (better) simply punish user for a bad designed callbacks by resetting hog connections 180 | */ 181 | lwip_event_packet_t* next_pkt = NULL; 182 | while (xQueuePeek(_async_queue, &next_pkt, 0) == pdPASS) { 183 | if (next_pkt->arg == (*e)->arg && next_pkt->event == LWIP_TCP_POLL) { 184 | if (xQueueReceive(_async_queue, &next_pkt, 0) == pdPASS) { 185 | free(next_pkt); 186 | next_pkt = NULL; 187 | log_d("coalescing polls, network congestion or async callbacks might be too slow!"); 188 | continue; 189 | } 190 | } 191 | 192 | // quit while loop if next event can't be discarded 193 | break; 194 | } 195 | 196 | /* 197 | now we have to decide if to proceed with poll callback handler or discard it? 198 | poor designed apps using asynctcp without proper dataflow control could flood the queue with interleaved pool/ack events. 199 | I.e. on each poll app would try to generate more data to send, which in turn results in additional ack event triggering chain effect 200 | for long connections. Or poll callback could take long time starving other connections. Anyway our goal is to keep the queue length 201 | grows under control (if possible) and poll events are the safest to discard. 202 | Let's discard poll events processing using linear-increasing probability curve when queue size grows over 3/4 203 | Poll events are periodic and connection could get another chance next time 204 | */ 205 | if (uxQueueMessagesWaiting(_async_queue) > (rand() % CONFIG_ASYNC_TCP_QUEUE_SIZE / 4 + CONFIG_ASYNC_TCP_QUEUE_SIZE * 3 / 4)) { 206 | free(*e); 207 | *e = NULL; 208 | log_d("discarding poll due to queue congestion"); 209 | // evict next event from a queue 210 | return _get_async_event(e); 211 | } 212 | 213 | // last resort return 214 | return true; 215 | } 216 | 217 | static bool _remove_events_with_arg(void* arg) { 218 | if (!_async_queue) { 219 | return false; 220 | } 221 | 222 | lwip_event_packet_t* first_packet = NULL; 223 | lwip_event_packet_t* packet = NULL; 224 | 225 | // figure out which is the first non-matching packet so we can keep the order 226 | while (!first_packet) { 227 | if (xQueueReceive(_async_queue, &first_packet, 0) != pdPASS) { 228 | return false; 229 | } 230 | // discard packet if matching 231 | if ((int)first_packet->arg == (int)arg) { 232 | free(first_packet); 233 | first_packet = NULL; 234 | } else if (xQueueSend(_async_queue, &first_packet, 0) != pdPASS) { 235 | // try to return first packet to the back of the queue 236 | // we can't wait here if queue is full, because this call has been done from the only consumer task of this queue 237 | // otherwise it would deadlock, we have to discard the event 238 | free(first_packet); 239 | first_packet = NULL; 240 | return false; 241 | } 242 | } 243 | 244 | while (xQueuePeek(_async_queue, &packet, 0) == pdPASS && packet != first_packet) { 245 | if (xQueueReceive(_async_queue, &packet, 0) != pdPASS) { 246 | return false; 247 | } 248 | if ((int)packet->arg == (int)arg) { 249 | // remove matching event 250 | free(packet); 251 | packet = NULL; 252 | // otherwise try to requeue it 253 | } else if (xQueueSend(_async_queue, &packet, 0) != pdPASS) { 254 | // we can't wait here if queue is full, because this call has been done from the only consumer task of this queue 255 | // otherwise it would deadlock, we have to discard the event 256 | free(packet); 257 | packet = NULL; 258 | return false; 259 | } 260 | } 261 | return true; 262 | } 263 | 264 | static void _handle_async_event(lwip_event_packet_t* e) { 265 | if (e->arg == NULL) { 266 | // do nothing when arg is NULL 267 | // ets_printf("event arg == NULL: 0x%08x\n", e->recv.pcb); 268 | } else if (e->event == LWIP_TCP_CLEAR) { 269 | _remove_events_with_arg(e->arg); 270 | } else if (e->event == LWIP_TCP_RECV) { 271 | // ets_printf("-R: 0x%08x\n", e->recv.pcb); 272 | AsyncClient::_s_recv(e->arg, e->recv.pcb, e->recv.pb, e->recv.err); 273 | } else if (e->event == LWIP_TCP_FIN) { 274 | // ets_printf("-F: 0x%08x\n", e->fin.pcb); 275 | AsyncClient::_s_fin(e->arg, e->fin.pcb, e->fin.err); 276 | } else if (e->event == LWIP_TCP_SENT) { 277 | // ets_printf("-S: 0x%08x\n", e->sent.pcb); 278 | AsyncClient::_s_sent(e->arg, e->sent.pcb, e->sent.len); 279 | } else if (e->event == LWIP_TCP_POLL) { 280 | // ets_printf("-P: 0x%08x\n", e->poll.pcb); 281 | AsyncClient::_s_poll(e->arg, e->poll.pcb); 282 | } else if (e->event == LWIP_TCP_ERROR) { 283 | // ets_printf("-E: 0x%08x %d\n", e->arg, e->error.err); 284 | AsyncClient::_s_error(e->arg, e->error.err); 285 | } else if (e->event == LWIP_TCP_CONNECTED) { 286 | // ets_printf("C: 0x%08x 0x%08x %d\n", e->arg, e->connected.pcb, e->connected.err); 287 | AsyncClient::_s_connected(e->arg, e->connected.pcb, e->connected.err); 288 | } else if (e->event == LWIP_TCP_ACCEPT) { 289 | // ets_printf("A: 0x%08x 0x%08x\n", e->arg, e->accept.client); 290 | AsyncServer::_s_accepted(e->arg, e->accept.client); 291 | } else if (e->event == LWIP_TCP_DNS) { 292 | // ets_printf("D: 0x%08x %s = %s\n", e->arg, e->dns.name, ipaddr_ntoa(&e->dns.addr)); 293 | AsyncClient::_s_dns_found(e->dns.name, &e->dns.addr, e->arg); 294 | } 295 | free((void*)(e)); 296 | } 297 | 298 | static void _async_service_task(void* pvParameters) { 299 | #if CONFIG_ASYNC_TCP_USE_WDT 300 | if (esp_task_wdt_add(NULL) != ESP_OK) { 301 | log_w("Failed to add async task to WDT"); 302 | } 303 | #endif 304 | lwip_event_packet_t* packet = NULL; 305 | for (;;) { 306 | if (_get_async_event(&packet)) { 307 | _handle_async_event(packet); 308 | } 309 | #if CONFIG_ASYNC_TCP_USE_WDT 310 | esp_task_wdt_reset(); 311 | #endif 312 | } 313 | #if CONFIG_ASYNC_TCP_USE_WDT 314 | esp_task_wdt_delete(NULL); 315 | #endif 316 | vTaskDelete(NULL); 317 | _async_service_task_handle = NULL; 318 | } 319 | /* 320 | static void _stop_async_task(){ 321 | if(_async_service_task_handle){ 322 | vTaskDelete(_async_service_task_handle); 323 | _async_service_task_handle = NULL; 324 | } 325 | } 326 | */ 327 | 328 | static bool customTaskCreateUniversal( 329 | TaskFunction_t pxTaskCode, 330 | const char* const pcName, 331 | const uint32_t usStackDepth, 332 | void* const pvParameters, 333 | UBaseType_t uxPriority, 334 | TaskHandle_t* const pxCreatedTask, 335 | const BaseType_t xCoreID) { 336 | #ifndef CONFIG_FREERTOS_UNICORE 337 | if (xCoreID >= 0 && xCoreID < 2) { 338 | return xTaskCreatePinnedToCore(pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask, xCoreID); 339 | } else { 340 | #endif 341 | return xTaskCreate(pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask); 342 | #ifndef CONFIG_FREERTOS_UNICORE 343 | } 344 | #endif 345 | } 346 | 347 | static bool _start_async_task() { 348 | if (!_init_async_event_queue()) { 349 | return false; 350 | } 351 | if (!_async_service_task_handle) { 352 | customTaskCreateUniversal(_async_service_task, "async_tcp", CONFIG_ASYNC_TCP_STACK_SIZE, NULL, CONFIG_ASYNC_TCP_PRIORITY, &_async_service_task_handle, CONFIG_ASYNC_TCP_RUNNING_CORE); 353 | if (!_async_service_task_handle) { 354 | return false; 355 | } 356 | } 357 | return true; 358 | } 359 | 360 | /* 361 | * LwIP Callbacks 362 | * */ 363 | 364 | static int8_t _tcp_clear_events(void* arg) { 365 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); 366 | e->event = LWIP_TCP_CLEAR; 367 | e->arg = arg; 368 | if (!_prepend_async_event(&e)) { 369 | free((void*)(e)); 370 | } 371 | return ERR_OK; 372 | } 373 | 374 | static int8_t _tcp_connected(void* arg, tcp_pcb* pcb, int8_t err) { 375 | // ets_printf("+C: 0x%08x\n", pcb); 376 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); 377 | e->event = LWIP_TCP_CONNECTED; 378 | e->arg = arg; 379 | e->connected.pcb = pcb; 380 | e->connected.err = err; 381 | if (!_prepend_async_event(&e)) { 382 | free((void*)(e)); 383 | } 384 | return ERR_OK; 385 | } 386 | 387 | static int8_t _tcp_poll(void* arg, struct tcp_pcb* pcb) { 388 | // throttle polling events queing when event queue is getting filled up, let it handle _onack's 389 | // log_d("qs:%u", uxQueueMessagesWaiting(_async_queue)); 390 | if (uxQueueMessagesWaiting(_async_queue) > (rand() % CONFIG_ASYNC_TCP_QUEUE_SIZE / 2 + CONFIG_ASYNC_TCP_QUEUE_SIZE / 4)) { 391 | log_d("throttling"); 392 | return ERR_OK; 393 | } 394 | 395 | // ets_printf("+P: 0x%08x\n", pcb); 396 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); 397 | e->event = LWIP_TCP_POLL; 398 | e->arg = arg; 399 | e->poll.pcb = pcb; 400 | // poll events are not critical 'cause those are repetitive, so we may not wait the queue in any case 401 | if (!_send_async_event(&e, 0)) { 402 | free((void*)(e)); 403 | } 404 | return ERR_OK; 405 | } 406 | 407 | static int8_t _tcp_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* pb, int8_t err) { 408 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); 409 | e->arg = arg; 410 | if (pb) { 411 | // ets_printf("+R: 0x%08x\n", pcb); 412 | e->event = LWIP_TCP_RECV; 413 | e->recv.pcb = pcb; 414 | e->recv.pb = pb; 415 | e->recv.err = err; 416 | } else { 417 | // ets_printf("+F: 0x%08x\n", pcb); 418 | e->event = LWIP_TCP_FIN; 419 | e->fin.pcb = pcb; 420 | e->fin.err = err; 421 | // close the PCB in LwIP thread 422 | AsyncClient::_s_lwip_fin(e->arg, e->fin.pcb, e->fin.err); 423 | } 424 | if (!_send_async_event(&e)) { 425 | free((void*)(e)); 426 | } 427 | return ERR_OK; 428 | } 429 | 430 | static int8_t _tcp_sent(void* arg, struct tcp_pcb* pcb, uint16_t len) { 431 | // ets_printf("+S: 0x%08x\n", pcb); 432 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); 433 | e->event = LWIP_TCP_SENT; 434 | e->arg = arg; 435 | e->sent.pcb = pcb; 436 | e->sent.len = len; 437 | if (!_send_async_event(&e)) { 438 | free((void*)(e)); 439 | } 440 | return ERR_OK; 441 | } 442 | 443 | static void _tcp_error(void* arg, int8_t err) { 444 | // ets_printf("+E: 0x%08x\n", arg); 445 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); 446 | e->event = LWIP_TCP_ERROR; 447 | e->arg = arg; 448 | e->error.err = err; 449 | if (!_send_async_event(&e)) { 450 | free((void*)(e)); 451 | } 452 | } 453 | 454 | static void _tcp_dns_found(const char* name, struct ip_addr* ipaddr, void* arg) { 455 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); 456 | // ets_printf("+DNS: name=%s ipaddr=0x%08x arg=%x\n", name, ipaddr, arg); 457 | e->event = LWIP_TCP_DNS; 458 | e->arg = arg; 459 | e->dns.name = name; 460 | if (ipaddr) { 461 | memcpy(&e->dns.addr, ipaddr, sizeof(struct ip_addr)); 462 | } else { 463 | memset(&e->dns.addr, 0, sizeof(e->dns.addr)); 464 | } 465 | if (!_send_async_event(&e)) { 466 | free((void*)(e)); 467 | } 468 | } 469 | 470 | // Used to switch out from LwIP thread 471 | static int8_t _tcp_accept(void* arg, AsyncClient* client) { 472 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); 473 | e->event = LWIP_TCP_ACCEPT; 474 | e->arg = arg; 475 | e->accept.client = client; 476 | if (!_prepend_async_event(&e)) { 477 | free((void*)(e)); 478 | } 479 | return ERR_OK; 480 | } 481 | 482 | /* 483 | * TCP/IP API Calls 484 | * */ 485 | 486 | #include "lwip/priv/tcpip_priv.h" 487 | 488 | typedef struct { 489 | struct tcpip_api_call_data call; 490 | tcp_pcb* pcb; 491 | int8_t closed_slot; 492 | int8_t err; 493 | union { 494 | struct { 495 | const char* data; 496 | size_t size; 497 | uint8_t apiflags; 498 | } write; 499 | size_t received; 500 | struct { 501 | ip_addr_t* addr; 502 | uint16_t port; 503 | tcp_connected_fn cb; 504 | } connect; 505 | struct { 506 | ip_addr_t* addr; 507 | uint16_t port; 508 | } bind; 509 | uint8_t backlog; 510 | }; 511 | } tcp_api_call_t; 512 | 513 | static err_t _tcp_output_api(struct tcpip_api_call_data* api_call_msg) { 514 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; 515 | msg->err = ERR_CONN; 516 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { 517 | msg->err = tcp_output(msg->pcb); 518 | } 519 | return msg->err; 520 | } 521 | 522 | static esp_err_t _tcp_output(tcp_pcb* pcb, int8_t closed_slot) { 523 | if (!pcb) { 524 | return ERR_CONN; 525 | } 526 | tcp_api_call_t msg; 527 | msg.pcb = pcb; 528 | msg.closed_slot = closed_slot; 529 | tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data*)&msg); 530 | return msg.err; 531 | } 532 | 533 | static err_t _tcp_write_api(struct tcpip_api_call_data* api_call_msg) { 534 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; 535 | msg->err = ERR_CONN; 536 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { 537 | msg->err = tcp_write(msg->pcb, msg->write.data, msg->write.size, msg->write.apiflags); 538 | } 539 | return msg->err; 540 | } 541 | 542 | static esp_err_t _tcp_write(tcp_pcb* pcb, int8_t closed_slot, const char* data, size_t size, uint8_t apiflags) { 543 | if (!pcb) { 544 | return ERR_CONN; 545 | } 546 | tcp_api_call_t msg; 547 | msg.pcb = pcb; 548 | msg.closed_slot = closed_slot; 549 | msg.write.data = data; 550 | msg.write.size = size; 551 | msg.write.apiflags = apiflags; 552 | tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data*)&msg); 553 | return msg.err; 554 | } 555 | 556 | static err_t _tcp_recved_api(struct tcpip_api_call_data* api_call_msg) { 557 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; 558 | msg->err = ERR_CONN; 559 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { 560 | // if(msg->closed_slot != INVALID_CLOSED_SLOT && !_closed_slots[msg->closed_slot]) { 561 | // if(msg->closed_slot != INVALID_CLOSED_SLOT) { 562 | msg->err = 0; 563 | tcp_recved(msg->pcb, msg->received); 564 | } 565 | return msg->err; 566 | } 567 | 568 | static esp_err_t _tcp_recved(tcp_pcb* pcb, int8_t closed_slot, size_t len) { 569 | if (!pcb) { 570 | return ERR_CONN; 571 | } 572 | tcp_api_call_t msg; 573 | msg.pcb = pcb; 574 | msg.closed_slot = closed_slot; 575 | msg.received = len; 576 | tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data*)&msg); 577 | return msg.err; 578 | } 579 | 580 | static err_t _tcp_close_api(struct tcpip_api_call_data* api_call_msg) { 581 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; 582 | msg->err = ERR_CONN; 583 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { 584 | msg->err = tcp_close(msg->pcb); 585 | } 586 | return msg->err; 587 | } 588 | 589 | static esp_err_t _tcp_close(tcp_pcb* pcb, int8_t closed_slot) { 590 | if (!pcb) { 591 | return ERR_CONN; 592 | } 593 | tcp_api_call_t msg; 594 | msg.pcb = pcb; 595 | msg.closed_slot = closed_slot; 596 | tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data*)&msg); 597 | return msg.err; 598 | } 599 | 600 | static err_t _tcp_abort_api(struct tcpip_api_call_data* api_call_msg) { 601 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; 602 | msg->err = ERR_CONN; 603 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { 604 | tcp_abort(msg->pcb); 605 | } 606 | return msg->err; 607 | } 608 | 609 | static esp_err_t _tcp_abort(tcp_pcb* pcb, int8_t closed_slot) { 610 | if (!pcb) { 611 | return ERR_CONN; 612 | } 613 | tcp_api_call_t msg; 614 | msg.pcb = pcb; 615 | msg.closed_slot = closed_slot; 616 | tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data*)&msg); 617 | return msg.err; 618 | } 619 | 620 | static err_t _tcp_connect_api(struct tcpip_api_call_data* api_call_msg) { 621 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; 622 | msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb); 623 | return msg->err; 624 | } 625 | 626 | static esp_err_t _tcp_connect(tcp_pcb* pcb, int8_t closed_slot, ip_addr_t* addr, uint16_t port, tcp_connected_fn cb) { 627 | if (!pcb) { 628 | return ESP_FAIL; 629 | } 630 | tcp_api_call_t msg; 631 | msg.pcb = pcb; 632 | msg.closed_slot = closed_slot; 633 | msg.connect.addr = addr; 634 | msg.connect.port = port; 635 | msg.connect.cb = cb; 636 | tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data*)&msg); 637 | return msg.err; 638 | } 639 | 640 | static err_t _tcp_bind_api(struct tcpip_api_call_data* api_call_msg) { 641 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; 642 | msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port); 643 | return msg->err; 644 | } 645 | 646 | static esp_err_t _tcp_bind(tcp_pcb* pcb, ip_addr_t* addr, uint16_t port) { 647 | if (!pcb) { 648 | return ESP_FAIL; 649 | } 650 | tcp_api_call_t msg; 651 | msg.pcb = pcb; 652 | msg.closed_slot = -1; 653 | msg.bind.addr = addr; 654 | msg.bind.port = port; 655 | tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data*)&msg); 656 | return msg.err; 657 | } 658 | 659 | static err_t _tcp_listen_api(struct tcpip_api_call_data* api_call_msg) { 660 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; 661 | msg->err = 0; 662 | msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog); 663 | return msg->err; 664 | } 665 | 666 | static tcp_pcb* _tcp_listen_with_backlog(tcp_pcb* pcb, uint8_t backlog) { 667 | if (!pcb) { 668 | return NULL; 669 | } 670 | tcp_api_call_t msg; 671 | msg.pcb = pcb; 672 | msg.closed_slot = -1; 673 | msg.backlog = backlog ? backlog : 0xFF; 674 | tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data*)&msg); 675 | return msg.pcb; 676 | } 677 | 678 | /* 679 | Async TCP Client 680 | */ 681 | 682 | AsyncClient::AsyncClient(tcp_pcb* pcb) 683 | : _connect_cb(0), _connect_cb_arg(0), _discard_cb(0), _discard_cb_arg(0), _sent_cb(0), _sent_cb_arg(0), _error_cb(0), _error_cb_arg(0), _recv_cb(0), _recv_cb_arg(0), _pb_cb(0), _pb_cb_arg(0), _timeout_cb(0), _timeout_cb_arg(0), _ack_pcb(true), _tx_last_packet(0), _rx_timeout(0), _rx_last_ack(0), _ack_timeout(CONFIG_ASYNC_TCP_MAX_ACK_TIME), _connect_port(0), prev(NULL), next(NULL) { 684 | _pcb = pcb; 685 | _closed_slot = INVALID_CLOSED_SLOT; 686 | if (_pcb) { 687 | _rx_last_packet = millis(); 688 | tcp_arg(_pcb, this); 689 | tcp_recv(_pcb, &_tcp_recv); 690 | tcp_sent(_pcb, &_tcp_sent); 691 | tcp_err(_pcb, &_tcp_error); 692 | tcp_poll(_pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER); 693 | if (!_allocate_closed_slot()) { 694 | _close(); 695 | } 696 | } 697 | } 698 | 699 | AsyncClient::~AsyncClient() { 700 | if (_pcb) { 701 | _close(); 702 | } 703 | _free_closed_slot(); 704 | } 705 | 706 | /* 707 | * Operators 708 | * */ 709 | 710 | AsyncClient& AsyncClient::operator=(const AsyncClient& other) { 711 | if (_pcb) { 712 | _close(); 713 | } 714 | 715 | _pcb = other._pcb; 716 | _closed_slot = other._closed_slot; 717 | if (_pcb) { 718 | _rx_last_packet = millis(); 719 | tcp_arg(_pcb, this); 720 | tcp_recv(_pcb, &_tcp_recv); 721 | tcp_sent(_pcb, &_tcp_sent); 722 | tcp_err(_pcb, &_tcp_error); 723 | tcp_poll(_pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER); 724 | } 725 | return *this; 726 | } 727 | 728 | bool AsyncClient::operator==(const AsyncClient& other) { 729 | return _pcb == other._pcb; 730 | } 731 | 732 | AsyncClient& AsyncClient::operator+=(const AsyncClient& other) { 733 | if (next == NULL) { 734 | next = (AsyncClient*)(&other); 735 | next->prev = this; 736 | } else { 737 | AsyncClient* c = next; 738 | while (c->next != NULL) { 739 | c = c->next; 740 | } 741 | c->next = (AsyncClient*)(&other); 742 | c->next->prev = c; 743 | } 744 | return *this; 745 | } 746 | 747 | /* 748 | * Callback Setters 749 | * */ 750 | 751 | void AsyncClient::onConnect(AcConnectHandler cb, void* arg) { 752 | _connect_cb = cb; 753 | _connect_cb_arg = arg; 754 | } 755 | 756 | void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg) { 757 | _discard_cb = cb; 758 | _discard_cb_arg = arg; 759 | } 760 | 761 | void AsyncClient::onAck(AcAckHandler cb, void* arg) { 762 | _sent_cb = cb; 763 | _sent_cb_arg = arg; 764 | } 765 | 766 | void AsyncClient::onError(AcErrorHandler cb, void* arg) { 767 | _error_cb = cb; 768 | _error_cb_arg = arg; 769 | } 770 | 771 | void AsyncClient::onData(AcDataHandler cb, void* arg) { 772 | _recv_cb = cb; 773 | _recv_cb_arg = arg; 774 | } 775 | 776 | void AsyncClient::onPacket(AcPacketHandler cb, void* arg) { 777 | _pb_cb = cb; 778 | _pb_cb_arg = arg; 779 | } 780 | 781 | void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg) { 782 | _timeout_cb = cb; 783 | _timeout_cb_arg = arg; 784 | } 785 | 786 | void AsyncClient::onPoll(AcConnectHandler cb, void* arg) { 787 | _poll_cb = cb; 788 | _poll_cb_arg = arg; 789 | } 790 | 791 | /* 792 | * Main Public Methods 793 | * */ 794 | 795 | bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) { 796 | if (_pcb) { 797 | log_d("already connected, state %d", _pcb->state); 798 | return false; 799 | } 800 | if (!_start_async_task()) { 801 | log_e("failed to start task"); 802 | return false; 803 | } 804 | 805 | if (!_allocate_closed_slot()) { 806 | log_e("failed to allocate: closed slot full"); 807 | return false; 808 | } 809 | 810 | TCP_MUTEX_LOCK(); 811 | tcp_pcb* pcb = tcp_new_ip_type(addr.type); 812 | if (!pcb) { 813 | TCP_MUTEX_UNLOCK(); 814 | log_e("pcb == NULL"); 815 | return false; 816 | } 817 | tcp_arg(pcb, this); 818 | tcp_err(pcb, &_tcp_error); 819 | tcp_recv(pcb, &_tcp_recv); 820 | tcp_sent(pcb, &_tcp_sent); 821 | tcp_poll(pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER); 822 | TCP_MUTEX_UNLOCK(); 823 | 824 | esp_err_t err = _tcp_connect(pcb, _closed_slot, &addr, port, (tcp_connected_fn)&_tcp_connected); 825 | return err == ESP_OK; 826 | } 827 | 828 | bool AsyncClient::connect(const IPAddress& ip, uint16_t port) { 829 | ip_addr_t addr; 830 | #if ESP_IDF_VERSION_MAJOR < 5 831 | addr.u_addr.ip4.addr = ip; 832 | addr.type = IPADDR_TYPE_V4; 833 | #else 834 | ip.to_ip_addr_t(&addr); 835 | #endif 836 | 837 | return _connect(addr, port); 838 | } 839 | 840 | #if LWIP_IPV6 && ESP_IDF_VERSION_MAJOR < 5 841 | bool AsyncClient::connect(const IPv6Address& ip, uint16_t port) { 842 | auto ipaddr = static_cast(ip); 843 | ip_addr_t addr = IPADDR6_INIT(ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3]); 844 | 845 | return _connect(addr, port); 846 | } 847 | #endif 848 | 849 | bool AsyncClient::connect(const char* host, uint16_t port) { 850 | ip_addr_t addr; 851 | 852 | if (!_start_async_task()) { 853 | log_e("failed to start task"); 854 | return false; 855 | } 856 | 857 | TCP_MUTEX_LOCK(); 858 | err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this); 859 | TCP_MUTEX_UNLOCK(); 860 | if (err == ERR_OK) { 861 | #if ESP_IDF_VERSION_MAJOR < 5 862 | #if LWIP_IPV6 863 | if (addr.type == IPADDR_TYPE_V6) { 864 | return connect(IPv6Address(addr.u_addr.ip6.addr), port); 865 | } 866 | return connect(IPAddress(addr.u_addr.ip4.addr), port); 867 | #else 868 | return connect(IPAddress(addr.addr), port); 869 | #endif 870 | #else 871 | return _connect(addr, port); 872 | #endif 873 | } else if (err == ERR_INPROGRESS) { 874 | _connect_port = port; 875 | return true; 876 | } 877 | log_d("error: %d", err); 878 | return false; 879 | } 880 | 881 | void AsyncClient::close(bool now) { 882 | if (_pcb) { 883 | _tcp_recved(_pcb, _closed_slot, _rx_ack_len); 884 | } 885 | _close(); 886 | } 887 | 888 | int8_t AsyncClient::abort() { 889 | if (_pcb) { 890 | _tcp_abort(_pcb, _closed_slot); 891 | _pcb = NULL; 892 | } 893 | return ERR_ABRT; 894 | } 895 | 896 | size_t AsyncClient::space() { 897 | if ((_pcb != NULL) && (_pcb->state == ESTABLISHED)) { 898 | return tcp_sndbuf(_pcb); 899 | } 900 | return 0; 901 | } 902 | 903 | size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) { 904 | if (!_pcb || size == 0 || data == NULL) { 905 | return 0; 906 | } 907 | size_t room = space(); 908 | if (!room) { 909 | return 0; 910 | } 911 | size_t will_send = (room < size) ? room : size; 912 | int8_t err = ERR_OK; 913 | err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags); 914 | if (err != ERR_OK) { 915 | return 0; 916 | } 917 | return will_send; 918 | } 919 | 920 | bool AsyncClient::send() { 921 | auto backup = _tx_last_packet; 922 | _tx_last_packet = millis(); 923 | if (_tcp_output(_pcb, _closed_slot) == ERR_OK) { 924 | return true; 925 | } 926 | _tx_last_packet = backup; 927 | return false; 928 | } 929 | 930 | size_t AsyncClient::ack(size_t len) { 931 | if (len > _rx_ack_len) 932 | len = _rx_ack_len; 933 | if (len) { 934 | _tcp_recved(_pcb, _closed_slot, len); 935 | } 936 | _rx_ack_len -= len; 937 | return len; 938 | } 939 | 940 | void AsyncClient::ackPacket(struct pbuf* pb) { 941 | if (!pb) { 942 | return; 943 | } 944 | _tcp_recved(_pcb, _closed_slot, pb->len); 945 | pbuf_free(pb); 946 | } 947 | 948 | /* 949 | * Main Private Methods 950 | * */ 951 | 952 | int8_t AsyncClient::_close() { 953 | // ets_printf("X: 0x%08x\n", (uint32_t)this); 954 | int8_t err = ERR_OK; 955 | if (_pcb) { 956 | TCP_MUTEX_LOCK(); 957 | tcp_arg(_pcb, NULL); 958 | tcp_sent(_pcb, NULL); 959 | tcp_recv(_pcb, NULL); 960 | tcp_err(_pcb, NULL); 961 | tcp_poll(_pcb, NULL, 0); 962 | TCP_MUTEX_UNLOCK(); 963 | _tcp_clear_events(this); 964 | err = _tcp_close(_pcb, _closed_slot); 965 | if (err != ERR_OK) { 966 | err = abort(); 967 | } 968 | _free_closed_slot(); 969 | _pcb = NULL; 970 | if (_discard_cb) { 971 | _discard_cb(_discard_cb_arg, this); 972 | } 973 | } 974 | return err; 975 | } 976 | 977 | bool AsyncClient::_allocate_closed_slot() { 978 | if (_closed_slot != INVALID_CLOSED_SLOT) { 979 | return true; 980 | } 981 | xSemaphoreTake(_slots_lock, portMAX_DELAY); 982 | uint32_t closed_slot_min_index = 0; 983 | for (int i = 0; i < _number_of_closed_slots; ++i) { 984 | if ((_closed_slot == INVALID_CLOSED_SLOT || _closed_slots[i] <= closed_slot_min_index) && _closed_slots[i] != 0) { 985 | closed_slot_min_index = _closed_slots[i]; 986 | _closed_slot = i; 987 | } 988 | } 989 | if (_closed_slot != INVALID_CLOSED_SLOT) { 990 | _closed_slots[_closed_slot] = 0; 991 | } 992 | xSemaphoreGive(_slots_lock); 993 | return (_closed_slot != INVALID_CLOSED_SLOT); 994 | } 995 | 996 | void AsyncClient::_free_closed_slot() { 997 | xSemaphoreTake(_slots_lock, portMAX_DELAY); 998 | if (_closed_slot != INVALID_CLOSED_SLOT) { 999 | _closed_slots[_closed_slot] = _closed_index; 1000 | _closed_slot = INVALID_CLOSED_SLOT; 1001 | ++_closed_index; 1002 | } 1003 | xSemaphoreGive(_slots_lock); 1004 | } 1005 | 1006 | /* 1007 | * Private Callbacks 1008 | * */ 1009 | 1010 | int8_t AsyncClient::_connected(tcp_pcb* pcb, int8_t err) { 1011 | _pcb = reinterpret_cast(pcb); 1012 | if (_pcb) { 1013 | _rx_last_packet = millis(); 1014 | } 1015 | if (_connect_cb) { 1016 | _connect_cb(_connect_cb_arg, this); 1017 | } 1018 | return ERR_OK; 1019 | } 1020 | 1021 | void AsyncClient::_error(int8_t err) { 1022 | if (_pcb) { 1023 | TCP_MUTEX_LOCK(); 1024 | tcp_arg(_pcb, NULL); 1025 | if (_pcb->state == LISTEN) { 1026 | tcp_sent(_pcb, NULL); 1027 | tcp_recv(_pcb, NULL); 1028 | tcp_err(_pcb, NULL); 1029 | tcp_poll(_pcb, NULL, 0); 1030 | } 1031 | TCP_MUTEX_UNLOCK(); 1032 | _free_closed_slot(); 1033 | _pcb = NULL; 1034 | } 1035 | if (_error_cb) { 1036 | _error_cb(_error_cb_arg, this, err); 1037 | } 1038 | if (_discard_cb) { 1039 | _discard_cb(_discard_cb_arg, this); 1040 | } 1041 | } 1042 | 1043 | // In LwIP Thread 1044 | int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { 1045 | if (!_pcb || pcb != _pcb) { 1046 | log_d("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); 1047 | return ERR_OK; 1048 | } 1049 | tcp_arg(_pcb, NULL); 1050 | if (_pcb->state == LISTEN) { 1051 | tcp_sent(_pcb, NULL); 1052 | tcp_recv(_pcb, NULL); 1053 | tcp_err(_pcb, NULL); 1054 | tcp_poll(_pcb, NULL, 0); 1055 | } 1056 | if (tcp_close(_pcb) != ERR_OK) { 1057 | tcp_abort(_pcb); 1058 | } 1059 | _free_closed_slot(); 1060 | _pcb = NULL; 1061 | return ERR_OK; 1062 | } 1063 | 1064 | // In Async Thread 1065 | int8_t AsyncClient::_fin(tcp_pcb* pcb, int8_t err) { 1066 | _tcp_clear_events(this); 1067 | if (_discard_cb) { 1068 | _discard_cb(_discard_cb_arg, this); 1069 | } 1070 | return ERR_OK; 1071 | } 1072 | 1073 | int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) { 1074 | _rx_last_ack = _rx_last_packet = millis(); 1075 | if (_sent_cb) { 1076 | _sent_cb(_sent_cb_arg, this, len, (_rx_last_packet - _tx_last_packet)); 1077 | } 1078 | return ERR_OK; 1079 | } 1080 | 1081 | int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) { 1082 | while (pb != NULL) { 1083 | _rx_last_packet = millis(); 1084 | // we should not ack before we assimilate the data 1085 | _ack_pcb = true; 1086 | pbuf* b = pb; 1087 | pb = b->next; 1088 | b->next = NULL; 1089 | if (_pb_cb) { 1090 | _pb_cb(_pb_cb_arg, this, b); 1091 | } else { 1092 | if (_recv_cb) { 1093 | _recv_cb(_recv_cb_arg, this, b->payload, b->len); 1094 | } 1095 | if (!_ack_pcb) { 1096 | _rx_ack_len += b->len; 1097 | } else if (_pcb) { 1098 | _tcp_recved(_pcb, _closed_slot, b->len); 1099 | } 1100 | } 1101 | pbuf_free(b); 1102 | } 1103 | return ERR_OK; 1104 | } 1105 | 1106 | int8_t AsyncClient::_poll(tcp_pcb* pcb) { 1107 | if (!_pcb) { 1108 | // log_d("pcb is NULL"); 1109 | return ERR_OK; 1110 | } 1111 | if (pcb != _pcb) { 1112 | log_d("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); 1113 | return ERR_OK; 1114 | } 1115 | 1116 | uint32_t now = millis(); 1117 | 1118 | // ACK Timeout 1119 | if (_ack_timeout) { 1120 | const uint32_t one_day = 86400000; 1121 | bool last_tx_is_after_last_ack = (_rx_last_ack - _tx_last_packet + one_day) < one_day; 1122 | if (last_tx_is_after_last_ack && (now - _tx_last_packet) >= _ack_timeout) { 1123 | log_d("ack timeout %d", pcb->state); 1124 | if (_timeout_cb) 1125 | _timeout_cb(_timeout_cb_arg, this, (now - _tx_last_packet)); 1126 | return ERR_OK; 1127 | } 1128 | } 1129 | // RX Timeout 1130 | if (_rx_timeout && (now - _rx_last_packet) >= (_rx_timeout * 1000)) { 1131 | log_d("rx timeout %d", pcb->state); 1132 | _close(); 1133 | return ERR_OK; 1134 | } 1135 | // Everything is fine 1136 | if (_poll_cb) { 1137 | _poll_cb(_poll_cb_arg, this); 1138 | } 1139 | return ERR_OK; 1140 | } 1141 | 1142 | void AsyncClient::_dns_found(struct ip_addr* ipaddr) { 1143 | #if ESP_IDF_VERSION_MAJOR < 5 1144 | if (ipaddr && IP_IS_V4(ipaddr)) { 1145 | connect(IPAddress(ip_addr_get_ip4_u32(ipaddr)), _connect_port); 1146 | #if LWIP_IPV6 1147 | } else if (ipaddr && ipaddr->u_addr.ip6.addr) { 1148 | connect(IPv6Address(ipaddr->u_addr.ip6.addr), _connect_port); 1149 | #endif 1150 | #else 1151 | if (ipaddr) { 1152 | IPAddress ip; 1153 | ip.from_ip_addr_t(ipaddr); 1154 | connect(ip, _connect_port); 1155 | #endif 1156 | } else { 1157 | if (_error_cb) { 1158 | _error_cb(_error_cb_arg, this, -55); 1159 | } 1160 | if (_discard_cb) { 1161 | _discard_cb(_discard_cb_arg, this); 1162 | } 1163 | } 1164 | } 1165 | 1166 | /* 1167 | * Public Helper Methods 1168 | * */ 1169 | 1170 | bool AsyncClient::free() { 1171 | if (!_pcb) { 1172 | return true; 1173 | } 1174 | if (_pcb->state == CLOSED || _pcb->state > ESTABLISHED) { 1175 | return true; 1176 | } 1177 | return false; 1178 | } 1179 | 1180 | size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) { 1181 | size_t will_send = add(data, size, apiflags); 1182 | if (!will_send || !send()) { 1183 | return 0; 1184 | } 1185 | return will_send; 1186 | } 1187 | 1188 | void AsyncClient::setRxTimeout(uint32_t timeout) { 1189 | _rx_timeout = timeout; 1190 | } 1191 | 1192 | uint32_t AsyncClient::getRxTimeout() { 1193 | return _rx_timeout; 1194 | } 1195 | 1196 | uint32_t AsyncClient::getAckTimeout() { 1197 | return _ack_timeout; 1198 | } 1199 | 1200 | void AsyncClient::setAckTimeout(uint32_t timeout) { 1201 | _ack_timeout = timeout; 1202 | } 1203 | 1204 | void AsyncClient::setNoDelay(bool nodelay) { 1205 | if (!_pcb) { 1206 | return; 1207 | } 1208 | if (nodelay) { 1209 | tcp_nagle_disable(_pcb); 1210 | } else { 1211 | tcp_nagle_enable(_pcb); 1212 | } 1213 | } 1214 | 1215 | bool AsyncClient::getNoDelay() { 1216 | if (!_pcb) { 1217 | return false; 1218 | } 1219 | return tcp_nagle_disabled(_pcb); 1220 | } 1221 | 1222 | void AsyncClient::setKeepAlive(uint32_t ms, uint8_t cnt) { 1223 | if (ms != 0) { 1224 | _pcb->so_options |= SOF_KEEPALIVE; // Turn on TCP Keepalive for the given pcb 1225 | // Set the time between keepalive messages in milli-seconds 1226 | _pcb->keep_idle = ms; 1227 | _pcb->keep_intvl = ms; 1228 | _pcb->keep_cnt = cnt; // The number of unanswered probes required to force closure of the socket 1229 | } else { 1230 | _pcb->so_options &= ~SOF_KEEPALIVE; // Turn off TCP Keepalive for the given pcb 1231 | } 1232 | } 1233 | 1234 | uint16_t AsyncClient::getMss() { 1235 | if (!_pcb) { 1236 | return 0; 1237 | } 1238 | return tcp_mss(_pcb); 1239 | } 1240 | 1241 | uint32_t AsyncClient::getRemoteAddress() { 1242 | if (!_pcb) { 1243 | return 0; 1244 | } 1245 | #if LWIP_IPV4 && LWIP_IPV6 1246 | return _pcb->remote_ip.u_addr.ip4.addr; 1247 | #else 1248 | return _pcb->remote_ip.addr; 1249 | #endif 1250 | } 1251 | 1252 | #if LWIP_IPV6 1253 | ip6_addr_t AsyncClient::getRemoteAddress6() { 1254 | if (!_pcb) { 1255 | ip6_addr_t nulladdr; 1256 | ip6_addr_set_zero(&nulladdr); 1257 | return nulladdr; 1258 | } 1259 | return _pcb->remote_ip.u_addr.ip6; 1260 | } 1261 | 1262 | ip6_addr_t AsyncClient::getLocalAddress6() { 1263 | if (!_pcb) { 1264 | ip6_addr_t nulladdr; 1265 | ip6_addr_set_zero(&nulladdr); 1266 | return nulladdr; 1267 | } 1268 | return _pcb->local_ip.u_addr.ip6; 1269 | } 1270 | #if ESP_IDF_VERSION_MAJOR < 5 1271 | IPv6Address AsyncClient::remoteIP6() { 1272 | return IPv6Address(getRemoteAddress6().addr); 1273 | } 1274 | 1275 | IPv6Address AsyncClient::localIP6() { 1276 | return IPv6Address(getLocalAddress6().addr); 1277 | } 1278 | #else 1279 | IPAddress AsyncClient::remoteIP6() { 1280 | if (!_pcb) { 1281 | return IPAddress(IPType::IPv6); 1282 | } 1283 | IPAddress ip; 1284 | ip.from_ip_addr_t(&(_pcb->remote_ip)); 1285 | return ip; 1286 | } 1287 | 1288 | IPAddress AsyncClient::localIP6() { 1289 | if (!_pcb) { 1290 | return IPAddress(IPType::IPv6); 1291 | } 1292 | IPAddress ip; 1293 | ip.from_ip_addr_t(&(_pcb->local_ip)); 1294 | return ip; 1295 | } 1296 | #endif 1297 | #endif 1298 | 1299 | uint16_t AsyncClient::getRemotePort() { 1300 | if (!_pcb) { 1301 | return 0; 1302 | } 1303 | return _pcb->remote_port; 1304 | } 1305 | 1306 | uint32_t AsyncClient::getLocalAddress() { 1307 | if (!_pcb) { 1308 | return 0; 1309 | } 1310 | #if LWIP_IPV4 && LWIP_IPV6 1311 | return _pcb->local_ip.u_addr.ip4.addr; 1312 | #else 1313 | return _pcb->local_ip.addr; 1314 | #endif 1315 | } 1316 | 1317 | uint16_t AsyncClient::getLocalPort() { 1318 | if (!_pcb) { 1319 | return 0; 1320 | } 1321 | return _pcb->local_port; 1322 | } 1323 | 1324 | IPAddress AsyncClient::remoteIP() { 1325 | #if ESP_IDF_VERSION_MAJOR < 5 1326 | return IPAddress(getRemoteAddress()); 1327 | #else 1328 | if (!_pcb) { 1329 | return IPAddress(); 1330 | } 1331 | IPAddress ip; 1332 | ip.from_ip_addr_t(&(_pcb->remote_ip)); 1333 | return ip; 1334 | #endif 1335 | } 1336 | 1337 | uint16_t AsyncClient::remotePort() { 1338 | return getRemotePort(); 1339 | } 1340 | 1341 | IPAddress AsyncClient::localIP() { 1342 | #if ESP_IDF_VERSION_MAJOR < 5 1343 | return IPAddress(getLocalAddress()); 1344 | #else 1345 | if (!_pcb) { 1346 | return IPAddress(); 1347 | } 1348 | IPAddress ip; 1349 | ip.from_ip_addr_t(&(_pcb->local_ip)); 1350 | return ip; 1351 | #endif 1352 | } 1353 | 1354 | uint16_t AsyncClient::localPort() { 1355 | return getLocalPort(); 1356 | } 1357 | 1358 | uint8_t AsyncClient::state() { 1359 | if (!_pcb) { 1360 | return 0; 1361 | } 1362 | return _pcb->state; 1363 | } 1364 | 1365 | bool AsyncClient::connected() { 1366 | if (!_pcb) { 1367 | return false; 1368 | } 1369 | return _pcb->state == ESTABLISHED; 1370 | } 1371 | 1372 | bool AsyncClient::connecting() { 1373 | if (!_pcb) { 1374 | return false; 1375 | } 1376 | return _pcb->state > CLOSED && _pcb->state < ESTABLISHED; 1377 | } 1378 | 1379 | bool AsyncClient::disconnecting() { 1380 | if (!_pcb) { 1381 | return false; 1382 | } 1383 | return _pcb->state > ESTABLISHED && _pcb->state < TIME_WAIT; 1384 | } 1385 | 1386 | bool AsyncClient::disconnected() { 1387 | if (!_pcb) { 1388 | return true; 1389 | } 1390 | return _pcb->state == CLOSED || _pcb->state == TIME_WAIT; 1391 | } 1392 | 1393 | bool AsyncClient::freeable() { 1394 | if (!_pcb) { 1395 | return true; 1396 | } 1397 | return _pcb->state == CLOSED || _pcb->state > ESTABLISHED; 1398 | } 1399 | 1400 | bool AsyncClient::canSend() { 1401 | return space() > 0; 1402 | } 1403 | 1404 | const char* AsyncClient::errorToString(int8_t error) { 1405 | switch (error) { 1406 | case ERR_OK: 1407 | return "OK"; 1408 | case ERR_MEM: 1409 | return "Out of memory error"; 1410 | case ERR_BUF: 1411 | return "Buffer error"; 1412 | case ERR_TIMEOUT: 1413 | return "Timeout"; 1414 | case ERR_RTE: 1415 | return "Routing problem"; 1416 | case ERR_INPROGRESS: 1417 | return "Operation in progress"; 1418 | case ERR_VAL: 1419 | return "Illegal value"; 1420 | case ERR_WOULDBLOCK: 1421 | return "Operation would block"; 1422 | case ERR_USE: 1423 | return "Address in use"; 1424 | case ERR_ALREADY: 1425 | return "Already connected"; 1426 | case ERR_CONN: 1427 | return "Not connected"; 1428 | case ERR_IF: 1429 | return "Low-level netif error"; 1430 | case ERR_ABRT: 1431 | return "Connection aborted"; 1432 | case ERR_RST: 1433 | return "Connection reset"; 1434 | case ERR_CLSD: 1435 | return "Connection closed"; 1436 | case ERR_ARG: 1437 | return "Illegal argument"; 1438 | case -55: 1439 | return "DNS failed"; 1440 | default: 1441 | return "UNKNOWN"; 1442 | } 1443 | } 1444 | 1445 | const char* AsyncClient::stateToString() { 1446 | switch (state()) { 1447 | case 0: 1448 | return "Closed"; 1449 | case 1: 1450 | return "Listen"; 1451 | case 2: 1452 | return "SYN Sent"; 1453 | case 3: 1454 | return "SYN Received"; 1455 | case 4: 1456 | return "Established"; 1457 | case 5: 1458 | return "FIN Wait 1"; 1459 | case 6: 1460 | return "FIN Wait 2"; 1461 | case 7: 1462 | return "Close Wait"; 1463 | case 8: 1464 | return "Closing"; 1465 | case 9: 1466 | return "Last ACK"; 1467 | case 10: 1468 | return "Time Wait"; 1469 | default: 1470 | return "UNKNOWN"; 1471 | } 1472 | } 1473 | 1474 | /* 1475 | * Static Callbacks (LwIP C2C++ interconnect) 1476 | * */ 1477 | 1478 | void AsyncClient::_s_dns_found(const char* name, struct ip_addr* ipaddr, void* arg) { 1479 | reinterpret_cast(arg)->_dns_found(ipaddr); 1480 | } 1481 | 1482 | int8_t AsyncClient::_s_poll(void* arg, struct tcp_pcb* pcb) { 1483 | return reinterpret_cast(arg)->_poll(pcb); 1484 | } 1485 | 1486 | int8_t AsyncClient::_s_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* pb, int8_t err) { 1487 | return reinterpret_cast(arg)->_recv(pcb, pb, err); 1488 | } 1489 | 1490 | int8_t AsyncClient::_s_fin(void* arg, struct tcp_pcb* pcb, int8_t err) { 1491 | return reinterpret_cast(arg)->_fin(pcb, err); 1492 | } 1493 | 1494 | int8_t AsyncClient::_s_lwip_fin(void* arg, struct tcp_pcb* pcb, int8_t err) { 1495 | return reinterpret_cast(arg)->_lwip_fin(pcb, err); 1496 | } 1497 | 1498 | int8_t AsyncClient::_s_sent(void* arg, struct tcp_pcb* pcb, uint16_t len) { 1499 | return reinterpret_cast(arg)->_sent(pcb, len); 1500 | } 1501 | 1502 | void AsyncClient::_s_error(void* arg, int8_t err) { 1503 | reinterpret_cast(arg)->_error(err); 1504 | } 1505 | 1506 | int8_t AsyncClient::_s_connected(void* arg, struct tcp_pcb* pcb, int8_t err) { 1507 | return reinterpret_cast(arg)->_connected(pcb, err); 1508 | } 1509 | 1510 | /* 1511 | Async TCP Server 1512 | */ 1513 | 1514 | AsyncServer::AsyncServer(IPAddress addr, uint16_t port) 1515 | : _port(port) 1516 | #if ESP_IDF_VERSION_MAJOR < 5 1517 | , 1518 | _bind4(true), _bind6(false) 1519 | #else 1520 | , 1521 | _bind4(addr.type() != IPType::IPv6), _bind6(addr.type() == IPType::IPv6) 1522 | #endif 1523 | , 1524 | _addr(addr), _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) { 1525 | } 1526 | 1527 | #if ESP_IDF_VERSION_MAJOR < 5 1528 | AsyncServer::AsyncServer(IPv6Address addr, uint16_t port) 1529 | : _port(port), _bind4(false), _bind6(true), _addr6(addr), _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) {} 1530 | #endif 1531 | 1532 | AsyncServer::AsyncServer(uint16_t port) 1533 | : _port(port), _bind4(true), _bind6(false), _addr((uint32_t)IPADDR_ANY) 1534 | #if ESP_IDF_VERSION_MAJOR < 5 1535 | , 1536 | _addr6() 1537 | #endif 1538 | , 1539 | _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) { 1540 | } 1541 | 1542 | AsyncServer::~AsyncServer() { 1543 | end(); 1544 | } 1545 | 1546 | void AsyncServer::onClient(AcConnectHandler cb, void* arg) { 1547 | _connect_cb = cb; 1548 | _connect_cb_arg = arg; 1549 | } 1550 | 1551 | void AsyncServer::begin() { 1552 | if (_pcb) { 1553 | return; 1554 | } 1555 | 1556 | if (!_start_async_task()) { 1557 | log_e("failed to start task"); 1558 | return; 1559 | } 1560 | int8_t err; 1561 | TCP_MUTEX_LOCK(); 1562 | _pcb = tcp_new_ip_type(_bind4 && _bind6 ? IPADDR_TYPE_ANY : (_bind6 ? IPADDR_TYPE_V6 : IPADDR_TYPE_V4)); 1563 | TCP_MUTEX_UNLOCK(); 1564 | if (!_pcb) { 1565 | log_e("_pcb == NULL"); 1566 | return; 1567 | } 1568 | 1569 | ip_addr_t local_addr; 1570 | #if ESP_IDF_VERSION_MAJOR < 5 1571 | if (_bind6) { // _bind6 && _bind4 both at the same time is not supported on Arduino 2 in this lib API 1572 | local_addr.type = IPADDR_TYPE_V6; 1573 | memcpy(local_addr.u_addr.ip6.addr, static_cast(_addr6), sizeof(uint32_t) * 4); 1574 | } else { 1575 | local_addr.type = IPADDR_TYPE_V4; 1576 | local_addr.u_addr.ip4.addr = _addr; 1577 | } 1578 | #else 1579 | _addr.to_ip_addr_t(&local_addr); 1580 | #endif 1581 | err = _tcp_bind(_pcb, &local_addr, _port); 1582 | 1583 | if (err != ERR_OK) { 1584 | _tcp_close(_pcb, -1); 1585 | log_e("bind error: %d", err); 1586 | return; 1587 | } 1588 | 1589 | static uint8_t backlog = 5; 1590 | _pcb = _tcp_listen_with_backlog(_pcb, backlog); 1591 | if (!_pcb) { 1592 | log_e("listen_pcb == NULL"); 1593 | return; 1594 | } 1595 | TCP_MUTEX_LOCK(); 1596 | tcp_arg(_pcb, (void*)this); 1597 | tcp_accept(_pcb, &_s_accept); 1598 | TCP_MUTEX_UNLOCK(); 1599 | } 1600 | 1601 | void AsyncServer::end() { 1602 | if (_pcb) { 1603 | TCP_MUTEX_LOCK(); 1604 | tcp_arg(_pcb, NULL); 1605 | tcp_accept(_pcb, NULL); 1606 | if (tcp_close(_pcb) != ERR_OK) { 1607 | TCP_MUTEX_UNLOCK(); 1608 | _tcp_abort(_pcb, -1); 1609 | } else { 1610 | TCP_MUTEX_UNLOCK(); 1611 | } 1612 | _pcb = NULL; 1613 | } 1614 | } 1615 | 1616 | // runs on LwIP thread 1617 | int8_t AsyncServer::_accept(tcp_pcb* pcb, int8_t err) { 1618 | // ets_printf("+A: 0x%08x\n", pcb); 1619 | if (_connect_cb) { 1620 | AsyncClient* c = new AsyncClient(pcb); 1621 | if (c) { 1622 | c->setNoDelay(_noDelay); 1623 | return _tcp_accept(this, c); 1624 | } 1625 | } 1626 | if (tcp_close(pcb) != ERR_OK) { 1627 | tcp_abort(pcb); 1628 | } 1629 | log_d("FAIL"); 1630 | return ERR_OK; 1631 | } 1632 | 1633 | int8_t AsyncServer::_accepted(AsyncClient* client) { 1634 | if (_connect_cb) { 1635 | _connect_cb(_connect_cb_arg, client); 1636 | } 1637 | return ERR_OK; 1638 | } 1639 | 1640 | void AsyncServer::setNoDelay(bool nodelay) { 1641 | _noDelay = nodelay; 1642 | } 1643 | 1644 | bool AsyncServer::getNoDelay() { 1645 | return _noDelay; 1646 | } 1647 | 1648 | uint8_t AsyncServer::status() { 1649 | if (!_pcb) { 1650 | return 0; 1651 | } 1652 | return _pcb->state; 1653 | } 1654 | 1655 | int8_t AsyncServer::_s_accept(void* arg, tcp_pcb* pcb, int8_t err) { 1656 | return reinterpret_cast(arg)->_accept(pcb, err); 1657 | } 1658 | 1659 | int8_t AsyncServer::_s_accepted(void* arg, AsyncClient* client) { 1660 | return reinterpret_cast(arg)->_accepted(client); 1661 | } 1662 | -------------------------------------------------------------------------------- /src/AsyncTCP.h: -------------------------------------------------------------------------------- 1 | /* 2 | Asynchronous TCP library for Espressif MCUs 3 | 4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved. 5 | This file is part of the esp8266 core for Arduino environment. 6 | 7 | This library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | */ 21 | 22 | #ifndef ASYNCTCP_H_ 23 | #define ASYNCTCP_H_ 24 | 25 | #define ASYNCTCP_VERSION "3.3.2" 26 | #define ASYNCTCP_VERSION_MAJOR 3 27 | #define ASYNCTCP_VERSION_MINOR 3 28 | #define ASYNCTCP_VERSION_REVISION 2 29 | #define ASYNCTCP_FORK_ESP32Async 30 | 31 | #include "IPAddress.h" 32 | #if ESP_IDF_VERSION_MAJOR < 5 33 | #include "IPv6Address.h" 34 | #endif 35 | #include "lwip/ip6_addr.h" 36 | #include "lwip/ip_addr.h" 37 | #include 38 | 39 | #ifndef LIBRETINY 40 | #include "sdkconfig.h" 41 | extern "C" { 42 | #include "freertos/semphr.h" 43 | #include "lwip/pbuf.h" 44 | } 45 | #else 46 | extern "C" { 47 | #include 48 | #include 49 | } 50 | #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core 51 | #endif 52 | 53 | // If core is not defined, then we are running in Arduino or PIO 54 | #ifndef CONFIG_ASYNC_TCP_RUNNING_CORE 55 | #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core 56 | #endif 57 | 58 | // guard AsyncTCP task with watchdog 59 | #ifndef CONFIG_ASYNC_TCP_USE_WDT 60 | #define CONFIG_ASYNC_TCP_USE_WDT 1 61 | #endif 62 | 63 | #ifndef CONFIG_ASYNC_TCP_STACK_SIZE 64 | #define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2 65 | #endif 66 | 67 | #ifndef CONFIG_ASYNC_TCP_PRIORITY 68 | #define CONFIG_ASYNC_TCP_PRIORITY 10 69 | #endif 70 | 71 | #ifndef CONFIG_ASYNC_TCP_QUEUE_SIZE 72 | #define CONFIG_ASYNC_TCP_QUEUE_SIZE 64 73 | #endif 74 | 75 | #ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME 76 | #define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000 77 | #endif 78 | 79 | class AsyncClient; 80 | 81 | #define ASYNC_WRITE_FLAG_COPY 0x01 // will allocate new buffer to hold the data while sending (else will hold reference to the data given) 82 | #define ASYNC_WRITE_FLAG_MORE 0x02 // will not send PSH flag, meaning that there should be more data to be sent before the application should react. 83 | 84 | typedef std::function AcConnectHandler; 85 | typedef std::function AcAckHandler; 86 | typedef std::function AcErrorHandler; 87 | typedef std::function AcDataHandler; 88 | typedef std::function AcPacketHandler; 89 | typedef std::function AcTimeoutHandler; 90 | 91 | struct tcp_pcb; 92 | struct ip_addr; 93 | 94 | class AsyncClient { 95 | public: 96 | AsyncClient(tcp_pcb* pcb = 0); 97 | ~AsyncClient(); 98 | 99 | AsyncClient& operator=(const AsyncClient& other); 100 | AsyncClient& operator+=(const AsyncClient& other); 101 | 102 | bool operator==(const AsyncClient& other); 103 | 104 | bool operator!=(const AsyncClient& other) { 105 | return !(*this == other); 106 | } 107 | bool connect(const IPAddress& ip, uint16_t port); 108 | #if ESP_IDF_VERSION_MAJOR < 5 109 | bool connect(const IPv6Address& ip, uint16_t port); 110 | #endif 111 | bool connect(const char* host, uint16_t port); 112 | /** 113 | * @brief close connection 114 | * 115 | * @param now - ignored 116 | */ 117 | void close(bool now = false); 118 | // same as close() 119 | void stop() { close(false); }; 120 | int8_t abort(); 121 | bool free(); 122 | 123 | // ack is not pending 124 | bool canSend(); 125 | // TCP buffer space available 126 | size_t space(); 127 | 128 | /** 129 | * @brief add data to be send (but do not send yet) 130 | * @note add() would call lwip's tcp_write() 131 | By default apiflags=ASYNC_WRITE_FLAG_COPY 132 | You could try to use apiflags with this flag unset to pass data by reference and avoid copy to socket buffer, 133 | but looks like it does not work for Arduino's lwip in ESP32/IDF at least 134 | it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30 135 | if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF 136 | https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744 137 | * 138 | * @param data 139 | * @param size 140 | * @param apiflags 141 | * @return size_t amount of data that has been copied 142 | */ 143 | size_t add(const char* data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); 144 | 145 | /** 146 | * @brief send data previously add()'ed 147 | * 148 | * @return true on success 149 | * @return false on error 150 | */ 151 | bool send(); 152 | 153 | /** 154 | * @brief add and enqueue data for sending 155 | * @note it is same as add() + send() 156 | * @note only make sense when canSend() == true 157 | * 158 | * @param data 159 | * @param size 160 | * @param apiflags 161 | * @return size_t 162 | */ 163 | size_t write(const char* data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); 164 | 165 | /** 166 | * @brief add and enque data for sending 167 | * @note treats data as null-terminated string 168 | * 169 | * @param data 170 | * @return size_t 171 | */ 172 | size_t write(const char* data) { return data == NULL ? 0 : write(data, strlen(data)); }; 173 | 174 | uint8_t state(); 175 | bool connecting(); 176 | bool connected(); 177 | bool disconnecting(); 178 | bool disconnected(); 179 | 180 | // disconnected or disconnecting 181 | bool freeable(); 182 | 183 | uint16_t getMss(); 184 | 185 | uint32_t getRxTimeout(); 186 | // no RX data timeout for the connection in seconds 187 | void setRxTimeout(uint32_t timeout); 188 | 189 | uint32_t getAckTimeout(); 190 | // no ACK timeout for the last sent packet in milliseconds 191 | void setAckTimeout(uint32_t timeout); 192 | 193 | void setNoDelay(bool nodelay); 194 | bool getNoDelay(); 195 | 196 | void setKeepAlive(uint32_t ms, uint8_t cnt); 197 | 198 | uint32_t getRemoteAddress(); 199 | uint16_t getRemotePort(); 200 | uint32_t getLocalAddress(); 201 | uint16_t getLocalPort(); 202 | #if LWIP_IPV6 203 | ip6_addr_t getRemoteAddress6(); 204 | ip6_addr_t getLocalAddress6(); 205 | #if ESP_IDF_VERSION_MAJOR < 5 206 | IPv6Address remoteIP6(); 207 | IPv6Address localIP6(); 208 | #else 209 | IPAddress remoteIP6(); 210 | IPAddress localIP6(); 211 | #endif 212 | #endif 213 | 214 | // compatibility 215 | IPAddress remoteIP(); 216 | uint16_t remotePort(); 217 | IPAddress localIP(); 218 | uint16_t localPort(); 219 | 220 | // set callback - on successful connect 221 | void onConnect(AcConnectHandler cb, void* arg = 0); 222 | // set callback - disconnected 223 | void onDisconnect(AcConnectHandler cb, void* arg = 0); 224 | // set callback - ack received 225 | void onAck(AcAckHandler cb, void* arg = 0); 226 | // set callback - unsuccessful connect or error 227 | void onError(AcErrorHandler cb, void* arg = 0); 228 | // set callback - data received (called if onPacket is not used) 229 | void onData(AcDataHandler cb, void* arg = 0); 230 | // set callback - data received 231 | void onPacket(AcPacketHandler cb, void* arg = 0); 232 | // set callback - ack timeout 233 | void onTimeout(AcTimeoutHandler cb, void* arg = 0); 234 | // set callback - every 125ms when connected 235 | void onPoll(AcConnectHandler cb, void* arg = 0); 236 | 237 | // ack pbuf from onPacket 238 | void ackPacket(struct pbuf* pb); 239 | // ack data that you have not acked using the method below 240 | size_t ack(size_t len); 241 | // will not ack the current packet. Call from onData 242 | void ackLater() { _ack_pcb = false; } 243 | 244 | static const char* errorToString(int8_t error); 245 | const char* stateToString(); 246 | 247 | // internal callbacks - Do NOT call any of the functions below in user code! 248 | static int8_t _s_poll(void* arg, struct tcp_pcb* tpcb); 249 | static int8_t _s_recv(void* arg, struct tcp_pcb* tpcb, struct pbuf* pb, int8_t err); 250 | static int8_t _s_fin(void* arg, struct tcp_pcb* tpcb, int8_t err); 251 | static int8_t _s_lwip_fin(void* arg, struct tcp_pcb* tpcb, int8_t err); 252 | static void _s_error(void* arg, int8_t err); 253 | static int8_t _s_sent(void* arg, struct tcp_pcb* tpcb, uint16_t len); 254 | static int8_t _s_connected(void* arg, struct tcp_pcb* tpcb, int8_t err); 255 | static void _s_dns_found(const char* name, struct ip_addr* ipaddr, void* arg); 256 | 257 | int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err); 258 | tcp_pcb* pcb() { return _pcb; } 259 | 260 | protected: 261 | bool _connect(ip_addr_t addr, uint16_t port); 262 | 263 | tcp_pcb* _pcb; 264 | int8_t _closed_slot; 265 | 266 | AcConnectHandler _connect_cb; 267 | void* _connect_cb_arg; 268 | AcConnectHandler _discard_cb; 269 | void* _discard_cb_arg; 270 | AcAckHandler _sent_cb; 271 | void* _sent_cb_arg; 272 | AcErrorHandler _error_cb; 273 | void* _error_cb_arg; 274 | AcDataHandler _recv_cb; 275 | void* _recv_cb_arg; 276 | AcPacketHandler _pb_cb; 277 | void* _pb_cb_arg; 278 | AcTimeoutHandler _timeout_cb; 279 | void* _timeout_cb_arg; 280 | AcConnectHandler _poll_cb; 281 | void* _poll_cb_arg; 282 | 283 | bool _ack_pcb; 284 | uint32_t _tx_last_packet; 285 | uint32_t _rx_ack_len; 286 | uint32_t _rx_last_packet; 287 | uint32_t _rx_timeout; 288 | uint32_t _rx_last_ack; 289 | uint32_t _ack_timeout; 290 | uint16_t _connect_port; 291 | 292 | int8_t _close(); 293 | void _free_closed_slot(); 294 | bool _allocate_closed_slot(); 295 | int8_t _connected(tcp_pcb* pcb, int8_t err); 296 | void _error(int8_t err); 297 | int8_t _poll(tcp_pcb* pcb); 298 | int8_t _sent(tcp_pcb* pcb, uint16_t len); 299 | int8_t _fin(tcp_pcb* pcb, int8_t err); 300 | int8_t _lwip_fin(tcp_pcb* pcb, int8_t err); 301 | void _dns_found(struct ip_addr* ipaddr); 302 | 303 | public: 304 | AsyncClient* prev; 305 | AsyncClient* next; 306 | }; 307 | 308 | class AsyncServer { 309 | public: 310 | AsyncServer(IPAddress addr, uint16_t port); 311 | #if ESP_IDF_VERSION_MAJOR < 5 312 | AsyncServer(IPv6Address addr, uint16_t port); 313 | #endif 314 | AsyncServer(uint16_t port); 315 | ~AsyncServer(); 316 | void onClient(AcConnectHandler cb, void* arg); 317 | void begin(); 318 | void end(); 319 | void setNoDelay(bool nodelay); 320 | bool getNoDelay(); 321 | uint8_t status(); 322 | 323 | // Do not use any of the functions below! 324 | static int8_t _s_accept(void* arg, tcp_pcb* newpcb, int8_t err); 325 | static int8_t _s_accepted(void* arg, AsyncClient* client); 326 | 327 | protected: 328 | uint16_t _port; 329 | bool _bind4 = false; 330 | bool _bind6 = false; 331 | IPAddress _addr; 332 | #if ESP_IDF_VERSION_MAJOR < 5 333 | IPv6Address _addr6; 334 | #endif 335 | bool _noDelay; 336 | tcp_pcb* _pcb; 337 | AcConnectHandler _connect_cb; 338 | void* _connect_cb_arg; 339 | 340 | int8_t _accept(tcp_pcb* newpcb, int8_t err); 341 | int8_t _accepted(AsyncClient* client); 342 | }; 343 | 344 | #endif /* ASYNCTCP_H_ */ 345 | --------------------------------------------------------------------------------