Compare commits

...

98 Commits

Author SHA1 Message Date
80c3cf0f02 websocket: Initial version based on IDF 5.0 2022-03-02 15:48:43 +04:00
b3c777ad43 freertos: Remove legacy data types
This commit removes the usage of all legacy FreeRTOS data types that
are exposed via configENABLE_BACKWARD_COMPATIBILITY. Legacy types can
still be used by enabling CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY.


* Original commit: espressif/esp-idf@57fd78f5ba
2022-03-01 12:18:50 +04:00
8ce791e969 websocket: Added configs reconnect_timeout_ms and network_timeout_ms
Closes https://github.com/espressif/esp-idf/issues/8263


* Original commit: espressif/esp-idf@6c26d65203
2022-03-01 12:18:50 +04:00
fc7ed90d74 websocket: Updated Kconfig to use 'echo.websocket.events' echo server
Updated README to show how to run websocket echo server using Flask

Merges https://github.com/espressif/esp-idf/pull/8262


* Original commit: espressif/esp-idf@23598dfcec
2022-03-01 12:18:50 +04:00
59e82695e7 ws_client: Optimize example test payloads and timeouts
Important update: NO_DATA_TIMEOUT_SEC=5, as some ws-servers typically send pings in 30s or 10s intervals, so we might never fire shutdown test


* Original commit: espressif/esp-idf@323622be64
2022-03-01 12:18:50 +04:00
755f16222f ci/websockets: Run ws-client example test on ethernet runners
* Original commit: espressif/esp-idf@2649413ae8
2022-03-01 12:18:50 +04:00
bece6e7045 Add http_parser (new component) dependency
* Original commit: espressif/esp-idf@8e94cf2bb1
2022-03-01 12:18:50 +04:00
46bd32d952 websocket: removed deprecated API "esp_websocket_client_send"
Closes IDF-1470


* Original commit: espressif/esp-idf@7f6ab93f7e
2022-03-01 12:18:50 +04:00
525c70c0b2 refactor (test_utils)!: separate file for memory check functions
Memory check (leaks and heap tracing) functions for unit tests
now have a separate file now and are renamed for more consistency.

BREAKING CHANGE: renamed memory check function names which may be used
                 in unit tests outside IDF.


* Original commit: espressif/esp-idf@16514f93f0
2022-03-01 12:18:50 +04:00
c4c323666e docs: Fix spell and grammatical errors
- PCNT
- ESP-TLS
- ESP WebSocket Client


* Original commit: espressif/esp-idf@1df7f340be
2022-03-01 12:18:50 +04:00
19c0455b4d Build & config: Remove leftover files from the unsupported "make" build system
* Original commit: espressif/esp-idf@766aa57084
2022-03-01 12:18:50 +04:00
665c520faf [examples]: removed hyphens
Replaced hyphens with underscores in examples
project definition for all examples which had
hyphens in their project name. dpp-enrollee is
an exceptions because the name matches the
project directory name while the project
directory also contains hyphens.


* Original commit: espressif/esp-idf@81e9266204
2022-03-01 12:18:50 +04:00
9118e0f044 transport: Add CONFI_WS_TRANSPORT for optimize the code size
* Original commit: espressif/esp-idf@8b02c9026a
2022-03-01 12:18:50 +04:00
fbdbd550c0 ws_client: Fix const correctness in the API config structure
Merges https://github.com/espressif/esp-idf/pull/7113


* Original commit: espressif/esp-idf@70b1247a47
2022-03-01 12:18:50 +04:00
028be5a8d7 Split example_tests with Example_WIFI tag group into Example_OTA and Example_Protocols
* Original commit: espressif/esp-idf@0a395134d4
2022-03-01 12:18:50 +04:00
de7cd72f70 components: Remove repeated keep alive function by ssl layer function
In esp_http_client and esp_websocket_client components, esp_transport_tcp_set_keep_alive has been called and keep-alive config has been saved in ssl->cfg.keep_alive_cfg,
So no need to call esp_transport_ssl_set_keep_alive again.


* Original commit: espressif/esp-idf@c79a907e4f
2022-03-01 12:18:50 +04:00
4a608ec1cd components: Support bind socket to specified interface in esp_http_client and esp_websocket_client component
* Original commit: espressif/esp-idf@bead3599ab
2022-03-01 12:18:50 +04:00
f0351ff378 esp_websocket_client: Don't log the filename when logging "Websocket already stop"
Progress towards https://jira.espressif.com:8443/browse/IDFGH-4477


* Original commit: espressif/esp-idf@10bde42551
2022-03-01 12:18:50 +04:00
9219ff710a websocket: Add websocket unit tests
* Original commit: espressif/esp-idf@cd01a0ca81
2022-03-01 12:18:50 +04:00
86aa0b8d39 websockets: Set keepalive options after adding transport to the list
To be in line with other code and mainly to support base/foundation transport used by both tcp and ssl transport layers


* Original commit: espressif/esp-idf@99805d880f
2022-03-01 12:18:50 +04:00
1933367f63 websocket: Add configurable ping interval
Merges https://github.com/espressif/esp-idf/pull/6399

Signed-off-by: David Cermak <cermak@espressif.com>


* Original commit: espressif/esp-idf@9ff9137e7a
2022-03-01 12:18:50 +04:00
cf697a1a1b style: format python files with isort and double-quote-string-fixer
* Original commit: espressif/esp-idf@0146f258d7
2022-03-01 12:18:50 +04:00
95cf983502 ws_transport: Add option to propagate control packets to the app
Client could choose if they want to receive control packets and handle
them.
* If disabled (default) the transport itself tries to handle PING
and CLOSE frames automatically during read operation. If handled
correctly, read outputs 0 indicating no (actual app) data received.
* if enabled, all control frames are passed to the application to be
  processed there.

Closes https://github.com/espressif/esp-idf/issues/6307


* Original commit: espressif/esp-idf@acc7bd2ca4
2022-03-01 12:18:50 +04:00
8a6c320a29 Add options for esp_http_client and esp_websocket_client to support keepalive
* Original commit: espressif/esp-idf@b53e46a68e
2022-03-01 12:18:50 +04:00
d1dd6ece38 websocket: support mutual tls for websocket
Closes https://github.com/espressif/esp-idf/issues/6059


* Original commit: espressif/esp-idf@5ab774f9d8
2022-03-01 12:18:50 +04:00
36167db336 test: remove fake binary size check in example test:
the binary size check in example test was removed long time ago. Now we
have updated ttfw_idf to raise exception when performance standard is
not found. These fake performance check will be regarded as error.
Remove them now.


* Original commit: espressif/esp-idf@a908174c06
2022-03-01 12:18:50 +04:00
d376480766 Whitespace: Automated whitespace fixes (large commit)
Apply the pre-commit hook whitespace fixes to all files in the repo.

(Line endings, blank lines at end of file, trailing whitespace)


* Original commit: espressif/esp-idf@66fb5a29bb
2022-03-01 12:18:50 +04:00
e90272c812 Websocket client: avoid deadlock if stop called from event handler
* Original commit: espressif/esp-idf@c2bb0762bb
2022-03-01 12:18:50 +04:00
fda070ba39 ws_client tests: Updated example test to use WebsSocket package
Added a new test for closing connection with close frames


* Original commit: espressif/esp-idf@44c553fd14
2022-03-01 12:18:50 +04:00
6d12d06605 tcp_transport: Added internal API for underlying socket, used for custom select on connection end for WS
Internal tcp_transport functions could now use custom socket operations.
This is used for WebSocket transport, when we typically wait for clean
connection closure, i.e. selecting for read/error with expected errno or
recv size=0 while socket readable (=connection terminated by FIN flag)


* Original commit: espressif/esp-idf@5e9f8b52e7
2022-03-01 12:18:50 +04:00
1455bc0305 ws_client: Added support for close frame, closing connection gracefully
* Original commit: espressif/esp-idf@b213f2c6d3
2022-03-01 12:18:50 +04:00
01b4f640d9 driver, http_client, web_socket, tcp_transport: remove __FILE__ from log messages
__FILE__ macro in the error messages adds full paths to the production binarys, remove __FILE__ from the ESP_LOGE.

Closes https://github.com/espressif/esp-idf/issues/5637
Merges https://github.com/espressif/esp-idf/pull/5638


* Original commit: espressif/esp-idf@caaf62bdad
2022-03-01 12:18:50 +04:00
6ab0aea841 websocket_client : fix some issues for websocket client
1. will post twice disconnect event when read error
2. will block `timeout` times when set disable_auto_connect
3. When `esp_websocket_client_stop` before `esp_websocket_client_send*`,
if the `esp_websocket_client_send*` fails, the status will change to
 'WEBSOCKET_STATE_WAIT_TIMEOUT', and the next `esp_websocket_client_start` will fail forever


* Original commit: espressif/esp-idf@341e480573
2022-03-01 12:18:50 +04:00
5f2a50f09f doc/websocket: updates the API reference for ESP WebSocket Client
* Original commit: espressif/esp-idf@09f240c1e1
2022-03-01 12:18:50 +04:00
b71c49c277 websocket: add configurable timeout for PONG not received
Closes IDF-1744


* Original commit: espressif/esp-idf@0049385850
2022-03-01 12:18:50 +04:00
f8e3ba7813 websocket client: the client now aborts the connection if send fails.
Closes IDF-1744


* Original commit: espressif/esp-idf@6bebfc84f3
2022-03-01 12:18:50 +04:00
7a5b2d5a7d ws_client: fix fragmented send setting proper opcodes
Previous implementation violated the RFC by having both the actual opcode and WS_FIN flag set for all fragments of a message.
Fixed by setting the opcode only for the first fragment and WS_FIN for the last one

Closes IDFGH-2938
Closes https://github.com/espressif/esp-idf/issues/4974


* Original commit: espressif/esp-idf@14992e62c5
2022-03-01 12:18:50 +04:00
42920d7fb5 Fix format string in websocket example
* Original commit: espressif/esp-idf@5288a797ef
2022-03-01 12:18:50 +04:00
2b044f2434 Add multi-target support for performance tests
* Original commit: espressif/esp-idf@15884eccf2
2022-03-01 12:18:50 +04:00
2b6022c85d examples: websocket example to send textual data with esp_websocket_client_send_text()
Closes https://github.com/espressif/esp-idf/issues/4640


* Original commit: espressif/esp-idf@e5650d1ed8
2022-03-01 12:18:50 +04:00
fae2343b19 docs: add new top-level docs builder that builds docs for a single chip
* Original commit: espressif/esp-idf@e6211c7864
2022-03-01 12:18:50 +04:00
17281a515e esp32: add implementation of esp_timer based on TG0 LAC timer
Closes: IDF-979


* Original commit: espressif/esp-idf@739eb05bb9
2022-03-01 12:18:50 +04:00
aec6a75d40 tcp_transport/ws_client: websockets now correctly handle messages longer than buffer
transport_ws can now be read multiple times in a row to read frames larger than the buffer.

Added reporting of total payload length and offset to the user in websocket_client.

Added local example test for long messages.

Closes IDF-1083


* Original commit: espressif/esp-idf@ffeda3003c
2022-03-01 12:18:50 +04:00
a6be8e2e3d websocket: added missing event data
user_context was missing from websocket event data, added. Also added the websocket client handle to the event data.

Removed  unused event data struct.

Closes: IDF-1271


* Original commit: espressif/esp-idf@7c0e3765ec
2022-03-01 12:18:50 +04:00
09453e4694 esp_netif, examples: esp_netif_init() moved into ESP_ERROR_CHECK()
esp_netif_init() returns standard esp_err_t error code (unlike tcpip_adapter init), so shall be checked for the return value
Also to make the initialization code more consistent.


* Original commit: espressif/esp-idf@31b2702387
2022-03-01 12:18:50 +04:00
3b0488cfdc websocket_client: added example_test with a local websocket server
- Added a example test that connects to a local python websocket server.
- Added readme for websocket_client example.

Closes IDF-907


* Original commit: espressif/esp-idf@67c5225c14
2022-03-01 12:18:50 +04:00
f21a2f32e0 test: update example and unit tests with new import roles:
tiny_test_fw is a python package now. import it using normal way.


* Original commit: espressif/esp-idf@c906e2afee
2022-03-01 12:18:50 +04:00
a48b0fafe8 Add User-Agent and additional headers to esp_websocket_client
Merges https://github.com/espressif/esp-idf/pull/4345


* Original commit: espressif/esp-idf@9200250f51
2022-03-01 12:18:50 +04:00
1fcc001ae8 ws_client: fix handling timeouts by websocket client.
tcp-transport component did not support wait forever. this update uses value of -1 to request this state.
websocket client uses timeouts in RTOS ticks. fixed recalculation to ms (including special value of -1) to use correctly tcp-transport component

Closes https://github.com/espressif/esp-idf/issues/4316


* Original commit: espressif/esp-idf@e1f982921a
2022-03-01 12:18:50 +04:00
a41e3383b3 examples: protocol examples which use common connection component
updated to use esp_netif_init instead of tcpip_adapter in initialization code


* Original commit: espressif/esp-idf@a49b934ef8
2022-03-01 12:18:50 +04:00
d0121b964d websocket_client: fix locking mechanism in ws-client task and when sending data
closes https://github.com/espressif/esp-idf/issues/4169


* Original commit: espressif/esp-idf@7c5011f411
2022-03-01 12:18:50 +04:00
f55d8391c9 ws_client: fix for not sending ping responses, updated to pass events also for PING and PONG messages, added interfaces to send both binary and text data
closes https://github.com/espressif/esp-idf/issues/3982


* Original commit: espressif/esp-idf@abf9345b85
2022-03-01 12:18:50 +04:00
fe26b734b5 Cosmetic Kconfig fixes
* Original commit: espressif/esp-idf@d3ed17acd7
2022-03-01 12:18:50 +04:00
f5a26c4d32 websocket_client: fix URI parsing to include also query part in websocket connection path
closes https://github.com/espressif/esp-idf/issues/4090


* Original commit: espressif/esp-idf@271e6c4c9c
2022-03-01 12:18:50 +04:00
23f6a1d46e ws_client: fixed posting to event loop with websocket timeout
Executing event loop `esp_event_loop_run()` with timeout causes delays in receiving events from user code. Fixed by removing the timeout to post synchronously.

closes https://github.com/espressif/esp-idf/issues/3957


* Original commit: espressif/esp-idf@50505068c4
2022-03-01 12:18:50 +04:00
2553d65e64 ws_client: added subprotocol configuration option to websocket client
closes https://github.com/espressif/esp-idf/issues/3893


* Original commit: espressif/esp-idf@de6ea396f1
2022-03-01 12:18:50 +04:00
67949f94f4 ws_client: fixed path config issue when ws server configured using host and path instead of uri
closes https://github.com/espressif/esp-idf/issues/3892


* Original commit: espressif/esp-idf@c0ba9e19fc
2022-03-01 12:18:50 +04:00
bfc88ab76c ws_client: fixed transport config option when server address configured as host, port, transport rather then uri
closes https://github.com/espressif/esp-idf/issues/3891


* Original commit: espressif/esp-idf@adee25d90e
2022-03-01 12:18:50 +04:00
343fbfdcc9 ci: limit example test to ESP32s
* Original commit: espressif/esp-idf@63329b169b
2022-03-01 12:18:50 +04:00
4d644954fe esp_wifi: wifi support new event mechanism
1. WiFi support new event mechanism
2. Update examples to use new event mechanism


* Original commit: espressif/esp-idf@003a9872b7
2022-03-01 12:18:50 +04:00
da74a4a489 tools: Mass fixing of empty prototypes (for -Wstrict-prototypes)
* Original commit: espressif/esp-idf@afbaf74007
2022-03-01 12:18:50 +04:00
f718676083 ws_client: fix double delete issue in ws client initialization
* Original commit: espressif/esp-idf@9b507c45c8
2022-03-01 12:18:50 +04:00
13a40d2344 ws_client: removed dependency on internal tcp_transport header
* Original commit: espressif/esp-idf@d1433564ec
2022-03-01 12:18:50 +04:00
35d6f9a2c6 examples: use new component registration api
* Original commit: espressif/esp-idf@6771eead80
2022-03-01 12:18:50 +04:00
f3a0586663 esp_websocket_client: Add websocket client component
Closes https://github.com/espressif/esp-idf/issues/2829


* Original commit: espressif/esp-idf@2a2d932cfe
2022-03-01 12:18:50 +04:00
325a1933c4 fix: missing default statement in a switch in the modem_console example 2022-01-28 22:34:03 +07:00
face03e4e5 fix: add virtual destructors to ModuleIf and CommandableIf 2022-01-28 22:34:03 +07:00
2cb74cf8d0 Update(esp_modem): Bump component version 2021-12-06 12:49:09 +01:00
d879e82a42 fix(esp_modem): Update the test to exersise CBC parser 2021-12-06 11:17:24 +01:00
4f1d31f9b7 fix(esp_modem): Fix battery status parse
Variable `pos`  was meant to hold position of first `,` (comma)
appearance in the parsed string, but due to wrong parantheses it
contained the boolean representation of not equal to `string::npos`
2021-12-06 11:17:24 +01:00
bece4efa09 Merge pull request #9 from david-cermak/bugfix/esp_modem_read_module_name
fix(esp_modem): Read module name with AT commands
2021-12-01 15:31:23 +01:00
8417e232aa fix(esp_modem): Read module name with AT commands
This fixes basic IDF test case which supplied dummy module name.
2021-11-25 16:24:52 +01:00
5f0832a0ad Merge pull request #7 from david-cermak/feature/ci_test_report
CI: Add run host to the CI
2021-11-18 15:42:33 +01:00
464baeeb83 Merge pull request #8 from sudeep-mohanty/master
esp_modem: Update FreeRTOS EventQueueHandle_t forward declaration
2021-11-18 12:13:17 +01:00
5d9ad9cffd esp_modem: Update FreeRTOS EventQueueHandle_t forward declaration
This commit includes freertos/event_groups.h header and removes the
forward declaration for EventGroupHandle_t.

Signed-off-by: Sudeep Mohanty <sudeep.mohanty@espressif.com>
2021-11-18 16:24:35 +05:30
9fbd6e658a CI: Build on linux and Run host tests 2021-11-18 08:35:55 +01:00
8465b14653 fix(esp_modem): linux port to work with lwip-2.1.2 2021-11-18 08:35:49 +01:00
e7ae0301ae Merge pull request #5 from david-cermak/bugfix/missign_c_api
fix(esp_modem): Add missing C API
2021-11-18 07:36:40 +01:00
96498760bf esp_modem: New version to publish to component registry 2021-11-16 08:13:26 +01:00
cf990d1a87 Docs: Add links to GitHub pages 2021-11-16 08:11:57 +01:00
5299b425e8 esp_modem: Add missing AT commands to plain C-API 2021-11-16 08:03:30 +01:00
bcb1ab99bd Merge pull request #4 from david-cermak/feature/build_docs
CI/Docs: Add jobs to deploy component docs
2021-11-16 07:56:50 +01:00
2c21aa1113 CI/Docs: Generate docs locally before uploading to registry 2021-11-16 07:51:24 +01:00
b9ff1e4e12 CI/Docs: Add jobs to deploy component docs 2021-11-12 16:51:21 +01:00
23dbdb584e Merge pull request #3 from david-cermak/feature/upload_components
Added actions to upload components and sync issues
2021-11-08 18:04:37 +01:00
1d0cc49c3f CI: Sync issues to JIRA 2021-11-08 15:18:44 +01:00
c1249aec17 CI: Upload comonents to Espressif Service on master
Co-authored-by: Sergei Silnov <po@kumekay.com>
2021-11-08 15:18:35 +01:00
2bbcb95e53 Merge pull request #2 from sudeep-mohanty/master
esp_modem: Update Task handle and Event Group handle to match freertos v10.4.3
2021-11-08 12:44:05 +01:00
58887170d2 esp_modem: Update Task handle and Event Group handle to match freertos v10.4.3
This commit updates the default handles for Task type and Signal Group
type to match the struct type handle from freertos v10.4.3.

Signed-off-by: Sudeep Mohanty <sudeep.mohanty@espressif.com>
2021-11-08 10:46:53 +05:30
cc7aa03a37 Merge pull request #1 from david-cermak/master
CI: Initial GitHub Action to build examples
2021-11-04 13:33:59 +01:00
15a858b735 CI: Move separate master job to the loop loop 2021-11-04 13:27:05 +01:00
01e2a9c109 Examples: Fix print format for http_client() API 2021-11-04 11:41:11 +01:00
c0021ceeb2 CI: Rewrite flat build jobs using test matrix 2021-11-04 11:33:28 +01:00
fec83e5915 CI: Initial GitHub Action to build examples
Build job definitions for building examples for ESP32 only against IDF
master, 4.4, 4.3, 4.2 and 4.1
2021-11-03 17:39:47 +01:00
c8c24ed2fc Examples: Fix build error when SIM needs no PIN 2021-11-03 13:59:24 +01:00
b380ded5fa Examples: Use bigger 4M flash for OTA example 2021-11-03 13:59:24 +01:00
38f6eb963a README: esp-protocol repository 2021-10-04 11:39:32 +02:00
2a2d27086f esp_modem(0.1.9): Fix CMake3.5 build with no cxx_std_17 feature
Older CMake (< 3.8, but still supported in IDF) doesn't support target_compile_features() for cxx_std_17.
If CMake doesn't know C++17 features, set it manually via comile options.
2021-10-04 11:35:27 +02:00
52 changed files with 2705 additions and 96 deletions

26
.github/workflows/build-websockets.yml vendored Normal file
View 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
View 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
View 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

View 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
version: "0.1.8"
version: "0.1.13"
description: esp modem
dependencies:
# Required IDF version

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

@ -89,4 +89,9 @@ void Task::Relinquish()
usleep(0);
}
} // namespace esp_modem
void Task::Delay(uint32_t ms)
{
usleep(ms*1000);
}
} // namespace esp_modem

View File

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

View File

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

View 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

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

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

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

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

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

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

View 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

View 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

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

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

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

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

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "websocket_example.c"
INCLUDE_DIRS ".")

View File

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

View File

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

View 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

View File

@ -0,0 +1,5 @@
version: "0.0.1"
description: esp websocket client
dependencies:
idf:
version: ">=5.0"

View 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

View File

@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
PRIV_REQUIRES cmock test_utils esp_websocket_client)

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