websocket: Initial version based on IDF 5.0

This commit is contained in:
gabsuren
2022-03-01 13:42:35 +04:00
parent b3c777ad43
commit 80c3cf0f02
18 changed files with 528 additions and 6 deletions

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