mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-23 07:17:29 +02:00
websocket: Initial version based on IDF 5.0
This commit is contained in:
10
components/esp_websocket_client/examples/CMakeLists.txt
Normal file
10
components/esp_websocket_client/examples/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "../..")
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(websocket_example)
|
89
components/esp_websocket_client/examples/README.md
Normal file
89
components/esp_websocket_client/examples/README.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Websocket Sample application
|
||||
|
||||
This example will shows how to set up and communicate over a websocket.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example can be executed on any ESP32 board, the only required interface is WiFi and connection to internet or a local server.
|
||||
|
||||
### Configure the project
|
||||
|
||||
* Open the project configuration menu (`idf.py menuconfig`)
|
||||
* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu.
|
||||
* Configure the websocket endpoint URI under "Example Configuration", if "WEBSOCKET_URI_FROM_STDIN" is selected then the example application will connect to the URI it reads from stdin (used for testing)
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (482) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
|
||||
I (2492) example_connect: Ethernet Link Up
|
||||
I (4472) tcpip_adapter: eth ip: 192.168.2.137, mask: 255.255.255.0, gw: 192.168.2.2
|
||||
I (4472) example_connect: Connected to Ethernet
|
||||
I (4472) example_connect: IPv4 address: 192.168.2.137
|
||||
I (4472) example_connect: IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed4:a92b
|
||||
I (4482) WEBSOCKET: Connecting to ws://echo.websocket.events...
|
||||
I (5012) WEBSOCKET: WEBSOCKET_EVENT_CONNECTED
|
||||
I (5492) WEBSOCKET: Sending hello 0000
|
||||
I (6052) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (6052) WEBSOCKET: Received=hello 0000
|
||||
|
||||
I (6492) WEBSOCKET: Sending hello 0001
|
||||
I (7052) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (7052) WEBSOCKET: Received=hello 0001
|
||||
|
||||
I (7492) WEBSOCKET: Sending hello 0002
|
||||
I (8082) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (8082) WEBSOCKET: Received=hello 0002
|
||||
|
||||
I (8492) WEBSOCKET: Sending hello 0003
|
||||
I (9152) WEBSOCKET: WEBSOCKET_EVENT_DATA
|
||||
W (9162) WEBSOCKET: Received=hello 0003
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Python Flask echo server
|
||||
|
||||
By default, the `ws://echo.websocket.events` endpoint is used. You can setup a Python websocket echo server locally and try the `ws://<your-ip>:5000` endpoint. To do this, install Flask-sock Python package
|
||||
|
||||
```
|
||||
pip install flask-sock
|
||||
```
|
||||
|
||||
and start a Flask websocket echo server locally by executing the following Python code:
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
from flask_sock import Sock
|
||||
|
||||
app = Flask(__name__)
|
||||
sock = Sock(app)
|
||||
|
||||
|
||||
@sock.route('/')
|
||||
def echo(ws):
|
||||
while True:
|
||||
data = ws.receive()
|
||||
ws.send(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# To run your Flask + WebSocket server in production you can use Gunicorn:
|
||||
# gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app
|
||||
app.run(host="0.0.0.0", debug=True)
|
||||
```
|
||||
|
146
components/esp_websocket_client/examples/example_test.py
Normal file
146
components/esp_websocket_client/examples/example_test.py
Normal file
@ -0,0 +1,146 @@
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import socket
|
||||
import string
|
||||
from threading import Event, Thread
|
||||
|
||||
import ttfw_idf
|
||||
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
|
||||
from tiny_test_fw import Utility
|
||||
|
||||
|
||||
def get_my_ip():
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
try:
|
||||
# doesn't even have to be reachable
|
||||
s.connect(('10.255.255.255', 1))
|
||||
IP = s.getsockname()[0]
|
||||
except Exception:
|
||||
IP = '127.0.0.1'
|
||||
finally:
|
||||
s.close()
|
||||
return IP
|
||||
|
||||
|
||||
class TestEcho(WebSocket):
|
||||
|
||||
def handleMessage(self):
|
||||
self.sendMessage(self.data)
|
||||
print('Server sent: {}'.format(self.data))
|
||||
|
||||
def handleConnected(self):
|
||||
print('Connection from: {}'.format(self.address))
|
||||
|
||||
def handleClose(self):
|
||||
print('{} closed the connection'.format(self.address))
|
||||
|
||||
|
||||
# Simple Websocket server for testing purposes
|
||||
class Websocket(object):
|
||||
|
||||
def send_data(self, data):
|
||||
for nr, conn in self.server.connections.items():
|
||||
conn.sendMessage(data)
|
||||
|
||||
def run(self):
|
||||
self.server = SimpleWebSocketServer('', self.port, TestEcho)
|
||||
while not self.exit_event.is_set():
|
||||
self.server.serveonce()
|
||||
|
||||
def __init__(self, port):
|
||||
self.port = port
|
||||
self.exit_event = Event()
|
||||
self.thread = Thread(target=self.run)
|
||||
self.thread.start()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.exit_event.set()
|
||||
self.thread.join(10)
|
||||
if self.thread.is_alive():
|
||||
Utility.console_log('Thread cannot be joined', 'orange')
|
||||
|
||||
|
||||
def test_echo(dut):
|
||||
dut.expect('WEBSOCKET_EVENT_CONNECTED')
|
||||
for i in range(0, 5):
|
||||
dut.expect(re.compile(r'Received=hello (\d)'), timeout=30)
|
||||
print('All echos received')
|
||||
|
||||
|
||||
def test_close(dut):
|
||||
code = dut.expect(re.compile(r'WEBSOCKET: Received closed message with code=(\d*)'), timeout=60)[0]
|
||||
print('Received close frame with code {}'.format(code))
|
||||
|
||||
|
||||
def test_recv_long_msg(dut, websocket, msg_len, repeats):
|
||||
send_msg = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(msg_len))
|
||||
|
||||
for _ in range(repeats):
|
||||
websocket.send_data(send_msg)
|
||||
|
||||
recv_msg = ''
|
||||
while len(recv_msg) < msg_len:
|
||||
# Filter out color encoding
|
||||
match = dut.expect(re.compile(r'Received=([a-zA-Z0-9]*).*\n'), timeout=30)[0]
|
||||
recv_msg += match
|
||||
|
||||
if recv_msg == send_msg:
|
||||
print('Sent message and received message are equal')
|
||||
else:
|
||||
raise ValueError('DUT received string do not match sent string, \nexpected: {}\nwith length {}\
|
||||
\nreceived: {}\nwith length {}'.format(send_msg, len(send_msg), recv_msg, len(recv_msg)))
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
|
||||
def test_examples_protocol_websocket(env, extra_data):
|
||||
"""
|
||||
steps:
|
||||
1. obtain IP address
|
||||
2. connect to uri specified in the config
|
||||
3. send and receive data
|
||||
"""
|
||||
dut1 = env.get_dut('websocket', 'examples/protocols/websocket', dut_class=ttfw_idf.ESP32DUT)
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut1.app.binary_path, 'websocket_example.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance('websocket_bin_size', '{}KB'.format(bin_size // 1024))
|
||||
|
||||
try:
|
||||
if 'CONFIG_WEBSOCKET_URI_FROM_STDIN' in dut1.app.get_sdkconfig():
|
||||
uri_from_stdin = True
|
||||
else:
|
||||
uri = dut1.app.get_sdkconfig()['CONFIG_WEBSOCKET_URI'].strip('"')
|
||||
uri_from_stdin = False
|
||||
|
||||
except Exception:
|
||||
print('ENV_TEST_FAILURE: Cannot find uri settings in sdkconfig')
|
||||
raise
|
||||
|
||||
# start test
|
||||
dut1.start_app()
|
||||
|
||||
if uri_from_stdin:
|
||||
server_port = 4455
|
||||
with Websocket(server_port) as ws:
|
||||
uri = 'ws://{}:{}'.format(get_my_ip(), server_port)
|
||||
print('DUT connecting to {}'.format(uri))
|
||||
dut1.expect('Please enter uri of websocket endpoint', timeout=30)
|
||||
dut1.write(uri)
|
||||
test_echo(dut1)
|
||||
# Message length should exceed DUT's buffer size to test fragmentation, default is 1024 byte
|
||||
test_recv_long_msg(dut1, ws, 2000, 3)
|
||||
test_close(dut1)
|
||||
|
||||
else:
|
||||
print('DUT connecting to {}'.format(uri))
|
||||
test_echo(dut1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_websocket()
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "websocket_example.c"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,23 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice WEBSOCKET_URI_SOURCE
|
||||
prompt "Websocket URI source"
|
||||
default WEBSOCKET_URI_FROM_STRING
|
||||
help
|
||||
Selects the source of the URI used in the example.
|
||||
|
||||
config WEBSOCKET_URI_FROM_STRING
|
||||
bool "From string"
|
||||
|
||||
config WEBSOCKET_URI_FROM_STDIN
|
||||
bool "From stdin"
|
||||
endchoice
|
||||
|
||||
config WEBSOCKET_URI
|
||||
string "Websocket endpoint URI"
|
||||
depends on WEBSOCKET_URI_FROM_STRING
|
||||
default "ws://echo.websocket.events"
|
||||
help
|
||||
URL of websocket endpoint this example connects to and sends echo
|
||||
|
||||
endmenu
|
@ -0,0 +1,154 @@
|
||||
/* ESP HTTP Client Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_event.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_websocket_client.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#define NO_DATA_TIMEOUT_SEC 5
|
||||
|
||||
static const char *TAG = "WEBSOCKET";
|
||||
|
||||
static TimerHandle_t shutdown_signal_timer;
|
||||
static SemaphoreHandle_t shutdown_sema;
|
||||
|
||||
static void shutdown_signaler(TimerHandle_t xTimer)
|
||||
{
|
||||
ESP_LOGI(TAG, "No data received for %d seconds, signaling shutdown", NO_DATA_TIMEOUT_SEC);
|
||||
xSemaphoreGive(shutdown_sema);
|
||||
}
|
||||
|
||||
#if CONFIG_WEBSOCKET_URI_FROM_STDIN
|
||||
static void get_string(char *line, size_t size)
|
||||
{
|
||||
int count = 0;
|
||||
while (count < size) {
|
||||
int c = fgetc(stdin);
|
||||
if (c == '\n') {
|
||||
line[count] = '\0';
|
||||
break;
|
||||
} else if (c > 0 && c < 127) {
|
||||
line[count] = c;
|
||||
++count;
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */
|
||||
|
||||
static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
|
||||
switch (event_id) {
|
||||
case WEBSOCKET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
|
||||
break;
|
||||
case WEBSOCKET_EVENT_DATA:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
|
||||
ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
|
||||
if (data->op_code == 0x08 && data->data_len == 2) {
|
||||
ESP_LOGW(TAG, "Received closed message with code=%d", 256*data->data_ptr[0] + data->data_ptr[1]);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr);
|
||||
}
|
||||
ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);
|
||||
|
||||
xTimerReset(shutdown_signal_timer, portMAX_DELAY);
|
||||
break;
|
||||
case WEBSOCKET_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void websocket_app_start(void)
|
||||
{
|
||||
esp_websocket_client_config_t websocket_cfg = {};
|
||||
|
||||
shutdown_signal_timer = xTimerCreate("Websocket shutdown timer", NO_DATA_TIMEOUT_SEC * 1000 / portTICK_PERIOD_MS,
|
||||
pdFALSE, NULL, shutdown_signaler);
|
||||
shutdown_sema = xSemaphoreCreateBinary();
|
||||
|
||||
#if CONFIG_WEBSOCKET_URI_FROM_STDIN
|
||||
char line[128];
|
||||
|
||||
ESP_LOGI(TAG, "Please enter uri of websocket endpoint");
|
||||
get_string(line, sizeof(line));
|
||||
|
||||
websocket_cfg.uri = line;
|
||||
ESP_LOGI(TAG, "Endpoint uri: %s\n", line);
|
||||
|
||||
#else
|
||||
websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
|
||||
|
||||
#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
|
||||
|
||||
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
|
||||
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);
|
||||
|
||||
esp_websocket_client_start(client);
|
||||
xTimerStart(shutdown_signal_timer, portMAX_DELAY);
|
||||
char data[32];
|
||||
int i = 0;
|
||||
while (i < 5) {
|
||||
if (esp_websocket_client_is_connected(client)) {
|
||||
int len = sprintf(data, "hello %04d", i++);
|
||||
ESP_LOGI(TAG, "Sending %s", data);
|
||||
esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
xSemaphoreTake(shutdown_sema, portMAX_DELAY);
|
||||
esp_websocket_client_close(client, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Websocket Stopped");
|
||||
esp_websocket_client_destroy(client);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "[APP] Startup..");
|
||||
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
|
||||
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
|
||||
esp_log_level_set("*", ESP_LOG_INFO);
|
||||
esp_log_level_set("WEBSOCKET_CLIENT", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("TRANSPORT_WS", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("TRANS_TCP", ESP_LOG_DEBUG);
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
websocket_app_start();
|
||||
}
|
11
components/esp_websocket_client/examples/sdkconfig.ci
Normal file
11
components/esp_websocket_client/examples/sdkconfig.ci
Normal file
@ -0,0 +1,11 @@
|
||||
CONFIG_WEBSOCKET_URI_FROM_STDIN=y
|
||||
CONFIG_WEBSOCKET_URI_FROM_STRING=n
|
||||
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=n
|
||||
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
|
||||
CONFIG_EXAMPLE_ETH_PHY_IP101=y
|
||||
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
|
||||
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
|
||||
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
|
||||
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
|
||||
CONFIG_EXAMPLE_CONNECT_IPV6=y
|
Reference in New Issue
Block a user