forked from espressif/esp-protocols
Compare commits
98 Commits
modem-v0.1
...
websocket-
Author | SHA1 | Date | |
---|---|---|---|
80c3cf0f02 | |||
b3c777ad43 | |||
8ce791e969 | |||
fc7ed90d74 | |||
59e82695e7 | |||
755f16222f | |||
bece6e7045 | |||
46bd32d952 | |||
525c70c0b2 | |||
c4c323666e | |||
19c0455b4d | |||
665c520faf | |||
9118e0f044 | |||
fbdbd550c0 | |||
028be5a8d7 | |||
de7cd72f70 | |||
4a608ec1cd | |||
f0351ff378 | |||
9219ff710a | |||
86aa0b8d39 | |||
1933367f63 | |||
cf697a1a1b | |||
95cf983502 | |||
8a6c320a29 | |||
d1dd6ece38 | |||
36167db336 | |||
d376480766 | |||
e90272c812 | |||
fda070ba39 | |||
6d12d06605 | |||
1455bc0305 | |||
01b4f640d9 | |||
6ab0aea841 | |||
5f2a50f09f | |||
b71c49c277 | |||
f8e3ba7813 | |||
7a5b2d5a7d | |||
42920d7fb5 | |||
2b044f2434 | |||
2b6022c85d | |||
fae2343b19 | |||
17281a515e | |||
aec6a75d40 | |||
a6be8e2e3d | |||
09453e4694 | |||
3b0488cfdc | |||
f21a2f32e0 | |||
a48b0fafe8 | |||
1fcc001ae8 | |||
a41e3383b3 | |||
d0121b964d | |||
f55d8391c9 | |||
fe26b734b5 | |||
f5a26c4d32 | |||
23f6a1d46e | |||
2553d65e64 | |||
67949f94f4 | |||
bfc88ab76c | |||
343fbfdcc9 | |||
4d644954fe | |||
da74a4a489 | |||
f718676083 | |||
13a40d2344 | |||
35d6f9a2c6 | |||
f3a0586663 | |||
325a1933c4 | |||
face03e4e5 | |||
2cb74cf8d0 | |||
d879e82a42 | |||
4f1d31f9b7 | |||
bece4efa09 | |||
8417e232aa | |||
5f0832a0ad | |||
464baeeb83 | |||
5d9ad9cffd | |||
9fbd6e658a | |||
8465b14653 | |||
e7ae0301ae | |||
96498760bf | |||
cf990d1a87 | |||
5299b425e8 | |||
bcb1ab99bd | |||
2c21aa1113 | |||
b9ff1e4e12 | |||
23dbdb584e | |||
1d0cc49c3f | |||
c1249aec17 | |||
2bbcb95e53 | |||
58887170d2 | |||
cc7aa03a37 | |||
15a858b735 | |||
01e2a9c109 | |||
c0021ceeb2 | |||
fec83e5915 | |||
c8c24ed2fc | |||
b380ded5fa | |||
38f6eb963a | |||
2a2d27086f |
26
.github/workflows/build-websockets.yml
vendored
Normal file
26
.github/workflows/build-websockets.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Build Websockets
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_target: ["esp32"]
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
path: esp-protocols
|
||||
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
|
||||
env:
|
||||
IDF_TARGET: ${{ matrix.idf_target }}
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
cd $GITHUB_WORKSPACE/esp-protocols/components/esp_websocket_client/examples/
|
||||
idf.py build
|
36
.github/workflows/build.yml
vendored
Normal file
36
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
name: Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v4.1", "release-v4.2", "release-v4.3", "release-v4.4"]
|
||||
example: ["pppos_client", "modem_console", "ap_to_pppos", "simple_cmux_client"]
|
||||
idf_target: ["esp32"]
|
||||
exclude:
|
||||
- idf_ver: "release-v4.1"
|
||||
example: modem_console
|
||||
- idf_ver: "release-v4.1"
|
||||
example: ap_to_pppos
|
||||
- idf_ver: "release-v4.1"
|
||||
example: simple_cmux_client
|
||||
- idf_ver: "release-v4.2"
|
||||
example: simple_cmux_client
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
path: esp-protocols
|
||||
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
|
||||
env:
|
||||
IDF_TARGET: ${{ matrix.idf_target }}
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
cd $GITHUB_WORKSPACE/esp-protocols/components/esp_modem/examples/${{ matrix.example }}
|
||||
idf.py build
|
44
.github/workflows/host-test.yml
vendored
Normal file
44
.github/workflows/host-test.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: Host test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
host_test:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-20.04
|
||||
container: espressif/idf:release-v4.3
|
||||
env:
|
||||
lwip: lwip-2.1.2
|
||||
lwip_contrib: contrib-2.1.0
|
||||
lwip_uri: http://download.savannah.nongnu.org/releases/lwip
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
path: esp-protocols
|
||||
|
||||
- name: Build and Test
|
||||
shell: bash
|
||||
run: |
|
||||
apt-get update && apt-get install -y gcc-8 g++-8
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8
|
||||
export LWIP_PATH=`pwd`/${{ env.lwip }}
|
||||
export LWIP_CONTRIB_PATH=`pwd`/${{ env.lwip_contrib }}
|
||||
wget --no-verbose ${lwip_uri}/${lwip}.zip
|
||||
unzip -oq ${lwip}.zip
|
||||
wget --no-verbose ${lwip_uri}/${lwip_contrib}.zip
|
||||
unzip -oq ${lwip_contrib}.zip
|
||||
. ${IDF_PATH}/export.sh
|
||||
cd $GITHUB_WORKSPACE/esp-protocols/components/esp_modem/examples/linux_modem
|
||||
idf.py build
|
||||
cd $GITHUB_WORKSPACE/esp-protocols/components/esp_modem/test/host_test
|
||||
idf.py build
|
||||
./build/host_modem_test.elf -r junit -o junit.xml
|
||||
|
||||
- name: Publish Results
|
||||
uses: EnricoMi/publish-unit-test-result-action@v1
|
||||
if: always()
|
||||
with:
|
||||
files: esp-protocols/components/esp_modem/test/host_test/junit.xml
|
39
.github/workflows/publish-docs-component-websockets.yml
vendored
Normal file
39
.github/workflows/publish-docs-component-websockets.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: Docs and Publish Websockets
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
docs_build:
|
||||
name: Docs-Build-And-Upload
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate docs
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install doxygen clang python3-pip git
|
||||
sudo git clone https://github.com/espressif/esp-idf
|
||||
python -m pip install breathe recommonmark
|
||||
python -m pip install -r esp-idf/docs/requirements.txt
|
||||
cd $GITHUB_WORKSPACE/components/esp_websocket_client/docs
|
||||
./generate_docs
|
||||
mkdir -p $GITHUB_WORKSPACE/docs/esp_websocket_client
|
||||
cp -r html/. $GITHUB_WORKSPACE/docs/esp_websocket_client
|
||||
cd $GITHUB_WORKSPACE/docs
|
||||
touch .nojekyll
|
||||
echo '<a href="esp_websocket_client/index.html">esp-websocket-client</a>' >> index.html
|
||||
|
||||
- name: Deploy generated docs.
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.5
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: docs
|
45
.github/workflows/publish-docs-component.yml
vendored
Normal file
45
.github/workflows/publish-docs-component.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Docs and Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
docs_build:
|
||||
name: Docs-Build-And-Upload
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate docs
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install doxygen clang python3-pip
|
||||
python -m pip install breathe recommonmark
|
||||
cd $GITHUB_WORKSPACE/components/esp_modem/docs
|
||||
./generate_docs
|
||||
mkdir -p $GITHUB_WORKSPACE/docs/esp_modem
|
||||
cp -r html/. $GITHUB_WORKSPACE/docs/esp_modem
|
||||
cd $GITHUB_WORKSPACE/docs
|
||||
touch .nojekyll
|
||||
echo '<a href="esp_modem/index.html">esp-modem</a>' > index.html
|
||||
|
||||
- name: Upload components to component service
|
||||
uses: espressif/github-actions/upload_components@master
|
||||
with:
|
||||
directories: "components/esp_modem"
|
||||
name: "esp_modem"
|
||||
namespace: "espressif"
|
||||
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}
|
||||
|
||||
- name: Deploy generated docs
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.5
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: docs
|
21
.github/workflows/sync_issues.yml
vendored
Normal file
21
.github/workflows/sync_issues.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Sync issue comments to JIRA
|
||||
|
||||
# This workflow will be triggered when new issue is created
|
||||
# or a new issue/PR comment is created
|
||||
on: [issues, issue_comment]
|
||||
|
||||
jobs:
|
||||
sync_issue_comments_to_jira:
|
||||
name: Sync Issue Comments to Jira
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Sync issue comments to JIRA
|
||||
uses: espressif/github-actions/sync_issues_to_jira@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
JIRA_PASS: ${{ secrets.JIRA_PASS }}
|
||||
JIRA_PROJECT: IDFGH
|
||||
JIRA_COMPONENT: esp-protocols
|
||||
JIRA_URL: ${{ secrets.JIRA_URL }}
|
||||
JIRA_USER: ${{ secrets.JIRA_USER }}
|
11
README.md
11
README.md
@ -1 +1,10 @@
|
||||
# Collection of components for ESP-IDF
|
||||
# Collection of protocol components for ESP-IDF
|
||||
|
||||
[Documentation of esp-protocol](https://espressif.github.io/esp-protocols)
|
||||
|
||||
## Components
|
||||
|
||||
### esp_modem
|
||||
|
||||
* Brief introduction [README](components/esp_modem/README.md)
|
||||
* Full html [documentation](https://espressif.github.io/esp-protocols/esp_modem/index.html)
|
||||
|
@ -7,6 +7,7 @@ if(${target} STREQUAL "linux")
|
||||
set(dependencies esp_system_protocols_linux)
|
||||
else()
|
||||
set(platform_srcs src/esp_modem_primitives_freertos.cpp
|
||||
src/esp_modem_api_target.cpp
|
||||
src/esp_modem_uart.cpp
|
||||
src/esp_modem_term_uart.cpp
|
||||
src/esp_modem_netif.cpp)
|
||||
@ -33,7 +34,16 @@ idf_component_register(SRCS "${srcs}"
|
||||
PRIV_INCLUDE_DIRS private_include
|
||||
REQUIRES ${dependencies})
|
||||
|
||||
target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17)
|
||||
|
||||
# If CMake doesn't know C++17 features, set it manually via comile options
|
||||
# esp-modem component requires C++17 internally, but older CMake (< 3.8, but still supported in IDF)
|
||||
# doesn't support target_compile_features() for cxx_std_17.
|
||||
get_property(cxx_known_features GLOBAL PROPERTY CMAKE_CXX_KNOWN_FEATURES)
|
||||
if ("cxx_std_17" IN_LIST cxx_known_features)
|
||||
target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17)
|
||||
else()
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-std=gnu++17")
|
||||
endif()
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
# This is needed for ESP_LOGx() macros, as integer formats differ on ESP32(..) and x64
|
||||
|
@ -16,4 +16,4 @@ Get started with one of the examples:
|
||||
## Documentation
|
||||
|
||||
* Continue with esp-modem [brief overview](docs/README.md)
|
||||
* View the full [html documentation ](docs/html/index.html)
|
||||
* View the full [html documentation](https://espressif.github.io/esp-protocols/esp_modem/index.html)
|
||||
|
@ -41,6 +41,9 @@ Modem commands
|
||||
|
||||
These functions are the actual commands to communicate with the modem using AT command interface.
|
||||
|
||||
Note that the functions which implement AT commands returning textual values use plain ``char *``
|
||||
pointer as the return value. The API expects the output data to point to user allocated space of at least
|
||||
``ESP_MODEM_C_API_STR_MAX`` (64 by default) bytes, it also truncates the output data to this size.
|
||||
|
||||
.. doxygenfile:: esp_modem_api_commands.h
|
||||
|
||||
|
@ -16,7 +16,8 @@ copyright = u'2016 - 2021, Espressif Systems (Shanghai) Co., Ltd'
|
||||
# for a list of supported languages.
|
||||
language = 'en'
|
||||
|
||||
extensions = ['breathe']
|
||||
extensions = ['breathe', 'recommonmark']
|
||||
|
||||
|
||||
breathe_projects = {'esp_modem': 'xml'}
|
||||
|
||||
|
@ -25,6 +25,7 @@ esp_err_t modem_init_network(esp_netif_t *netif)
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_NEED_SIM_PIN
|
||||
// configure the PIN
|
||||
bool pin_ok = false;
|
||||
if (esp_modem_read_pin(dce, &pin_ok) == ESP_OK && pin_ok == false) {
|
||||
@ -34,6 +35,7 @@ esp_err_t modem_init_network(esp_netif_t *netif)
|
||||
abort();
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_EXAMPLE_NEED_SIM_PIN
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt)
|
||||
case HTTP_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -83,9 +84,9 @@ static int do_http_client(int argc, char **argv)
|
||||
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
uint64_t content_length = esp_http_client_get_content_length(client);
|
||||
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %lld",
|
||||
esp_http_client_get_status_code(client), content_length);
|
||||
return 0;
|
||||
}
|
||||
ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
|
||||
@ -105,4 +106,4 @@ void modem_console_register_http(void)
|
||||
.argtable = &http_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&http_cmd));
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096
|
||||
CONFIG_LWIP_PPP_ENABLE_IPV6=n
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_PARTITION_TABLE_TWO_OTA=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF=y
|
||||
CONFIG_NEWLIB_STDIN_LINE_ENDING_LF=y
|
||||
CONFIG_MAIN_TASK_STACK_SIZE=8192
|
@ -1,4 +1,4 @@
|
||||
version: "0.1.8"
|
||||
version: "0.1.13"
|
||||
description: esp modem
|
||||
dependencies:
|
||||
# Required IDF version
|
||||
|
@ -79,6 +79,7 @@ public:
|
||||
}
|
||||
return true;
|
||||
} else if (mode == modem_mode::COMMAND_MODE) {
|
||||
Task::Delay(1000); // Mandatory 1s pause
|
||||
return set_command_mode() == command_result::OK;
|
||||
} else if (mode == modem_mode::CMUX_MODE) {
|
||||
return set_cmux() == command_result::OK;
|
||||
@ -118,7 +119,6 @@ protected:
|
||||
class SIM7600: public GenericModule {
|
||||
using GenericModule::GenericModule;
|
||||
public:
|
||||
command_result get_module_name(std::string &name) override;
|
||||
command_result get_battery_status(int &voltage, int &bcs, int &bcl) override;
|
||||
command_result power_down() override;
|
||||
};
|
||||
@ -129,7 +129,6 @@ public:
|
||||
class SIM800: public GenericModule {
|
||||
using GenericModule::GenericModule;
|
||||
public:
|
||||
command_result get_module_name(std::string &name) override;
|
||||
command_result power_down() override;
|
||||
command_result set_data_mode() override;
|
||||
};
|
||||
@ -139,8 +138,6 @@ public:
|
||||
*/
|
||||
class BG96: public GenericModule {
|
||||
using GenericModule::GenericModule;
|
||||
public:
|
||||
command_result get_module_name(std::string &name) override;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -22,9 +22,9 @@
|
||||
#include <thread>
|
||||
|
||||
#else
|
||||
#include "freertos/event_groups.h"
|
||||
// forward declarations of FreeRTOS primitives
|
||||
struct QueueDefinition;
|
||||
typedef void *EventGroupHandle_t;
|
||||
#endif
|
||||
|
||||
|
||||
@ -43,8 +43,8 @@ struct Lock {
|
||||
private:
|
||||
MutexT m{};
|
||||
};
|
||||
using TaskT = void*;
|
||||
using SignalT = void*;
|
||||
using TaskT = TaskHandle_t;
|
||||
using SignalT = EventGroupHandle_t;
|
||||
#else
|
||||
using Lock = std::mutex;
|
||||
struct SignalGroupInternal;
|
||||
@ -76,6 +76,7 @@ public:
|
||||
|
||||
static void Delete();
|
||||
static void Relinquish();
|
||||
static void Delay(uint32_t delay);
|
||||
private:
|
||||
TaskT task_handle;
|
||||
};
|
||||
|
@ -67,6 +67,12 @@ struct PdpContext {
|
||||
*/
|
||||
class CommandableIf {
|
||||
public:
|
||||
CommandableIf() = default;
|
||||
CommandableIf(const CommandableIf&) = delete;
|
||||
CommandableIf& operator=(const CommandableIf&) = delete;
|
||||
CommandableIf(CommandableIf&&) = delete;
|
||||
CommandableIf& operator=(CommandableIf&&) = delete;
|
||||
virtual ~CommandableIf() = default;
|
||||
/**
|
||||
* @brief Sends custom AT command
|
||||
* @param command Command to be sent
|
||||
@ -83,6 +89,12 @@ public:
|
||||
*/
|
||||
class ModuleIf {
|
||||
public:
|
||||
ModuleIf() = default;
|
||||
ModuleIf(const ModuleIf&) = delete;
|
||||
ModuleIf& operator=(const ModuleIf&) = delete;
|
||||
ModuleIf(ModuleIf&&) = delete;
|
||||
ModuleIf& operator=(ModuleIf&&) = delete;
|
||||
virtual ~ModuleIf() = default;
|
||||
/**
|
||||
* @brief Sets the data mode up (provides the necessary configuration to connect to the cellular network)
|
||||
* @return true on success
|
||||
|
@ -3,18 +3,20 @@ set(LWIP_CONTRIB_DIR "$ENV{LWIP_CONTRIB_PATH}")
|
||||
|
||||
set(lwipcontribportunix_SRCS ${LWIP_CONTRIB_DIR}/ports/unix/port/sys_arch.c)
|
||||
|
||||
include(${LWIP_DIR}/src/Filelists.cmake)
|
||||
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
|
||||
|
||||
set (LWIP_INCLUDE_DIRS
|
||||
include(${LWIP_DIR}/src/Filelists.cmake)
|
||||
|
||||
set (LWIP_INCLUDE_DIRS
|
||||
"${LWIP_DIR}/src/include"
|
||||
"${LWIP_CONTRIB_DIR}/ports/unix/port/include")
|
||||
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/netif/slipif.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/core/ip4.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/core/ipv6/ip6.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/netif/slipif.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/core/ipv4/ip4.c")
|
||||
list(REMOVE_ITEM lwipnoapps_SRCS "${LWIP_DIR}/src/core/ipv6/ip6.c")
|
||||
endif()
|
||||
|
||||
|
||||
idf_component_register(SRCS esp_netif_linux.cpp tun_io.c ip6_stub.c ${lwipnoapps_SRCS} ${lwipcontribportunix_SRCS}
|
||||
idf_component_register(SRCS esp_netif_linux.cpp tun_io.c ip4_stub.c ip6_stub.c ${lwipnoapps_SRCS} ${lwipcontribportunix_SRCS}
|
||||
INCLUDE_DIRS include ${LWIP_INCLUDE_DIRS}
|
||||
PRIV_INCLUDE_DIRS .
|
||||
REQUIRES esp_system_protocols_linux)
|
||||
|
@ -90,43 +90,6 @@ public:
|
||||
extern "C" esp_netif_t *esp_netif_new(const esp_netif_config_t *config)
|
||||
{
|
||||
return new NetifStorage(config);
|
||||
// struct ifreq ifr = { };
|
||||
// esp_netif_t * netif = netif_storage;
|
||||
// if (netif == NULL) {
|
||||
// return NULL;
|
||||
// }
|
||||
// if ((netif->fd = open(config->dev_name, O_RDWR)) == -1) {
|
||||
// ESP_LOGE(TAG, "Cannot open %s", config->dev_name);
|
||||
// goto cleanup;
|
||||
// }
|
||||
// ifr.ifr_flags = IFF_TUN;
|
||||
// strncpy(ifr.ifr_name, config->if_name, IFNAMSIZ);
|
||||
//
|
||||
// if (ioctl(netif->fd, TUNSETIFF, (void *)&ifr) == -1) {
|
||||
// ESP_LOGE(TAG, "Cannot set ioctl TUNSETIFF %m");
|
||||
// goto cleanup;
|
||||
// }
|
||||
// ioctl(netif->fd, TUNSETNOCSUM, 1);
|
||||
//
|
||||
// netif->in_buf = new uint8_t[BUF_SIZE];
|
||||
// netif->out_buf = new uint8_t[BUF_SIZE];
|
||||
// if (netif->in_buf == nullptr || netif->out_buf == nullptr) {
|
||||
// goto cleanup;
|
||||
// }
|
||||
//
|
||||
// if (!ppp_netif_init(netif)) {
|
||||
// ESP_LOGE(TAG, "Cannot initialize pppos lwip netif %m");
|
||||
// goto cleanup;
|
||||
// }
|
||||
//
|
||||
// return netif;
|
||||
//
|
||||
//cleanup:
|
||||
// close(netif->fd);
|
||||
// delete[] netif->in_buf;
|
||||
// delete[] netif->out_buf;
|
||||
// delete netif_storage;
|
||||
// return nullptr;
|
||||
}
|
||||
|
||||
void esp_netif_destroy(esp_netif_t *netif)
|
||||
|
17
components/esp_modem/port/linux/esp_netif_linux/ip4_stub.c
Normal file
17
components/esp_modem/port/linux/esp_netif_linux/ip4_stub.c
Normal file
@ -0,0 +1,17 @@
|
||||
#include "lwip/ip4.h"
|
||||
|
||||
err_t
|
||||
ip4_output_if(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
|
||||
u8_t ttl, u8_t tos,
|
||||
u8_t proto, struct netif *netif)
|
||||
{ return ERR_OK; }
|
||||
|
||||
struct netif *
|
||||
ip4_route(const ip4_addr_t *dest)
|
||||
{ return NULL; }
|
||||
|
||||
err_t
|
||||
ip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
|
||||
u8_t ttl, u8_t tos,
|
||||
u8_t proto, struct netif *netif)
|
||||
{ return ERR_OK; }
|
@ -1,5 +1,8 @@
|
||||
apk add clang g++ doxygen
|
||||
pip install breathe==4.29.0
|
||||
cd components/esp_modem/docs/
|
||||
#!/bin/bash
|
||||
|
||||
apt-get update
|
||||
apt-get -y install doxygen clang python3-pip
|
||||
python -m pip install breathe recommonmark
|
||||
pushd components/esp_modem/docs
|
||||
./generate_docs
|
||||
cd ../../..
|
||||
popd
|
||||
|
@ -30,13 +30,6 @@ struct PdpContext;
|
||||
static const char *TAG = "modem_api";
|
||||
#endif
|
||||
|
||||
std::shared_ptr<DTE> create_uart_dte(const dte_config *config)
|
||||
{
|
||||
TRY_CATCH_RET_NULL(
|
||||
auto term = create_uart_terminal(config);
|
||||
return std::make_shared<DTE>(config, std::move(term));
|
||||
)
|
||||
}
|
||||
|
||||
std::shared_ptr<DTE> create_vfs_dte(const dte_config *config)
|
||||
{
|
||||
|
39
components/esp_modem/src/esp_modem_api_target.cpp
Normal file
39
components/esp_modem/src/esp_modem_api_target.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2021 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <cassert>
|
||||
#include "esp_log.h"
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "uart_terminal.hpp"
|
||||
#include "vfs_termial.hpp"
|
||||
#include "cxx_include/esp_modem_api.hpp"
|
||||
#include "cxx_include/esp_modem_dce_factory.hpp"
|
||||
#include "esp_modem_config.h"
|
||||
#include "exception_stub.hpp"
|
||||
|
||||
namespace esp_modem {
|
||||
|
||||
#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
|
||||
static const char *TAG = "modem_api_target";
|
||||
#endif
|
||||
|
||||
std::shared_ptr<DTE> create_uart_dte(const dte_config *config)
|
||||
{
|
||||
TRY_CATCH_RET_NULL(
|
||||
auto term = create_uart_terminal(config);
|
||||
return std::make_shared<DTE>(config, std::move(term));
|
||||
)
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
@ -23,6 +23,14 @@
|
||||
#include "exception_stub.hpp"
|
||||
#include "cstring"
|
||||
|
||||
#ifndef ESP_MODEM_C_API_STR_MAX
|
||||
#define ESP_MODEM_C_API_STR_MAX 64
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_STRLCPY
|
||||
size_t strlcpy(char *dest, const char *src, size_t len);
|
||||
#endif
|
||||
|
||||
//
|
||||
// C API definitions
|
||||
using namespace esp_modem;
|
||||
@ -173,7 +181,85 @@ extern "C" esp_err_t esp_modem_get_imsi(esp_modem_dce_t *dce_wrap, char *p_imsi)
|
||||
std::string imsi;
|
||||
auto ret = command_response_to_esp_err(dce_wrap->dce->get_imsi(imsi));
|
||||
if (ret == ESP_OK && !imsi.empty()) {
|
||||
strcpy(p_imsi, imsi.c_str());
|
||||
strlcpy(p_imsi, imsi.c_str(), ESP_MODEM_C_API_STR_MAX);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_set_flow_control(esp_modem_dce_t *dce_wrap, int dce_flow, int dte_flow)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return command_response_to_esp_err(dce_wrap->dce->set_flow_control(dce_flow, dte_flow));
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_store_profile(esp_modem_dce_t *dce_wrap)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return command_response_to_esp_err(dce_wrap->dce->store_profile());
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_get_imei(esp_modem_dce_t *dce_wrap, char *p_imei)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
std::string imei;
|
||||
auto ret = command_response_to_esp_err(dce_wrap->dce->get_imei(imei));
|
||||
if (ret == ESP_OK && !imei.empty()) {
|
||||
strlcpy(p_imei, imei.c_str(), ESP_MODEM_C_API_STR_MAX);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_get_operator_name(esp_modem_dce_t *dce_wrap, char *p_name)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
std::string name;
|
||||
auto ret = command_response_to_esp_err(dce_wrap->dce->get_operator_name(name));
|
||||
if (ret == ESP_OK && !name.empty()) {
|
||||
strlcpy(p_name, name.c_str(), ESP_MODEM_C_API_STR_MAX);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_get_module_name(esp_modem_dce_t *dce_wrap, char *p_name)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
std::string name;
|
||||
auto ret = command_response_to_esp_err(dce_wrap->dce->get_module_name(name));
|
||||
if (ret == ESP_OK && !name.empty()) {
|
||||
strlcpy(p_name, name.c_str(), ESP_MODEM_C_API_STR_MAX);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_get_battery_status(esp_modem_dce_t *dce_wrap, int *p_volt, int *p_bcs, int *p_bcl)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr || p_bcs == nullptr || p_bcl == nullptr || p_volt == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
int bcs, bcl, volt;
|
||||
auto ret = command_response_to_esp_err(dce_wrap->dce->get_battery_status(volt, bcs, bcl));
|
||||
if (ret == ESP_OK) {
|
||||
*p_volt = volt;
|
||||
*p_bcs = bcs;
|
||||
*p_bcl = bcl;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_power_down(esp_modem_dce_t *dce_wrap)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return command_response_to_esp_err(dce_wrap->dce->power_down());
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ command_result get_battery_status(CommandableIf *t, int &voltage, int &bcs, int
|
||||
// Parsing +CBC: <bcs>,<bcl>,<voltage>
|
||||
out = out.substr(pattern.size());
|
||||
int pos, value, property = 0;
|
||||
while ((pos = out.find(',') != std::string::npos)) {
|
||||
while ((pos = out.find(',')) != std::string::npos) {
|
||||
if (std::from_chars(out.data(), out.data() + pos, value).ec == std::errc::invalid_argument) {
|
||||
return command_result::FAIL;
|
||||
}
|
||||
|
@ -50,12 +50,6 @@ DECLARE_ALL_COMMAND_APIS(return_type name(...) )
|
||||
//
|
||||
// Handle specific commands for specific supported modems
|
||||
//
|
||||
command_result SIM7600::get_module_name(std::string &name)
|
||||
{
|
||||
name = "7600";
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result SIM7600::get_battery_status(int &voltage, int &bcs, int &bcl)
|
||||
{
|
||||
return dce_commands::get_battery_status_sim7xxx(dte.get(), voltage, bcs, bcl);
|
||||
@ -66,12 +60,6 @@ command_result SIM7600::power_down()
|
||||
return dce_commands::power_down_sim7xxx(dte.get());
|
||||
}
|
||||
|
||||
command_result SIM800::get_module_name(std::string &name)
|
||||
{
|
||||
name = "800L";
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
command_result SIM800::power_down()
|
||||
{
|
||||
return dce_commands::power_down_sim8xx(dte.get());
|
||||
@ -82,10 +70,4 @@ command_result SIM800::set_data_mode()
|
||||
return dce_commands::set_data_mode_sim8xx(dte.get());
|
||||
}
|
||||
|
||||
command_result BG96::get_module_name(std::string &name)
|
||||
{
|
||||
name = "BG96";
|
||||
return command_result::OK;
|
||||
}
|
||||
|
||||
}
|
@ -106,4 +106,9 @@ void Task::Relinquish()
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
||||
void Task::Delay(uint32_t ms)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(ms));
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
||||
|
@ -89,4 +89,9 @@ void Task::Relinquish()
|
||||
usleep(0);
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
||||
void Task::Delay(uint32_t ms)
|
||||
{
|
||||
usleep(ms*1000);
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
||||
|
@ -28,8 +28,10 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
|
||||
response = "CONNECT\r\n";
|
||||
} else if (command.find("AT+CSQ\r") != std::string::npos) {
|
||||
response = "+CSQ: 123,456\n\r\nOK\r\n";
|
||||
} else if (command.find("AT+CGMM\r") != std::string::npos) {
|
||||
response = "0G Dummy Model\n\r\nOK\r\n";
|
||||
} else if (command.find("AT+CBC\r") != std::string::npos) {
|
||||
response = is_bg96 ? "+CBC: 1,2,123456V\r\r\n\r\nOK\r\n\n\r\n" :
|
||||
response = is_bg96 ? "+CBC: 1,20,123456\r\r\n\r\nOK\r\n\n\r\n" :
|
||||
"+CBC: 123.456V\r\r\n\r\nOK\r\n\n\r\n";
|
||||
} else if (command.find("AT+CPIN=1234\r") != std::string::npos) {
|
||||
response = "OK\r\n";
|
||||
|
@ -24,7 +24,7 @@ TEST_CASE("DCE AT parser", "[esp_modem]")
|
||||
CHECK(dce->get_battery_status(milli_volt, bcl, bcs) == command_result::OK);
|
||||
CHECK(milli_volt == 123456);
|
||||
CHECK(bcl == 1);
|
||||
CHECK(bcs == 2);
|
||||
CHECK(bcs == 20);
|
||||
|
||||
int rssi, ber;
|
||||
CHECK(dce->get_signal_quality(rssi, ber) == command_result::OK);
|
||||
@ -37,6 +37,10 @@ TEST_CASE("DCE AT parser", "[esp_modem]")
|
||||
CHECK(dce->set_pin("1234") == command_result::OK);
|
||||
CHECK(dce->read_pin(pin_ok) == command_result::OK);
|
||||
CHECK(pin_ok == true);
|
||||
|
||||
std::string model;
|
||||
CHECK(dce->get_module_name(model) == command_result::OK);
|
||||
CHECK(model == "0G Dummy Model");
|
||||
}
|
||||
|
||||
|
||||
|
93
components/esp_websocket_client/.gitignore
vendored
Normal file
93
components/esp_websocket_client/.gitignore
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
.config
|
||||
*.o
|
||||
*.pyc
|
||||
|
||||
# gtags
|
||||
GTAGS
|
||||
GRTAGS
|
||||
GPATH
|
||||
|
||||
# emacs
|
||||
.dir-locals.el
|
||||
|
||||
# emacs temp file suffixes
|
||||
*~
|
||||
.#*
|
||||
\#*#
|
||||
|
||||
# eclipse setting
|
||||
.settings
|
||||
|
||||
# MacOS directory files
|
||||
.DS_Store
|
||||
|
||||
# Components Unit Test Apps files
|
||||
components/**/build
|
||||
components/**/sdkconfig
|
||||
components/**/sdkconfig.old
|
||||
|
||||
# Example project files
|
||||
examples/**/sdkconfig
|
||||
examples/**/sdkconfig.old
|
||||
examples/**/build
|
||||
|
||||
# Doc build artifacts
|
||||
docs/_build/
|
||||
docs/doxygen_sqlite3.db
|
||||
|
||||
# Downloaded font files
|
||||
docs/_static/DejaVuSans.ttf
|
||||
docs/_static/NotoSansSC-Regular.otf
|
||||
|
||||
# Unit test app files
|
||||
tools/unit-test-app/sdkconfig
|
||||
tools/unit-test-app/sdkconfig.old
|
||||
tools/unit-test-app/build
|
||||
tools/unit-test-app/builds
|
||||
tools/unit-test-app/output
|
||||
tools/unit-test-app/test_configs
|
||||
|
||||
# Unit Test CMake compile log folder
|
||||
log_ut_cmake
|
||||
|
||||
# test application build files
|
||||
test/**/build
|
||||
test/**/sdkconfig
|
||||
test/**/sdkconfig.old
|
||||
|
||||
# IDF monitor test
|
||||
tools/test_idf_monitor/outputs
|
||||
|
||||
TEST_LOGS
|
||||
|
||||
# gcov coverage reports
|
||||
*.gcda
|
||||
*.gcno
|
||||
coverage.info
|
||||
coverage_report/
|
||||
|
||||
test_multi_heap_host
|
||||
|
||||
# VS Code Settings
|
||||
.vscode/
|
||||
|
||||
# VIM files
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Clion IDE CMake build & config
|
||||
.idea/
|
||||
cmake-build-*/
|
||||
|
||||
# Results for the checking of the Python coding style and static analysis
|
||||
.mypy_cache
|
||||
flake8_output.txt
|
||||
|
||||
# ESP-IDF default build directory name
|
||||
build
|
||||
|
||||
# lock files for examples and components
|
||||
dependencies.lock
|
||||
|
||||
# ignore generated docs
|
||||
docs/html
|
12
components/esp_websocket_client/CMakeLists.txt
Normal file
12
components/esp_websocket_client/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
if(NOT CONFIG_WS_TRANSPORT AND NOT CMAKE_BUILD_EARLY_EXPANSION)
|
||||
message(STATUS "Websocket transport is disabled so the esp_websocket_client component will not be built")
|
||||
# note: the component is still included in the build so it can become visible again in config
|
||||
# without needing to re-run CMake. However no source or header files are built.
|
||||
idf_component_register()
|
||||
return()
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS "esp_websocket_client.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES lwip esp-tls tcp_transport http_parser
|
||||
PRIV_REQUIRES esp_timer)
|
202
components/esp_websocket_client/LICENSE
Normal file
202
components/esp_websocket_client/LICENSE
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
11
components/esp_websocket_client/README.md
Normal file
11
components/esp_websocket_client/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# ESP WEBSOCKET CLIENT
|
||||
|
||||
The `esp-websocket_client` component is a managed component for `esp-idf` that contains implementation of [WebSocket protocol client](https://datatracker.ietf.org/doc/html/rfc6455) for ESP32
|
||||
|
||||
## Examples
|
||||
|
||||
Get started with example test [Example](examples/README.md):
|
||||
|
||||
## Documentation
|
||||
|
||||
* View the full [html documentation](https://espressif.github.io/esp-protocols/esp_websocket_client/index.html)
|
75
components/esp_websocket_client/docs/Doxyfile
Normal file
75
components/esp_websocket_client/docs/Doxyfile
Normal file
@ -0,0 +1,75 @@
|
||||
# This is Doxygen configuration file
|
||||
#
|
||||
# Doxygen provides over 260 configuration statements
|
||||
# To make this file easier to follow,
|
||||
# it contains only statements that are non-default
|
||||
#
|
||||
# NOTE:
|
||||
# It is recommended not to change defaults unless specifically required
|
||||
# Test any changes how they affect generated documentation
|
||||
# Make sure that correct warnings are generated to flag issues with documented code
|
||||
#
|
||||
# For the complete list of configuration statements see:
|
||||
# http://doxygen.nl/manual/config.html
|
||||
|
||||
|
||||
PROJECT_NAME = "ESP Protocols Programming Guide"
|
||||
|
||||
## The 'INPUT' statement below is used as input by script 'gen-df-input.py'
|
||||
## to automatically generate API reference list files heder_file.inc
|
||||
## These files are placed in '_inc' directory
|
||||
## and used to include in API reference documentation
|
||||
|
||||
INPUT = \
|
||||
$(PROJECT_PATH)/include/esp_websocket_client.h
|
||||
|
||||
## Get warnings for functions that have no documentation for their parameters or return value
|
||||
##
|
||||
WARN_NO_PARAMDOC = YES
|
||||
|
||||
## Enable preprocessing and remove __attribute__(...) expressions from the INPUT files
|
||||
##
|
||||
ENABLE_PREPROCESSING = YES
|
||||
MACRO_EXPANSION = YES
|
||||
EXPAND_ONLY_PREDEF = YES
|
||||
PREDEFINED = \
|
||||
$(ENV_DOXYGEN_DEFINES) \
|
||||
__DOXYGEN__=1 \
|
||||
__attribute__(x)= \
|
||||
_Static_assert()= \
|
||||
IDF_DEPRECATED(X)= \
|
||||
IRAM_ATTR= \
|
||||
configSUPPORT_DYNAMIC_ALLOCATION=1 \
|
||||
configSUPPORT_STATIC_ALLOCATION=1 \
|
||||
configQUEUE_REGISTRY_SIZE=1 \
|
||||
configUSE_RECURSIVE_MUTEXES=1 \
|
||||
configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS=1 \
|
||||
configNUM_THREAD_LOCAL_STORAGE_POINTERS=1 \
|
||||
configUSE_APPLICATION_TASK_TAG=1 \
|
||||
configTASKLIST_INCLUDE_COREID=1 \
|
||||
"ESP_EVENT_DECLARE_BASE(x)=extern esp_event_base_t x"
|
||||
|
||||
## Do not complain about not having dot
|
||||
##
|
||||
HAVE_DOT = NO
|
||||
|
||||
## Generate XML that is required for Breathe
|
||||
##
|
||||
GENERATE_XML = YES
|
||||
XML_OUTPUT = xml
|
||||
|
||||
GENERATE_HTML = NO
|
||||
HAVE_DOT = NO
|
||||
GENERATE_LATEX = NO
|
||||
GENERATE_MAN = YES
|
||||
GENERATE_RTF = NO
|
||||
|
||||
## Skip distracting progress messages
|
||||
##
|
||||
QUIET = YES
|
||||
|
||||
## Enable Section Tags for conditional documentation
|
||||
##
|
||||
ENABLED_SECTIONS += \
|
||||
DOC_EXCLUDE_HEADER_SECTION \ ## To conditionally remove doc sections from IDF source files without affecting documentation in upstream files.
|
||||
DOC_SINGLE_GROUP ## To conditionally remove groups from the documentation and create a 'flat' document without affecting documentation in upstream files.
|
21
components/esp_websocket_client/docs/conf_common.py
Normal file
21
components/esp_websocket_client/docs/conf_common.py
Normal file
@ -0,0 +1,21 @@
|
||||
from esp_docs.conf_docs import * # noqa: F403,F401
|
||||
|
||||
extensions += ['sphinx_copybutton',
|
||||
# Needed as a trigger for running doxygen
|
||||
'esp_docs.esp_extensions.dummy_build_system',
|
||||
'esp_docs.esp_extensions.run_doxygen',
|
||||
]
|
||||
|
||||
# link roles config
|
||||
github_repo = 'espressif/esp-idf'
|
||||
|
||||
# context used by sphinx_idf_theme
|
||||
html_context['github_user'] = 'espressif'
|
||||
html_context['github_repo'] = 'esp-docs'
|
||||
|
||||
# Extra options required by sphinx_idf_theme
|
||||
project_slug = 'esp-idf' # >=5.0
|
||||
versions_url = 'https://dl.espressif.com/dl/esp-idf/idf_versions.js'
|
||||
|
||||
idf_targets = ['esp32']
|
||||
languages = ['en']
|
24
components/esp_websocket_client/docs/en/conf.py
Normal file
24
components/esp_websocket_client/docs/en/conf.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# English Language RTD & Sphinx config file
|
||||
#
|
||||
# Uses ../conf_common.py for most non-language-specific settings.
|
||||
|
||||
# Importing conf_common adds all the non-language-specific
|
||||
# parts to this conf module
|
||||
|
||||
try:
|
||||
from conf_common import * # noqa: F403,F401
|
||||
except ImportError:
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../'))
|
||||
from conf_common import * # noqa: F403,F401
|
||||
|
||||
# General information about the project.
|
||||
project = u'ESP-Docs'
|
||||
copyright = u'2016 - 2022, Espressif Systems (Shanghai) Co., Ltd'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
language = 'en'
|
127
components/esp_websocket_client/docs/en/index.rst
Normal file
127
components/esp_websocket_client/docs/en/index.rst
Normal file
@ -0,0 +1,127 @@
|
||||
ESP WebSocket Client
|
||||
====================
|
||||
|
||||
Overview
|
||||
--------
|
||||
The ESP WebSocket client is an implementation of `WebSocket protocol client <https://tools.ietf.org/html/rfc6455>`_ for {IDF_TARGET_NAME}
|
||||
|
||||
Features
|
||||
--------
|
||||
* Supports WebSocket over TCP, TLS with mbedtls
|
||||
* Easy to setup with URI
|
||||
* Multiple instances (Multiple clients in one application)
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
URI
|
||||
^^^
|
||||
|
||||
- Supports ``ws``, ``wss`` schemes
|
||||
- WebSocket samples:
|
||||
|
||||
- ``ws://echo.websocket.org``: WebSocket over TCP, default port 80
|
||||
- ``wss://echo.websocket.org``: WebSocket over SSL, default port 443
|
||||
|
||||
Minimal configurations:
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "ws://echo.websocket.org",
|
||||
};
|
||||
|
||||
The WebSocket client supports the use of both path and query in the URI. Sample:
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "ws://echo.websocket.org/connectionhandler?id=104",
|
||||
};
|
||||
|
||||
|
||||
If there are any options related to the URI in
|
||||
:cpp:type:`esp_websocket_client_config_t`, the option defined by the URI will be
|
||||
overridden. Sample:
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "ws://echo.websocket.org:123",
|
||||
.port = 4567,
|
||||
};
|
||||
//WebSocket client will connect to websocket.org using port 4567
|
||||
|
||||
TLS
|
||||
^^^
|
||||
|
||||
Configuration:
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "wss://echo.websocket.org",
|
||||
.cert_pem = (const char *)websocket_org_pem_start,
|
||||
};
|
||||
|
||||
.. note:: If you want to verify the server, then you need to provide a certificate in PEM format, and provide to ``cert_pem`` in :cpp:type:`websocket_client_config_t`. If no certficate is provided then the TLS connection will default to not requiring verification.
|
||||
|
||||
PEM certificate for this example could be extracted from an openssl `s_client` command connecting to websocket.org.
|
||||
In case a host operating system has `openssl` and `sed` packages installed, one could execute the following command to download and save the root or intermediate root certificate to a file (Note for Windows users: Both Linux like environment or Windows native packages may be used).
|
||||
```
|
||||
echo "" | openssl s_client -showcerts -connect websocket.org:443 | sed -n "1,/Root/d; /BEGIN/,/END/p" | openssl x509 -outform PEM >websocket_org.pem
|
||||
```
|
||||
|
||||
This command will extract the second certificate in the chain and save it as a pem-file.
|
||||
|
||||
Subprotocol
|
||||
^^^^^^^^^^^
|
||||
|
||||
The subprotocol field in the config struct can be used to request a subprotocol
|
||||
|
||||
.. code:: c
|
||||
|
||||
const esp_websocket_client_config_t ws_cfg = {
|
||||
.uri = "ws://websocket.org",
|
||||
.subprotocol = "soap",
|
||||
};
|
||||
|
||||
.. note:: The client is indifferent to the subprotocol field in the server response and will accept the connection no matter what the server replies.
|
||||
|
||||
For more options on :cpp:type:`esp_websocket_client_config_t`, please refer to API reference below
|
||||
|
||||
Events
|
||||
------
|
||||
* `WEBSOCKET_EVENT_CONNECTED`: The client has successfully established a connection to the server. The client is now ready to send and receive data. Contains no event data.
|
||||
* `WEBSOCKET_EVENT_DISCONNECTED`: The client has aborted the connection due to the transport layer failing to read data, e.g. because the server is unavailable. Contains no event data.
|
||||
* `WEBSOCKET_EVENT_DATA`: The client has successfully received and parsed a WebSocket frame. The event data contains a pointer to the payload data, the length of the payload data as well as the opcode of the received frame. A message may be fragmented into multiple events if the length exceeds the buffer size. This event will also be posted for non-payload frames, e.g. pong or connection close frames.
|
||||
* `WEBSOCKET_EVENT_ERROR`: Not used in the current implementation of the client.
|
||||
|
||||
If the client handle is needed in the event handler it can be accessed through the pointer passed to the event handler:
|
||||
|
||||
.. code:: c
|
||||
|
||||
esp_websocket_client_handle_t client = (esp_websocket_client_handle_t)handler_args;
|
||||
|
||||
|
||||
Limitations and Known Issues
|
||||
----------------------------
|
||||
* The client is able to request the use of a subprotocol from the server during the handshake, but does not do any subprotocol related checks on the response from the server.
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
A simple WebSocket example that uses esp_websocket_client to establish a websocket connection and send/receive data with the `websocket.org <https://websocket.org>`_ server can be found here: :example:`example <../examples>`.
|
||||
|
||||
Sending Text Data
|
||||
^^^^^^^^^^^^^^^^^
|
||||
The WebSocket client supports sending data as a text data frame, which informs the application layer that the payload data is text data encoded as UTF-8. Example:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_websocket_client.inc
|
||||
|
27
components/esp_websocket_client/docs/generate_docs
Executable file
27
components/esp_websocket_client/docs/generate_docs
Executable file
@ -0,0 +1,27 @@
|
||||
build-docs --target esp32 --language en
|
||||
|
||||
cp -rf _build/en/esp32/html .
|
||||
rm -rf _build __pycache__
|
||||
|
||||
# Modifes some version and target fields of index.html
|
||||
echo "<script type="text/javascript">
|
||||
window.onload =(function() {
|
||||
var myAnchor = document.getElementById('version-select');
|
||||
var mySpan = document.createElement('input');
|
||||
mySpan.setAttribute('type', 'text');
|
||||
mySpan.setAttribute('maxLength', '10');
|
||||
mySpan.value = 'latest';
|
||||
mySpan.setAttribute('disabled', true);
|
||||
myAnchor.parentNode.replaceChild(mySpan, myAnchor);
|
||||
|
||||
var myAnchor = document.getElementById('target-select');
|
||||
var mySpan = document.createElement('input');
|
||||
mySpan.setAttribute('type', 'text');
|
||||
mySpan.setAttribute('maxLength', '10');
|
||||
mySpan.value = 'all targets';
|
||||
mySpan.setAttribute('disabled', true);
|
||||
myAnchor.parentNode.replaceChild(mySpan, myAnchor);
|
||||
|
||||
})();
|
||||
</script>" >> html/index.html
|
||||
|
911
components/esp_websocket_client/esp_websocket_client.c
Normal file
911
components/esp_websocket_client/esp_websocket_client.c
Normal file
@ -0,0 +1,911 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_websocket_client.h"
|
||||
#include "esp_transport.h"
|
||||
#include "esp_transport_tcp.h"
|
||||
#include "esp_transport_ssl.h"
|
||||
#include "esp_transport_ws.h"
|
||||
/* using uri parser */
|
||||
#include "http_parser.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
static const char *TAG = "WEBSOCKET_CLIENT";
|
||||
|
||||
#define WEBSOCKET_TCP_DEFAULT_PORT (80)
|
||||
#define WEBSOCKET_SSL_DEFAULT_PORT (443)
|
||||
#define WEBSOCKET_BUFFER_SIZE_BYTE (1024)
|
||||
#define WEBSOCKET_RECONNECT_TIMEOUT_MS (10*1000)
|
||||
#define WEBSOCKET_TASK_PRIORITY (5)
|
||||
#define WEBSOCKET_TASK_STACK (4*1024)
|
||||
#define WEBSOCKET_NETWORK_TIMEOUT_MS (10*1000)
|
||||
#define WEBSOCKET_PING_INTERVAL_SEC (10)
|
||||
#define WEBSOCKET_EVENT_QUEUE_SIZE (1)
|
||||
#define WEBSOCKET_PINGPONG_TIMEOUT_SEC (120)
|
||||
#define WEBSOCKET_KEEP_ALIVE_IDLE (5)
|
||||
#define WEBSOCKET_KEEP_ALIVE_INTERVAL (5)
|
||||
#define WEBSOCKET_KEEP_ALIVE_COUNT (3)
|
||||
|
||||
#define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) { \
|
||||
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted"); \
|
||||
action; \
|
||||
}
|
||||
|
||||
#define ESP_WS_CLIENT_ERR_OK_CHECK(TAG, err, action) { \
|
||||
esp_err_t _esp_ws_err_to_check = err; \
|
||||
if (_esp_ws_err_to_check != ESP_OK) { \
|
||||
ESP_LOGE(TAG,"%s(%d): Expected ESP_OK; reported: %d", __FUNCTION__, __LINE__, _esp_ws_err_to_check); \
|
||||
action; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define ESP_WS_CLIENT_STATE_CHECK(TAG, a, action) if ((a->state) < WEBSOCKET_STATE_INIT) { \
|
||||
ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Websocket already stop"); \
|
||||
action; \
|
||||
}
|
||||
|
||||
const static int STOPPED_BIT = BIT0;
|
||||
const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame was sent by the client
|
||||
// and we are waiting for the server to continue with clean close
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS);
|
||||
|
||||
typedef struct {
|
||||
int task_stack;
|
||||
int task_prio;
|
||||
char *uri;
|
||||
char *host;
|
||||
char *path;
|
||||
char *scheme;
|
||||
char *username;
|
||||
char *password;
|
||||
int port;
|
||||
bool auto_reconnect;
|
||||
void *user_context;
|
||||
int network_timeout_ms;
|
||||
char *subprotocol;
|
||||
char *user_agent;
|
||||
char *headers;
|
||||
int pingpong_timeout_sec;
|
||||
size_t ping_interval_sec;
|
||||
} websocket_config_storage_t;
|
||||
|
||||
typedef enum {
|
||||
WEBSOCKET_STATE_ERROR = -1,
|
||||
WEBSOCKET_STATE_UNKNOW = 0,
|
||||
WEBSOCKET_STATE_INIT,
|
||||
WEBSOCKET_STATE_CONNECTED,
|
||||
WEBSOCKET_STATE_WAIT_TIMEOUT,
|
||||
WEBSOCKET_STATE_CLOSING,
|
||||
} websocket_client_state_t;
|
||||
|
||||
struct esp_websocket_client {
|
||||
esp_event_loop_handle_t event_handle;
|
||||
TaskHandle_t task_handle;
|
||||
esp_transport_list_handle_t transport_list;
|
||||
esp_transport_handle_t transport;
|
||||
websocket_config_storage_t *config;
|
||||
websocket_client_state_t state;
|
||||
uint64_t keepalive_tick_ms;
|
||||
uint64_t reconnect_tick_ms;
|
||||
uint64_t ping_tick_ms;
|
||||
uint64_t pingpong_tick_ms;
|
||||
int wait_timeout_ms;
|
||||
int auto_reconnect;
|
||||
bool run;
|
||||
bool wait_for_pong_resp;
|
||||
EventGroupHandle_t status_bits;
|
||||
SemaphoreHandle_t lock;
|
||||
char *rx_buffer;
|
||||
char *tx_buffer;
|
||||
int buffer_size;
|
||||
ws_transport_opcodes_t last_opcode;
|
||||
int payload_len;
|
||||
int payload_offset;
|
||||
esp_transport_keep_alive_t keep_alive_cfg;
|
||||
struct ifreq *if_name;
|
||||
};
|
||||
|
||||
static uint64_t _tick_get_ms(void)
|
||||
{
|
||||
return esp_timer_get_time()/1000;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle_t client,
|
||||
esp_websocket_event_id_t event,
|
||||
const char *data,
|
||||
int data_len)
|
||||
{
|
||||
esp_err_t err;
|
||||
esp_websocket_event_data_t event_data;
|
||||
|
||||
event_data.client = client;
|
||||
event_data.user_context = client->config->user_context;
|
||||
event_data.data_ptr = data;
|
||||
event_data.data_len = data_len;
|
||||
event_data.op_code = client->last_opcode;
|
||||
event_data.payload_len = client->payload_len;
|
||||
event_data.payload_offset = client->payload_offset;
|
||||
|
||||
if ((err = esp_event_post_to(client->event_handle,
|
||||
WEBSOCKET_EVENTS, event,
|
||||
&event_data,
|
||||
sizeof(esp_websocket_event_data_t),
|
||||
portMAX_DELAY)) != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
return esp_event_loop_run(client->event_handle, 0);
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client)
|
||||
{
|
||||
ESP_WS_CLIENT_STATE_CHECK(TAG, client, return ESP_FAIL);
|
||||
esp_transport_close(client->transport);
|
||||
|
||||
if (client->config->auto_reconnect) {
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms);
|
||||
}
|
||||
client->state = WEBSOCKET_STATE_WAIT_TIMEOUT;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DISCONNECTED, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t client, const esp_websocket_client_config_t *config)
|
||||
{
|
||||
websocket_config_storage_t *cfg = client->config;
|
||||
cfg->task_prio = config->task_prio;
|
||||
if (cfg->task_prio <= 0) {
|
||||
cfg->task_prio = WEBSOCKET_TASK_PRIORITY;
|
||||
}
|
||||
|
||||
cfg->task_stack = config->task_stack;
|
||||
if (cfg->task_stack == 0) {
|
||||
cfg->task_stack = WEBSOCKET_TASK_STACK;
|
||||
}
|
||||
|
||||
if (config->host) {
|
||||
cfg->host = strdup(config->host);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->host, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (config->port) {
|
||||
cfg->port = config->port;
|
||||
}
|
||||
|
||||
if (config->username) {
|
||||
free(cfg->username);
|
||||
cfg->username = strdup(config->username);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->username, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (config->password) {
|
||||
free(cfg->password);
|
||||
cfg->password = strdup(config->password);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->password, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (config->uri) {
|
||||
free(cfg->uri);
|
||||
cfg->uri = strdup(config->uri);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->uri, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (config->path) {
|
||||
free(cfg->path);
|
||||
cfg->path = strdup(config->path);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->path, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (config->subprotocol) {
|
||||
free(cfg->subprotocol);
|
||||
cfg->subprotocol = strdup(config->subprotocol);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->subprotocol, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (config->user_agent) {
|
||||
free(cfg->user_agent);
|
||||
cfg->user_agent = strdup(config->user_agent);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->user_agent, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (config->headers) {
|
||||
free(cfg->headers);
|
||||
cfg->headers = strdup(config->headers);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->headers, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
|
||||
cfg->user_context = config->user_context;
|
||||
cfg->auto_reconnect = true;
|
||||
if (config->disable_auto_reconnect) {
|
||||
cfg->auto_reconnect = false;
|
||||
}
|
||||
|
||||
if (config->disable_pingpong_discon){
|
||||
cfg->pingpong_timeout_sec = 0;
|
||||
} else if (config->pingpong_timeout_sec) {
|
||||
cfg->pingpong_timeout_sec = config->pingpong_timeout_sec;
|
||||
} else {
|
||||
cfg->pingpong_timeout_sec = WEBSOCKET_PINGPONG_TIMEOUT_SEC;
|
||||
}
|
||||
|
||||
if (config->network_timeout_ms <= 0) {
|
||||
cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS;
|
||||
ESP_LOGW(TAG, "`network_timeout_ms` is not set, or it is less than or equal to zero, using default time out %d (milliseconds)", WEBSOCKET_NETWORK_TIMEOUT_MS);
|
||||
} else {
|
||||
cfg->network_timeout_ms = config->network_timeout_ms;
|
||||
}
|
||||
|
||||
if (config->ping_interval_sec == 0) {
|
||||
cfg->ping_interval_sec = WEBSOCKET_PING_INTERVAL_SEC;
|
||||
} else {
|
||||
cfg->ping_interval_sec = config->ping_interval_sec;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
websocket_config_storage_t *cfg = client->config;
|
||||
if (client->config == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
free(cfg->host);
|
||||
free(cfg->uri);
|
||||
free(cfg->path);
|
||||
free(cfg->scheme);
|
||||
free(cfg->username);
|
||||
free(cfg->password);
|
||||
free(cfg->subprotocol);
|
||||
free(cfg->user_agent);
|
||||
free(cfg->headers);
|
||||
memset(cfg, 0, sizeof(websocket_config_storage_t));
|
||||
free(client->config);
|
||||
client->config = NULL;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, const char *scheme)
|
||||
{
|
||||
esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, scheme);
|
||||
if (trans) {
|
||||
const esp_transport_ws_config_t config = {
|
||||
.ws_path = client->config->path,
|
||||
.sub_protocol = client->config->subprotocol,
|
||||
.user_agent = client->config->user_agent,
|
||||
.headers = client->config->headers,
|
||||
.propagate_control_frames = true
|
||||
};
|
||||
return esp_transport_ws_set_config(trans, &config);
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config)
|
||||
{
|
||||
esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client));
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client, return NULL);
|
||||
|
||||
esp_event_loop_args_t event_args = {
|
||||
.queue_size = WEBSOCKET_EVENT_QUEUE_SIZE,
|
||||
.task_name = NULL // no task will be created
|
||||
};
|
||||
|
||||
if (esp_event_loop_create(&event_args, &client->event_handle) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error create event handler for websocket client");
|
||||
free(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (config->keep_alive_enable == true) {
|
||||
client->keep_alive_cfg.keep_alive_enable = true;
|
||||
client->keep_alive_cfg.keep_alive_idle = (config->keep_alive_idle == 0) ? WEBSOCKET_KEEP_ALIVE_IDLE : config->keep_alive_idle;
|
||||
client->keep_alive_cfg.keep_alive_interval = (config->keep_alive_interval == 0) ? WEBSOCKET_KEEP_ALIVE_INTERVAL : config->keep_alive_interval;
|
||||
client->keep_alive_cfg.keep_alive_count = (config->keep_alive_count == 0) ? WEBSOCKET_KEEP_ALIVE_COUNT : config->keep_alive_count;
|
||||
}
|
||||
|
||||
if (config->if_name) {
|
||||
client->if_name = calloc(1, sizeof(struct ifreq) + 1);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->if_name, goto _websocket_init_fail);
|
||||
memcpy(client->if_name, config->if_name, sizeof(struct ifreq));
|
||||
}
|
||||
|
||||
client->lock = xSemaphoreCreateRecursiveMutex();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail);
|
||||
|
||||
client->config = calloc(1, sizeof(websocket_config_storage_t));
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail);
|
||||
|
||||
client->transport_list = esp_transport_list_init();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->transport_list, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT);
|
||||
esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup
|
||||
esp_transport_tcp_set_keep_alive(tcp, &client->keep_alive_cfg);
|
||||
esp_transport_tcp_set_interface_name(tcp, client->if_name);
|
||||
|
||||
esp_transport_handle_t ws = esp_transport_ws_init(tcp);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, ws, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_set_default_port(ws, WEBSOCKET_TCP_DEFAULT_PORT);
|
||||
esp_transport_list_add(client->transport_list, ws, "ws");
|
||||
if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) {
|
||||
asprintf(&client->config->scheme, "ws");
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
|
||||
}
|
||||
|
||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, ssl, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_set_default_port(ssl, WEBSOCKET_SSL_DEFAULT_PORT);
|
||||
esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup
|
||||
if (config->use_global_ca_store == true) {
|
||||
esp_transport_ssl_enable_global_ca_store(ssl);
|
||||
} else if (config->cert_pem) {
|
||||
if (!config->cert_len) {
|
||||
esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem));
|
||||
} else {
|
||||
esp_transport_ssl_set_cert_data_der(ssl, config->cert_pem, config->cert_len);
|
||||
}
|
||||
}
|
||||
if (config->client_cert) {
|
||||
if (!config->client_cert_len) {
|
||||
esp_transport_ssl_set_client_cert_data(ssl, config->client_cert, strlen(config->client_cert));
|
||||
} else {
|
||||
esp_transport_ssl_set_client_cert_data_der(ssl, config->client_cert, config->client_cert_len);
|
||||
}
|
||||
}
|
||||
if (config->client_key) {
|
||||
if (!config->client_key_len) {
|
||||
esp_transport_ssl_set_client_key_data(ssl, config->client_key, strlen(config->client_key));
|
||||
} else {
|
||||
esp_transport_ssl_set_client_key_data_der(ssl, config->client_key, config->client_key_len);
|
||||
}
|
||||
}
|
||||
if (config->skip_cert_common_name_check) {
|
||||
esp_transport_ssl_skip_common_name_check(ssl);
|
||||
}
|
||||
if (config->reconnect_timeout_ms <= 0) {
|
||||
client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS;
|
||||
ESP_LOGW(TAG, "`reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out %d (milliseconds)", WEBSOCKET_RECONNECT_TIMEOUT_MS);
|
||||
} else {
|
||||
client->wait_timeout_ms = config->reconnect_timeout_ms;
|
||||
}
|
||||
esp_transport_handle_t wss = esp_transport_ws_init(ssl);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, wss, goto _websocket_init_fail);
|
||||
|
||||
esp_transport_set_default_port(wss, WEBSOCKET_SSL_DEFAULT_PORT);
|
||||
|
||||
esp_transport_list_add(client->transport_list, wss, "wss");
|
||||
if (config->transport == WEBSOCKET_TRANSPORT_OVER_SSL) {
|
||||
asprintf(&client->config->scheme, "wss");
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
|
||||
}
|
||||
|
||||
if (config->uri) {
|
||||
if (esp_websocket_client_set_uri(client, config->uri) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Invalid uri");
|
||||
goto _websocket_init_fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (esp_websocket_client_set_config(client, config) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set the configuration");
|
||||
goto _websocket_init_fail;
|
||||
}
|
||||
|
||||
if (client->config->scheme == NULL) {
|
||||
asprintf(&client->config->scheme, "ws");
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
|
||||
}
|
||||
|
||||
ESP_WS_CLIENT_ERR_OK_CHECK(TAG, set_websocket_transport_optional_settings(client, "ws"), goto _websocket_init_fail;)
|
||||
ESP_WS_CLIENT_ERR_OK_CHECK(TAG, set_websocket_transport_optional_settings(client, "wss"), goto _websocket_init_fail;)
|
||||
|
||||
client->keepalive_tick_ms = _tick_get_ms();
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
client->ping_tick_ms = _tick_get_ms();
|
||||
client->wait_for_pong_resp = false;
|
||||
|
||||
int buffer_size = config->buffer_size;
|
||||
if (buffer_size <= 0) {
|
||||
buffer_size = WEBSOCKET_BUFFER_SIZE_BYTE;
|
||||
}
|
||||
client->rx_buffer = malloc(buffer_size);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->rx_buffer, {
|
||||
goto _websocket_init_fail;
|
||||
});
|
||||
client->tx_buffer = malloc(buffer_size);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->tx_buffer, {
|
||||
goto _websocket_init_fail;
|
||||
});
|
||||
client->status_bits = xEventGroupCreate();
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->status_bits, {
|
||||
goto _websocket_init_fail;
|
||||
});
|
||||
|
||||
client->buffer_size = buffer_size;
|
||||
return client;
|
||||
|
||||
_websocket_init_fail:
|
||||
esp_websocket_client_destroy(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (client->run) {
|
||||
esp_websocket_client_stop(client);
|
||||
}
|
||||
if (client->event_handle) {
|
||||
esp_event_loop_delete(client->event_handle);
|
||||
}
|
||||
if (client->if_name) {
|
||||
free(client->if_name);
|
||||
}
|
||||
esp_websocket_client_destroy_config(client);
|
||||
esp_transport_list_destroy(client->transport_list);
|
||||
vQueueDelete(client->lock);
|
||||
free(client->tx_buffer);
|
||||
free(client->rx_buffer);
|
||||
if (client->status_bits) {
|
||||
vEventGroupDelete(client->status_bits);
|
||||
}
|
||||
free(client);
|
||||
client = NULL;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, const char *uri)
|
||||
{
|
||||
if (client == NULL || uri == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
struct http_parser_url puri;
|
||||
http_parser_url_init(&puri);
|
||||
int parser_status = http_parser_parse_url(uri, strlen(uri), 0, &puri);
|
||||
if (parser_status != 0) {
|
||||
ESP_LOGE(TAG, "Error parse uri = %s", uri);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (puri.field_data[UF_SCHEMA].len) {
|
||||
free(client->config->scheme);
|
||||
asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
if (puri.field_data[UF_HOST].len) {
|
||||
free(client->config->host);
|
||||
asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->host, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
|
||||
|
||||
if (puri.field_data[UF_PATH].len || puri.field_data[UF_QUERY].len) {
|
||||
free(client->config->path);
|
||||
if (puri.field_data[UF_QUERY].len == 0) {
|
||||
asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off);
|
||||
} else if (puri.field_data[UF_PATH].len == 0) {
|
||||
asprintf(&client->config->path, "/?%.*s", puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
|
||||
} else {
|
||||
asprintf(&client->config->path, "%.*s?%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off,
|
||||
puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
|
||||
}
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
if (puri.field_data[UF_PORT].off) {
|
||||
client->config->port = strtol((const char*)(uri + puri.field_data[UF_PORT].off), NULL, 10);
|
||||
}
|
||||
|
||||
if (puri.field_data[UF_USERINFO].len) {
|
||||
char *user_info = NULL;
|
||||
asprintf(&user_info, "%.*s", puri.field_data[UF_USERINFO].len, uri + puri.field_data[UF_USERINFO].off);
|
||||
if (user_info) {
|
||||
char *pass = strchr(user_info, ':');
|
||||
if (pass) {
|
||||
pass[0] = 0; //terminal username
|
||||
pass ++;
|
||||
free(client->config->password);
|
||||
client->config->password = strdup(pass);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->password, return ESP_ERR_NO_MEM);
|
||||
}
|
||||
free(client->config->username);
|
||||
client->config->username = strdup(user_info);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->username, return ESP_ERR_NO_MEM);
|
||||
free(user_info);
|
||||
} else {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
|
||||
{
|
||||
int rlen;
|
||||
client->payload_offset = 0;
|
||||
do {
|
||||
rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms);
|
||||
if (rlen < 0) {
|
||||
ESP_LOGE(TAG, "Error read data");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
client->payload_len = esp_transport_ws_get_read_payload_len(client->transport);
|
||||
client->last_opcode = esp_transport_ws_get_read_opcode(client->transport);
|
||||
|
||||
if (rlen == 0 && client->last_opcode == WS_TRANSPORT_OPCODES_NONE ) {
|
||||
ESP_LOGV(TAG, "esp_transport_read timeouts");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen);
|
||||
|
||||
client->payload_offset += rlen;
|
||||
} while (client->payload_offset < client->payload_len);
|
||||
|
||||
// if a PING message received -> send out the PONG, this will not work for PING messages with payload longer than buffer len
|
||||
if (client->last_opcode == WS_TRANSPORT_OPCODES_PING) {
|
||||
const char *data = (client->payload_len == 0) ? NULL : client->rx_buffer;
|
||||
ESP_LOGD(TAG, "Sending PONG with payload len=%d", client->payload_len);
|
||||
esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len,
|
||||
client->config->network_timeout_ms);
|
||||
} else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) {
|
||||
client->wait_for_pong_resp = false;
|
||||
} else if (client->last_opcode == WS_TRANSPORT_OPCODES_CLOSE) {
|
||||
ESP_LOGD(TAG, "Received close frame");
|
||||
client->state = WEBSOCKET_STATE_CLOSING;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const uint8_t *data, int len, TickType_t timeout);
|
||||
|
||||
static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout);
|
||||
|
||||
static void esp_websocket_client_task(void *pv)
|
||||
{
|
||||
const int lock_timeout = portMAX_DELAY;
|
||||
esp_websocket_client_handle_t client = (esp_websocket_client_handle_t) pv;
|
||||
client->run = true;
|
||||
|
||||
//get transport by scheme
|
||||
client->transport = esp_transport_list_get_transport(client->transport_list, client->config->scheme);
|
||||
|
||||
if (client->transport == NULL) {
|
||||
ESP_LOGE(TAG, "There are no transports valid, stop websocket client");
|
||||
client->run = false;
|
||||
}
|
||||
//default port
|
||||
if (client->config->port == 0) {
|
||||
client->config->port = esp_transport_get_default_port(client->transport);
|
||||
}
|
||||
|
||||
client->state = WEBSOCKET_STATE_INIT;
|
||||
xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
|
||||
int read_select = 0;
|
||||
while (client->run) {
|
||||
if (xSemaphoreTakeRecursive(client->lock, lock_timeout) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to lock ws-client tasks, exiting the task...");
|
||||
break;
|
||||
}
|
||||
switch ((int)client->state) {
|
||||
case WEBSOCKET_STATE_INIT:
|
||||
if (client->transport == NULL) {
|
||||
ESP_LOGE(TAG, "There are no transport");
|
||||
client->run = false;
|
||||
break;
|
||||
}
|
||||
if (esp_transport_connect(client->transport,
|
||||
client->config->host,
|
||||
client->config->port,
|
||||
client->config->network_timeout_ms) < 0) {
|
||||
ESP_LOGE(TAG, "Error transport connect");
|
||||
esp_websocket_client_abort_connection(client);
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "Transport connected to %s://%s:%d", client->config->scheme, client->config->host, client->config->port);
|
||||
|
||||
client->state = WEBSOCKET_STATE_CONNECTED;
|
||||
client->wait_for_pong_resp = false;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CONNECTED, NULL, 0);
|
||||
|
||||
break;
|
||||
case WEBSOCKET_STATE_CONNECTED:
|
||||
if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) { // only send and check for PING
|
||||
// if closing hasn't been initiated
|
||||
if (_tick_get_ms() - client->ping_tick_ms > client->config->ping_interval_sec*1000) {
|
||||
client->ping_tick_ms = _tick_get_ms();
|
||||
ESP_LOGD(TAG, "Sending PING...");
|
||||
esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms);
|
||||
|
||||
if (!client->wait_for_pong_resp && client->config->pingpong_timeout_sec) {
|
||||
client->pingpong_tick_ms = _tick_get_ms();
|
||||
client->wait_for_pong_resp = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( _tick_get_ms() - client->pingpong_tick_ms > client->config->pingpong_timeout_sec*1000 ) {
|
||||
if (client->wait_for_pong_resp) {
|
||||
ESP_LOGE(TAG, "Error, no PONG received for more than %d seconds after PING", client->config->pingpong_timeout_sec);
|
||||
esp_websocket_client_abort_connection(client);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (read_select == 0) {
|
||||
ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_read()...");
|
||||
break;
|
||||
}
|
||||
client->ping_tick_ms = _tick_get_ms();
|
||||
|
||||
if (esp_websocket_client_recv(client) == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Error receive data");
|
||||
esp_websocket_client_abort_connection(client);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case WEBSOCKET_STATE_WAIT_TIMEOUT:
|
||||
|
||||
if (!client->config->auto_reconnect) {
|
||||
client->run = false;
|
||||
break;
|
||||
}
|
||||
if (_tick_get_ms() - client->reconnect_tick_ms > client->wait_timeout_ms) {
|
||||
client->state = WEBSOCKET_STATE_INIT;
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
ESP_LOGD(TAG, "Reconnecting...");
|
||||
}
|
||||
break;
|
||||
case WEBSOCKET_STATE_CLOSING:
|
||||
// if closing not initiated by the client echo the close message back
|
||||
if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) {
|
||||
ESP_LOGD(TAG, "Closing initiated by the server, sending close frame");
|
||||
esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_CLOSE | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms);
|
||||
xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGD(TAG, "Client run iteration in a default state: %d", client->state);
|
||||
break;
|
||||
}
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
if (WEBSOCKET_STATE_CONNECTED == client->state) {
|
||||
read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms
|
||||
if (read_select < 0) {
|
||||
ESP_LOGE(TAG, "Network error: esp_transport_poll_read() returned %d, errno=%d", read_select, errno);
|
||||
esp_websocket_client_abort_connection(client);
|
||||
}
|
||||
} else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) {
|
||||
// waiting for reconnecting...
|
||||
vTaskDelay(client->wait_timeout_ms / 2 / portTICK_PERIOD_MS);
|
||||
} else if (WEBSOCKET_STATE_CLOSING == client->state &&
|
||||
(CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits))) {
|
||||
ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server");
|
||||
int ret = esp_transport_ws_poll_connection_closed(client->transport, 1000);
|
||||
if (ret == 0) {
|
||||
// still waiting
|
||||
break;
|
||||
}
|
||||
if (ret < 0) {
|
||||
ESP_LOGW(TAG, "Connection terminated while waiting for clean TCP close");
|
||||
}
|
||||
client->run = false;
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CLOSED, NULL, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_transport_close(client->transport);
|
||||
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (client->state >= WEBSOCKET_STATE_INIT) {
|
||||
ESP_LOGE(TAG, "The client has started");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (xTaskCreate(esp_websocket_client_task, "websocket_task", client->config->task_stack, client, client->config->task_prio, &client->task_handle) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "Error create websocket task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!client->run) {
|
||||
ESP_LOGW(TAG, "Client was not started");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* A running client cannot be stopped from the websocket task/event handler */
|
||||
TaskHandle_t running_task = xTaskGetCurrentTaskHandle();
|
||||
if (running_task == client->task_handle) {
|
||||
ESP_LOGE(TAG, "Client cannot be stopped from websocket task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
|
||||
client->run = false;
|
||||
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout)
|
||||
{
|
||||
uint8_t *close_status_data = NULL;
|
||||
// RFC6455#section-5.5.1: The Close frame MAY contain a body (indicated by total_len >= 2)
|
||||
if (total_len >= 2) {
|
||||
close_status_data = calloc(1, total_len);
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, close_status_data, return -1);
|
||||
// RFC6455#section-5.5.1: The first two bytes of the body MUST be a 2-byte representing a status
|
||||
uint16_t *code_network_order = (uint16_t *) close_status_data;
|
||||
*code_network_order = htons(code);
|
||||
memcpy(close_status_data + 2, additional_data, total_len - 2);
|
||||
}
|
||||
int ret = esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_CLOSE, close_status_data, total_len, timeout);
|
||||
free(close_status_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t esp_websocket_client_close_with_optional_body(esp_websocket_client_handle_t client, bool send_body, int code, const char *data, int len, TickType_t timeout)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!client->run) {
|
||||
ESP_LOGW(TAG, "Client was not started");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* A running client cannot be stopped from the websocket task/event handler */
|
||||
TaskHandle_t running_task = xTaskGetCurrentTaskHandle();
|
||||
if (running_task == client->task_handle) {
|
||||
ESP_LOGE(TAG, "Client cannot be stopped from websocket task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (send_body) {
|
||||
esp_websocket_client_send_close(client, code, data, len + 2, portMAX_DELAY); // len + 2 -> always sending the code
|
||||
} else {
|
||||
esp_websocket_client_send_close(client, 0, NULL, 0, portMAX_DELAY); // only opcode frame
|
||||
}
|
||||
|
||||
// Set closing bit to prevent from sending PING frames while connected
|
||||
xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT);
|
||||
|
||||
if (STOPPED_BIT & xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, timeout)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// If could not close gracefully within timeout, stop the client and disconnect
|
||||
client->run = false;
|
||||
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_close_with_code(esp_websocket_client_handle_t client, int code, const char *data, int len, TickType_t timeout)
|
||||
{
|
||||
return esp_websocket_client_close_with_optional_body(client, true, code, data, len, timeout);
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickType_t timeout)
|
||||
{
|
||||
return esp_websocket_client_close_with_optional_body(client, false, 0, NULL, 0, timeout);
|
||||
}
|
||||
|
||||
int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
|
||||
{
|
||||
return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, (const uint8_t *)data, len, timeout);
|
||||
}
|
||||
|
||||
int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
|
||||
{
|
||||
return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout);
|
||||
}
|
||||
|
||||
static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const uint8_t *data, int len, TickType_t timeout)
|
||||
{
|
||||
int need_write = len;
|
||||
int wlen = 0, widx = 0;
|
||||
int ret = ESP_FAIL;
|
||||
|
||||
if (client == NULL || len < 0 ||
|
||||
(opcode != WS_TRANSPORT_OPCODES_CLOSE && (data == NULL || len <= 0))) {
|
||||
ESP_LOGE(TAG, "Invalid arguments");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (xSemaphoreTakeRecursive(client->lock, timeout) != pdPASS) {
|
||||
ESP_LOGE(TAG, "Could not lock ws-client within %d timeout", timeout);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (!esp_websocket_client_is_connected(client)) {
|
||||
ESP_LOGE(TAG, "Websocket client is not connected");
|
||||
goto unlock_and_return;
|
||||
}
|
||||
|
||||
if (client->transport == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid transport");
|
||||
goto unlock_and_return;
|
||||
}
|
||||
uint32_t current_opcode = opcode;
|
||||
while (widx < len || current_opcode) { // allow for sending "current_opcode" only message with len==0
|
||||
if (need_write > client->buffer_size) {
|
||||
need_write = client->buffer_size;
|
||||
} else {
|
||||
current_opcode |= WS_TRANSPORT_OPCODES_FIN;
|
||||
}
|
||||
memcpy(client->tx_buffer, data + widx, need_write);
|
||||
// send with ws specific way and specific opcode
|
||||
wlen = esp_transport_ws_send_raw(client->transport, current_opcode, (char *)client->tx_buffer, need_write,
|
||||
(timeout==portMAX_DELAY)? -1 : timeout * portTICK_PERIOD_MS);
|
||||
if (wlen < 0 || (wlen == 0 && need_write != 0)) {
|
||||
ret = wlen;
|
||||
ESP_LOGE(TAG, "Network error: esp_transport_write() returned %d, errno=%d", ret, errno);
|
||||
esp_websocket_client_abort_connection(client);
|
||||
goto unlock_and_return;
|
||||
}
|
||||
current_opcode = 0;
|
||||
widx += wlen;
|
||||
need_write = len - widx;
|
||||
|
||||
}
|
||||
ret = widx;
|
||||
unlock_and_return:
|
||||
xSemaphoreGiveRecursive(client->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return false;
|
||||
}
|
||||
return client->state == WEBSOCKET_STATE_CONNECTED;
|
||||
}
|
||||
|
||||
esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client,
|
||||
esp_websocket_event_id_t event,
|
||||
esp_event_handler_t event_handler,
|
||||
void *event_handler_arg)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return esp_event_handler_register_with(client->event_handle, WEBSOCKET_EVENTS, event, event_handler, event_handler_arg);
|
||||
}
|
10
components/esp_websocket_client/examples/CMakeLists.txt
Normal file
10
components/esp_websocket_client/examples/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../..")
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(websocket_example)
|
89
components/esp_websocket_client/examples/README.md
Normal file
89
components/esp_websocket_client/examples/README.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Websocket Sample application
|
||||
|
||||
This example will shows how to set up and communicate over a websocket.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example can be executed on any ESP32 board, the only required interface is WiFi and connection to internet or a local server.
|
||||
|
||||
### Configure the project
|
||||
|
||||
* Open the project configuration menu (`idf.py menuconfig`)
|
||||
* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu.
|
||||
* Configure the websocket endpoint URI under "Example Configuration", if "WEBSOCKET_URI_FROM_STDIN" is selected then the example application will connect to the URI it reads from stdin (used for testing)
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (482) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (2492) example_connect: Ethernet Link Up
|
||||
I (4472) tcpip_adapter: eth ip: 192.168.2.137, mask: 255.255.255.0, gw: 192.168.2.2
|
||||
I (4472) example_connect: Connected to Ethernet
|
||||
I (4472) example_connect: IPv4 address: 192.168.2.137
|
||||
I (4472) example_connect: IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed4:a92b
|
||||
I (4482) WEBSOCKET: Connecting to ws://echo.websocket.events...
|
||||
I (5012) WEBSOCKET: WEBSOCKET_EVENT_CONNECTED
|
||||
I (5492) WEBSOCKET: Sending hello 0000
|
||||
I (6052) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (6052) WEBSOCKET: Received=hello 0000
|
||||
|
||||
I (6492) WEBSOCKET: Sending hello 0001
|
||||
I (7052) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (7052) WEBSOCKET: Received=hello 0001
|
||||
|
||||
I (7492) WEBSOCKET: Sending hello 0002
|
||||
I (8082) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (8082) WEBSOCKET: Received=hello 0002
|
||||
|
||||
I (8492) WEBSOCKET: Sending hello 0003
|
||||
I (9152) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (9162) WEBSOCKET: Received=hello 0003
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Python Flask echo server
|
||||
|
||||
By default, the `ws://echo.websocket.events` endpoint is used. You can setup a Python websocket echo server locally and try the `ws://<your-ip>:5000` endpoint. To do this, install Flask-sock Python package
|
||||
|
||||
```
|
||||
pip install flask-sock
|
||||
```
|
||||
|
||||
and start a Flask websocket echo server locally by executing the following Python code:
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
from flask_sock import Sock
|
||||
|
||||
app = Flask(__name__)
|
||||
sock = Sock(app)
|
||||
|
||||
|
||||
@sock.route('/')
|
||||
def echo(ws):
|
||||
while True:
|
||||
data = ws.receive()
|
||||
ws.send(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# To run your Flask + WebSocket server in production you can use Gunicorn:
|
||||
# gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app
|
||||
app.run(host="0.0.0.0", debug=True)
|
||||
```
|
||||
|
146
components/esp_websocket_client/examples/example_test.py
Normal file
146
components/esp_websocket_client/examples/example_test.py
Normal file
@ -0,0 +1,146 @@
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import socket
|
||||
import string
|
||||
from threading import Event, Thread
|
||||
|
||||
import ttfw_idf
|
||||
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
|
||||
from tiny_test_fw import Utility
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(('10.255.255.255', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
IP = '127.0.0.1'
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
|
||||
class TestEcho(WebSocket):
|
||||
|
||||
def handleMessage(self):
|
||||
self.sendMessage(self.data)
|
||||
print('Server sent: {}'.format(self.data))
|
||||
|
||||
def handleConnected(self):
|
||||
print('Connection from: {}'.format(self.address))
|
||||
|
||||
def handleClose(self):
|
||||
print('{} closed the connection'.format(self.address))
|
||||
|
||||
|
||||
# Simple Websocket server for testing purposes
|
||||
class Websocket(object):
|
||||
|
||||
def send_data(self, data):
|
||||
for nr, conn in self.server.connections.items():
|
||||
conn.sendMessage(data)
|
||||
|
||||
def run(self):
|
||||
self.server = SimpleWebSocketServer('', self.port, TestEcho)
|
||||
while not self.exit_event.is_set():
|
||||
self.server.serveonce()
|
||||
|
||||
def __init__(self, port):
|
||||
self.port = port
|
||||
self.exit_event = Event()
|
||||
self.thread = Thread(target=self.run)
|
||||
self.thread.start()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.exit_event.set()
|
||||
self.thread.join(10)
|
||||
if self.thread.is_alive():
|
||||
Utility.console_log('Thread cannot be joined', 'orange')
|
||||
|
||||
|
||||
def test_echo(dut):
|
||||
dut.expect('WEBSOCKET_EVENT_CONNECTED')
|
||||
for i in range(0, 5):
|
||||
dut.expect(re.compile(r'Received=hello (\d)'), timeout=30)
|
||||
print('All echos received')
|
||||
|
||||
|
||||
def test_close(dut):
|
||||
code = dut.expect(re.compile(r'WEBSOCKET: Received closed message with code=(\d*)'), timeout=60)[0]
|
||||
print('Received close frame with code {}'.format(code))
|
||||
|
||||
|
||||
def test_recv_long_msg(dut, websocket, msg_len, repeats):
|
||||
send_msg = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(msg_len))
|
||||
|
||||
for _ in range(repeats):
|
||||
websocket.send_data(send_msg)
|
||||
|
||||
recv_msg = ''
|
||||
while len(recv_msg) < msg_len:
|
||||
# Filter out color encoding
|
||||
match = dut.expect(re.compile(r'Received=([a-zA-Z0-9]*).*\n'), timeout=30)[0]
|
||||
recv_msg += match
|
||||
|
||||
if recv_msg == send_msg:
|
||||
print('Sent message and received message are equal')
|
||||
else:
|
||||
raise ValueError('DUT received string do not match sent string, \nexpected: {}\nwith length {}\
|
||||
\nreceived: {}\nwith length {}'.format(send_msg, len(send_msg), recv_msg, len(recv_msg)))
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
|
||||
def test_examples_protocol_websocket(env, extra_data):
|
||||
"""
|
||||
steps:
|
||||
1. obtain IP address
|
||||
2. connect to uri specified in the config
|
||||
3. send and receive data
|
||||
"""
|
||||
dut1 = env.get_dut('websocket', 'examples/protocols/websocket', dut_class=ttfw_idf.ESP32DUT)
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, 'websocket_example.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance('websocket_bin_size', '{}KB'.format(bin_size // 1024))
|
||||
|
||||
try:
|
||||
if 'CONFIG_WEBSOCKET_URI_FROM_STDIN' in dut1.app.get_sdkconfig():
|
||||
uri_from_stdin = True
|
||||
else:
|
||||
uri = dut1.app.get_sdkconfig()['CONFIG_WEBSOCKET_URI'].strip('"')
|
||||
uri_from_stdin = False
|
||||
|
||||
except Exception:
|
||||
print('ENV_TEST_FAILURE: Cannot find uri settings in sdkconfig')
|
||||
raise
|
||||
|
||||
# start test
|
||||
dut1.start_app()
|
||||
|
||||
if uri_from_stdin:
|
||||
server_port = 4455
|
||||
with Websocket(server_port) as ws:
|
||||
uri = 'ws://{}:{}'.format(get_my_ip(), server_port)
|
||||
print('DUT connecting to {}'.format(uri))
|
||||
dut1.expect('Please enter uri of websocket endpoint', timeout=30)
|
||||
dut1.write(uri)
|
||||
test_echo(dut1)
|
||||
# Message length should exceed DUT's buffer size to test fragmentation, default is 1024 byte
|
||||
test_recv_long_msg(dut1, ws, 2000, 3)
|
||||
test_close(dut1)
|
||||
|
||||
else:
|
||||
print('DUT connecting to {}'.format(uri))
|
||||
test_echo(dut1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_websocket()
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "websocket_example.c"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,23 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice WEBSOCKET_URI_SOURCE
|
||||
prompt "Websocket URI source"
|
||||
default WEBSOCKET_URI_FROM_STRING
|
||||
help
|
||||
Selects the source of the URI used in the example.
|
||||
|
||||
config WEBSOCKET_URI_FROM_STRING
|
||||
bool "From string"
|
||||
|
||||
config WEBSOCKET_URI_FROM_STDIN
|
||||
bool "From stdin"
|
||||
endchoice
|
||||
|
||||
config WEBSOCKET_URI
|
||||
string "Websocket endpoint URI"
|
||||
depends on WEBSOCKET_URI_FROM_STRING
|
||||
default "ws://echo.websocket.events"
|
||||
help
|
||||
URL of websocket endpoint this example connects to and sends echo
|
||||
|
||||
endmenu
|
@ -0,0 +1,154 @@
|
||||
/* ESP HTTP Client Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_event.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_websocket_client.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#define NO_DATA_TIMEOUT_SEC 5
|
||||
|
||||
static const char *TAG = "WEBSOCKET";
|
||||
|
||||
static TimerHandle_t shutdown_signal_timer;
|
||||
static SemaphoreHandle_t shutdown_sema;
|
||||
|
||||
static void shutdown_signaler(TimerHandle_t xTimer)
|
||||
{
|
||||
ESP_LOGI(TAG, "No data received for %d seconds, signaling shutdown", NO_DATA_TIMEOUT_SEC);
|
||||
xSemaphoreGive(shutdown_sema);
|
||||
}
|
||||
|
||||
#if CONFIG_WEBSOCKET_URI_FROM_STDIN
|
||||
static void get_string(char *line, size_t size)
|
||||
{
|
||||
int count = 0;
|
||||
while (count < size) {
|
||||
int c = fgetc(stdin);
|
||||
if (c == '\n') {
|
||||
line[count] = '\0';
|
||||
break;
|
||||
} else if (c > 0 && c < 127) {
|
||||
line[count] = c;
|
||||
++count;
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */
|
||||
|
||||
static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
|
||||
switch (event_id) {
|
||||
case WEBSOCKET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DATA:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
|
||||
ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
|
||||
if (data->op_code == 0x08 && data->data_len == 2) {
|
||||
ESP_LOGW(TAG, "Received closed message with code=%d", 256*data->data_ptr[0] + data->data_ptr[1]);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr);
|
||||
}
|
||||
ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);
|
||||
|
||||
xTimerReset(shutdown_signal_timer, portMAX_DELAY);
|
||||
break;
|
||||
case WEBSOCKET_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void websocket_app_start(void)
|
||||
{
|
||||
esp_websocket_client_config_t websocket_cfg = {};
|
||||
|
||||
shutdown_signal_timer = xTimerCreate("Websocket shutdown timer", NO_DATA_TIMEOUT_SEC * 1000 / portTICK_PERIOD_MS,
|
||||
pdFALSE, NULL, shutdown_signaler);
|
||||
shutdown_sema = xSemaphoreCreateBinary();
|
||||
|
||||
#if CONFIG_WEBSOCKET_URI_FROM_STDIN
|
||||
char line[128];
|
||||
|
||||
ESP_LOGI(TAG, "Please enter uri of websocket endpoint");
|
||||
get_string(line, sizeof(line));
|
||||
|
||||
websocket_cfg.uri = line;
|
||||
ESP_LOGI(TAG, "Endpoint uri: %s\n", line);
|
||||
|
||||
#else
|
||||
websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
|
||||
|
||||
#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
|
||||
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
|
||||
|
||||
esp_websocket_client_start(client);
|
||||
xTimerStart(shutdown_signal_timer, portMAX_DELAY);
|
||||
char data[32];
|
||||
int i = 0;
|
||||
while (i < 5) {
|
||||
if (esp_websocket_client_is_connected(client)) {
|
||||
int len = sprintf(data, "hello %04d", i++);
|
||||
ESP_LOGI(TAG, "Sending %s", data);
|
||||
esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
xSemaphoreTake(shutdown_sema, portMAX_DELAY);
|
||||
esp_websocket_client_close(client, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Websocket Stopped");
|
||||
esp_websocket_client_destroy(client);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "[APP] Startup..");
|
||||
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
|
||||
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
|
||||
esp_log_level_set("*", ESP_LOG_INFO);
|
||||
esp_log_level_set("WEBSOCKET_CLIENT", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("TRANSPORT_WS", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("TRANS_TCP", ESP_LOG_DEBUG);
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
websocket_app_start();
|
||||
}
|
11
components/esp_websocket_client/examples/sdkconfig.ci
Normal file
11
components/esp_websocket_client/examples/sdkconfig.ci
Normal file
@ -0,0 +1,11 @@
|
||||
CONFIG_WEBSOCKET_URI_FROM_STDIN=y
|
||||
CONFIG_WEBSOCKET_URI_FROM_STRING=n
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_IP101=y
|
||||
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=y
|
5
components/esp_websocket_client/idf_component.yml
Normal file
5
components/esp_websocket_client/idf_component.yml
Normal file
@ -0,0 +1,5 @@
|
||||
version: "0.0.1"
|
||||
description: esp websocket client
|
||||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
259
components/esp_websocket_client/include/esp_websocket_client.h
Normal file
259
components/esp_websocket_client/include/esp_websocket_client.h
Normal file
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef _ESP_WEBSOCKET_CLIENT_H_
|
||||
#define _ESP_WEBSOCKET_CLIENT_H_
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include <sys/socket.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct esp_websocket_client *esp_websocket_client_handle_t;
|
||||
|
||||
ESP_EVENT_DECLARE_BASE(WEBSOCKET_EVENTS); // declaration of the task events family
|
||||
|
||||
/**
|
||||
* @brief Websocket Client events id
|
||||
*/
|
||||
typedef enum {
|
||||
WEBSOCKET_EVENT_ANY = -1,
|
||||
WEBSOCKET_EVENT_ERROR = 0, /*!< This event occurs when there are any errors during execution */
|
||||
WEBSOCKET_EVENT_CONNECTED, /*!< Once the Websocket has been connected to the server, no data exchange has been performed */
|
||||
WEBSOCKET_EVENT_DISCONNECTED, /*!< The connection has been disconnected */
|
||||
WEBSOCKET_EVENT_DATA, /*!< When receiving data from the server, possibly multiple portions of the packet */
|
||||
WEBSOCKET_EVENT_CLOSED, /*!< The connection has been closed cleanly */
|
||||
WEBSOCKET_EVENT_MAX
|
||||
} esp_websocket_event_id_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket event data
|
||||
*/
|
||||
typedef struct {
|
||||
const char *data_ptr; /*!< Data pointer */
|
||||
int data_len; /*!< Data length */
|
||||
uint8_t op_code; /*!< Received opcode */
|
||||
esp_websocket_client_handle_t client; /*!< esp_websocket_client_handle_t context */
|
||||
void *user_context; /*!< user_data context, from esp_websocket_client_config_t user_data */
|
||||
int payload_len; /*!< Total payload length, payloads exceeding buffer will be posted through multiple events */
|
||||
int payload_offset; /*!< Actual offset for the data associated with this event */
|
||||
} esp_websocket_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket Client transport
|
||||
*/
|
||||
typedef enum {
|
||||
WEBSOCKET_TRANSPORT_UNKNOWN = 0x0, /*!< Transport unknown */
|
||||
WEBSOCKET_TRANSPORT_OVER_TCP, /*!< Transport over tcp */
|
||||
WEBSOCKET_TRANSPORT_OVER_SSL, /*!< Transport over ssl */
|
||||
} esp_websocket_transport_t;
|
||||
|
||||
/**
|
||||
* @brief Websocket client setup configuration
|
||||
*/
|
||||
typedef struct {
|
||||
const char *uri; /*!< Websocket URI, the information on the URI can be overrides the other fields below, if any */
|
||||
const char *host; /*!< Domain or IP as string */
|
||||
int port; /*!< Port to connect, default depend on esp_websocket_transport_t (80 or 443) */
|
||||
const char *username; /*!< Using for Http authentication - Not supported for now */
|
||||
const char *password; /*!< Using for Http authentication - Not supported for now */
|
||||
const char *path; /*!< HTTP Path, if not set, default is `/` */
|
||||
bool disable_auto_reconnect; /*!< Disable the automatic reconnect function when disconnected */
|
||||
void *user_context; /*!< HTTP user data context */
|
||||
int task_prio; /*!< Websocket task priority */
|
||||
int task_stack; /*!< Websocket task stack */
|
||||
int buffer_size; /*!< Websocket buffer size */
|
||||
const char *cert_pem; /*!< Pointer to certificate data in PEM or DER format for server verify (with SSL), default is NULL, not required to verify the server. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in cert_len. */
|
||||
size_t cert_len; /*!< Length of the buffer pointed to by cert_pem. May be 0 for null-terminated pem */
|
||||
const char *client_cert; /*!< Pointer to certificate data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_key` has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_cert_len. */
|
||||
size_t client_cert_len; /*!< Length of the buffer pointed to by client_cert. May be 0 for null-terminated pem */
|
||||
const char *client_key; /*!< Pointer to private key data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_cert` has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_key_len */
|
||||
size_t client_key_len; /*!< Length of the buffer pointed to by client_key_pem. May be 0 for null-terminated pem */
|
||||
esp_websocket_transport_t transport; /*!< Websocket transport type, see `esp_websocket_transport_t */
|
||||
const char *subprotocol; /*!< Websocket subprotocol */
|
||||
const char *user_agent; /*!< Websocket user-agent */
|
||||
const char *headers; /*!< Websocket additional headers */
|
||||
int pingpong_timeout_sec; /*!< Period before connection is aborted due to no PONGs received */
|
||||
bool disable_pingpong_discon; /*!< Disable auto-disconnect due to no PONG received within pingpong_timeout_sec */
|
||||
bool use_global_ca_store; /*!< Use a global ca_store for all the connections in which this bool is set. */
|
||||
bool skip_cert_common_name_check;/*!< Skip any validation of server certificate CN field */
|
||||
bool keep_alive_enable; /*!< Enable keep-alive timeout */
|
||||
int keep_alive_idle; /*!< Keep-alive idle time. Default is 5 (second) */
|
||||
int keep_alive_interval; /*!< Keep-alive interval time. Default is 5 (second) */
|
||||
int keep_alive_count; /*!< Keep-alive packet retry send count. Default is 3 counts */
|
||||
int reconnect_timeout_ms; /*!< Reconnect after this value in miliseconds if disable_auto_reconnect is not enabled (defaults to 10s) */
|
||||
int network_timeout_ms; /*!< Abort network operation if it is not completed after this value, in milliseconds (defaults to 10s) */
|
||||
size_t ping_interval_sec; /*!< Websocket ping interval, defaults to 10 seconds if not set */
|
||||
struct ifreq *if_name; /*!< The name of interface for data to go through. Use the default interface without setting */
|
||||
} esp_websocket_client_config_t;
|
||||
|
||||
/**
|
||||
* @brief Start a Websocket session
|
||||
* This function must be the first function to call,
|
||||
* and it returns a esp_websocket_client_handle_t that you must use as input to other functions in the interface.
|
||||
* This call MUST have a corresponding call to esp_websocket_client_destroy when the operation is complete.
|
||||
*
|
||||
* @param[in] config The configuration
|
||||
*
|
||||
* @return
|
||||
* - `esp_websocket_client_handle_t`
|
||||
* - NULL if any errors
|
||||
*/
|
||||
esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Set URL for client, when performing this behavior, the options in the URL will replace the old ones
|
||||
* Must stop the WebSocket client before set URI if the client has been connected
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] uri The uri
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, const char *uri);
|
||||
|
||||
/**
|
||||
* @brief Open the WebSocket connection
|
||||
*
|
||||
* @param[in] client The client
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client);
|
||||
|
||||
/**
|
||||
* @brief Stops the WebSocket connection without websocket closing handshake
|
||||
*
|
||||
* This API stops ws client and closes TCP connection directly without sending
|
||||
* close frames. It is a good practice to close the connection in a clean way
|
||||
* using esp_websocket_client_close().
|
||||
*
|
||||
* Notes:
|
||||
* - Cannot be called from the websocket event handler
|
||||
*
|
||||
* @param[in] client The client
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client);
|
||||
|
||||
/**
|
||||
* @brief Destroy the WebSocket connection and free all resources.
|
||||
* This function must be the last function to call for an session.
|
||||
* It is the opposite of the esp_websocket_client_init function and must be called with the same handle as input that a esp_websocket_client_init call returned.
|
||||
* This might close all connections this handle has used.
|
||||
*
|
||||
* Notes:
|
||||
* - Cannot be called from the websocket event handler
|
||||
*
|
||||
* @param[in] client The client
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client);
|
||||
|
||||
/**
|
||||
* @brief Write binary data to the WebSocket connection (data send with WS OPCODE=02, i.e. binary)
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] data The data
|
||||
* @param[in] len The length
|
||||
* @param[in] timeout Write data timeout in RTOS ticks
|
||||
*
|
||||
* @return
|
||||
* - Number of data was sent
|
||||
* - (-1) if any errors
|
||||
*/
|
||||
int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Write textual data to the WebSocket connection (data send with WS OPCODE=01, i.e. text)
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] data The data
|
||||
* @param[in] len The length
|
||||
* @param[in] timeout Write data timeout in RTOS ticks
|
||||
*
|
||||
* @return
|
||||
* - Number of data was sent
|
||||
* - (-1) if any errors
|
||||
*/
|
||||
int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Close the WebSocket connection in a clean way
|
||||
*
|
||||
* Sequence of clean close initiated by client:
|
||||
* * Client sends CLOSE frame
|
||||
* * Client waits until server echos the CLOSE frame
|
||||
* * Client waits until server closes the connection
|
||||
* * Client is stopped the same way as by the `esp_websocket_client_stop()`
|
||||
*
|
||||
* Notes:
|
||||
* - Cannot be called from the websocket event handler
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] timeout Timeout in RTOS ticks for waiting
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickType_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Close the WebSocket connection in a clean way with custom code/data
|
||||
* Closing sequence is the same as for esp_websocket_client_close()
|
||||
*
|
||||
* Notes:
|
||||
* - Cannot be called from the websocket event handler
|
||||
*
|
||||
* @param[in] client The client
|
||||
* @param[in] code Close status code as defined in RFC6455 section-7.4
|
||||
* @param[in] data Additional data to closing message
|
||||
* @param[in] len The length of the additional data
|
||||
* @param[in] timeout Timeout in RTOS ticks for waiting
|
||||
*
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_client_close_with_code(esp_websocket_client_handle_t client, int code, const char *data, int len, TickType_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Check the WebSocket client connection state
|
||||
*
|
||||
* @param[in] client The client handle
|
||||
*
|
||||
* @return
|
||||
* - true
|
||||
* - false
|
||||
*/
|
||||
bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client);
|
||||
|
||||
/**
|
||||
* @brief Register the Websocket Events
|
||||
*
|
||||
* @param client The client handle
|
||||
* @param event The event id
|
||||
* @param event_handler The callback function
|
||||
* @param event_handler_arg User context
|
||||
* @return esp_err_t
|
||||
*/
|
||||
esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client,
|
||||
esp_websocket_event_id_t event,
|
||||
esp_event_handler_t event_handler,
|
||||
void *event_handler_arg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
2
components/esp_websocket_client/test/CMakeLists.txt
Normal file
2
components/esp_websocket_client/test/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRC_DIRS "."
|
||||
PRIV_REQUIRES cmock test_utils esp_websocket_client)
|
56
components/esp_websocket_client/test/test_websocket_client.c
Normal file
56
components/esp_websocket_client/test/test_websocket_client.c
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*
|
||||
* This test code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, this
|
||||
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
* CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <esp_websocket_client.h>
|
||||
|
||||
#include "unity.h"
|
||||
#include "memory_checks.h"
|
||||
|
||||
static void test_leak_setup(const char * file, long line)
|
||||
{
|
||||
printf("%s:%ld\n", file, line);
|
||||
test_utils_record_free_mem();
|
||||
}
|
||||
|
||||
TEST_CASE("websocket init and deinit", "[websocket][leaks=0]")
|
||||
{
|
||||
test_leak_setup(__FILE__, __LINE__);
|
||||
const esp_websocket_client_config_t websocket_cfg = {
|
||||
// no connection takes place, but the uri has to be valid for init() to succeed
|
||||
.uri = "ws://echo.websocket.org",
|
||||
};
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
TEST_ASSERT_NOT_EQUAL(NULL, client);
|
||||
esp_websocket_client_destroy(client);
|
||||
}
|
||||
|
||||
TEST_CASE("websocket init with invalid url", "[websocket][leaks=0]")
|
||||
{
|
||||
test_leak_setup(__FILE__, __LINE__);
|
||||
const esp_websocket_client_config_t websocket_cfg = {
|
||||
.uri = "INVALID",
|
||||
};
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
TEST_ASSERT_NULL(client);
|
||||
}
|
||||
|
||||
TEST_CASE("websocket set url with invalid url", "[websocket][leaks=0]")
|
||||
{
|
||||
test_leak_setup(__FILE__, __LINE__);
|
||||
const esp_websocket_client_config_t websocket_cfg = {};
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
TEST_ASSERT_NOT_EQUAL(NULL, client);
|
||||
TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_websocket_client_set_uri(client, "INVALID"));
|
||||
esp_websocket_client_destroy(client);
|
||||
}
|
Reference in New Issue
Block a user