feat(websocket): Added linux port for websocket

This commit is contained in:
Suren Gabrielyan
2023-06-13 13:23:24 +04:00
parent ecc465daa3
commit a22391ae2c
20 changed files with 362 additions and 17 deletions

View File

@ -109,7 +109,6 @@ jobs:
- name: Build and Test
shell: bash
run: |
apt-get update
apt-get update && apt-get install -y gcc-8 g++-8 python3-pip
apt-get install -y rsync
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8
@ -129,9 +128,9 @@ jobs:
cd $GITHUB_WORKSPACE/${{ env.COMP_DIR }}
gcov-8 `find . -name "esp_modem*gcda" -printf '%h\n' | head -n 1`/*
gcovr --gcov-ignore-parse-errors -g -k -r . --html index.html -x esp_modem_coverage.xml
mkdir docs_gcovr
cp $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/index.html docs_gcovr
cp -rf docs_gcovr $GITHUB_WORKSPACE
mkdir modem_coverage_report
cp $GITHUB_WORKSPACE/${{ env.COMP_DIR }}/index.html modem_coverage_report
cp -rf modem_coverage_report $GITHUB_WORKSPACE
- name: Code Coverage Summary Report
uses: irongut/CodeCoverageSummary@v1.3.0
with:
@ -151,13 +150,7 @@ jobs:
uses: actions/upload-artifact@v3
if: always()
with:
name: docs_gcovr
name: modem_coverage_report
path: |
${{ env.COMP_DIR }}/docs_gcovr
${{ env.COMP_DIR }}/modem_coverage_report
if-no-files-found: error
- name: Deploy code coverage results
if: github.ref == 'refs/heads/master'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs_gcovr

View File

@ -0,0 +1,43 @@
name: Publish coverage report to Github Pages
on:
workflow_run:
workflows: ["websocket: build/host-tests", "esp-modem: build/host-tests"]
types:
- completed
jobs:
publish_github_pages:
runs-on: ubuntu-latest
if: github.repository == 'espressif/esp-protocols'
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Download Websocket Artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: websocket__build-host-tests.yml
workflow_conclusion: success
name: websocket_coverage_report
path: websocket_coverage_report_artifact
- name: Download Modem Artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: modem__build-host-tests.yml
workflow_conclusion: success
name: modem_coverage_report
path: modem_coverage_report_artifact
- name: Merge HTML files
run: |
echo "<html><body>" > index.html
cat modem_coverage_report_artifact/index.html >> index.html
cat websocket_coverage_report_artifact/index.html >> index.html
echo "</body></html>" >> index.html
mkdir coverage_report
mv index.html coverage_report
- name: Deploy generated docs
uses: JamesIves/github-pages-deploy-action@4.1.5
with:
branch: gh-pages
folder: coverage_report

86
.github/workflows/run-host-tests.yml vendored Normal file
View File

@ -0,0 +1,86 @@
name: Run on host
on:
workflow_call:
inputs:
idf_version:
required: true
type: string
app_name:
type: string
required: true
app_path:
type: string
required: true
component_path:
type: string
required: true
upload_artifacts:
type: boolean
required: true
jobs:
build:
name: Build App
runs-on: ubuntu-20.04
permissions:
contents: write
container: espressif/idf:${{inputs.idf_version}}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
with:
path: esp-protocols
- name: Build ${{ inputs.app_name }} with IDF-${{ inputs.idf_version }}
shell: bash
run: |
. ${IDF_PATH}/export.sh
cd $GITHUB_WORKSPACE/${{inputs.app_path}}
rm -rf sdkconfig sdkconfig.defaults build
cp sdkconfig.ci.linux sdkconfig.defaults
idf.py build
./build/${{inputs.app_name}}.elf
- name: Build with Coverage Enabled
shell: bash
run: |
. ${IDF_PATH}/export.sh
cd $GITHUB_WORKSPACE/${{inputs.app_path}}
rm -rf build sdkconfig sdkconfig.defaults
cp sdkconfig.ci.coverage sdkconfig.defaults
idf.py fullclean
idf.py build
./build/${{inputs.app_name}}.elf
- name: Run Coverage
shell: bash
run: |
apt-get update && apt-get install -y gcc-8 g++-8 python3-pip rsync
python -m pip install gcovr
cd $GITHUB_WORKSPACE/${{inputs.component_path}}
gcov `find . -name "*gcda"`
gcovr --gcov-ignore-parse-errors -g -k -r . --html index.html -x ${{inputs.app_name}}_coverage.xml
mkdir ${{inputs.app_name}}_coverage_report
touch ${{inputs.app_name}}_coverage_report/.nojekyll
cp index.html ${{inputs.app_name}}_coverage_report
cp -rf ${{inputs.app_name}}_coverage_report ${{inputs.app_name}}_coverage.xml $GITHUB_WORKSPACE
- name: Code Coverage Summary Report
uses: irongut/CodeCoverageSummary@v1.3.0
with:
filename: esp-protocols/**/${{inputs.app_name}}_coverage.xml
badge: true
fail_below_min: false
format: markdown
hide_branch_rate: false
hide_complexity: false
indicators: true
output: both
thresholds: '60 80'
- name: Write to Job Summary
run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY
- name: Upload files to artifacts for run-target job
uses: actions/upload-artifact@v3
if: ${{inputs.upload_artifacts}}
with:
name: ${{inputs.app_name}}_coverage_report
path: |
${{inputs.component_path}}/${{inputs.app_name}}_coverage_report
if-no-files-found: error

View File

@ -0,0 +1,20 @@
name: "websocket: build/host-tests"
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
host_test_websocket:
if: contains(github.event.pull_request.labels.*.name, 'websocket') || github.event_name == 'push'
uses: "./.github/workflows/run-host-tests.yml"
with:
idf_version: "latest"
app_name: "websocket"
app_path: "esp-protocols/components/esp_websocket_client/examples/linux"
component_path: "esp-protocols/components/esp_websocket_client"
upload_artifacts: true

View File

@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
idf_ver: ["release-v5.0", "release-v5.1", "latest"]
test: [ { app: example, path: "examples" }, { app: unit_test, path: "test" } ]
test: [ { app: example, path: "examples/target" }, { app: unit_test, path: "test" } ]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
env:
@ -53,7 +53,7 @@ jobs:
matrix:
idf_ver: ["release-v5.0", "release-v5.1", "latest"]
idf_target: ["esp32"]
test: [ { app: example, path: "examples" }, { app: unit_test, path: "test" } ]
test: [ { app: example, path: "examples/target" }, { app: unit_test, path: "test" } ]
runs-on:
- self-hosted
- ESP32-ETHERNET-KIT

View File

@ -1,3 +1,5 @@
idf_build_get_property(target IDF_TARGET)
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
@ -6,7 +8,14 @@ if(NOT CONFIG_WS_TRANSPORT AND NOT CMAKE_BUILD_EARLY_EXPANSION)
return()
endif()
idf_component_register(SRCS "esp_websocket_client.c"
if(${IDF_TARGET} STREQUAL "linux")
idf_component_register(SRCS "esp_websocket_client.c"
INCLUDE_DIRS "include"
REQUIRES esp-tls tcp_transport http_parser esp_event nvs_flash esp_stubs json
PRIV_REQUIRES esp_timer)
else()
idf_component_register(SRCS "esp_websocket_client.c"
INCLUDE_DIRS "include"
REQUIRES lwip esp-tls tcp_transport http_parser
PRIV_REQUIRES esp_timer esp_event)
endif()

View File

@ -19,6 +19,9 @@
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_tls_crypto.h"
#include "esp_system.h"
#include <errno.h>
#include <arpa/inet.h>
static const char *TAG = "websocket_client";

View File

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.5)
set(COMPONENTS esp_websocket_client main)
set(common_component_dir ../../../../common_components)
set(EXTRA_COMPONENT_DIRS
../../..
"${common_component_dir}/linux_compat/esp_timer"
"${common_component_dir}/linux_compat"
"${common_component_dir}/linux_compat/freertos")
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/protocols/linux_stubs/esp_stubs)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(websocket)

View File

@ -0,0 +1,14 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS
"."
REQUIRES esp_websocket_client protocol_examples_common)
if(CONFIG_GCOV_ENABLED)
target_compile_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
target_link_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
idf_component_get_property(esp_websocket_client esp_websocket_client COMPONENT_LIB)
target_compile_options(${esp_websocket_client} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
target_link_options(${esp_websocket_client} PUBLIC --coverage -fprofile-arcs -ftest-coverage)
endif()

View File

@ -0,0 +1,15 @@
menu "Host-test config"
config GCOV_ENABLED
bool "Coverage analyzer"
default n
help
Enables coverage analyzing for host tests.
config WEBSOCKET_URI
string "Websocket endpoint URI"
default "ws://echo.websocket.events"
help
URL of websocket endpoint this example connects to and sends echo
endmenu

View File

@ -0,0 +1,122 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "esp_log.h"
#include "nvs_flash.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_websocket_client.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
static const char *TAG = "websocket";
static void log_error_if_nonzero(const char *message, int error_code)
{
if (error_code != 0) {
ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
}
}
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");
log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
}
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);
}
// If received data contains json structure it succeed to parse
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);
break;
case WEBSOCKET_EVENT_ERROR:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code);
if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) {
log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err);
log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err);
log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno);
}
break;
}
}
static void websocket_app_start(void)
{
esp_websocket_client_config_t websocket_cfg = {};
websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
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);
char data[32];
int i = 0;
while (i < 1) {
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);
}
esp_websocket_client_destroy(client);
}
int main(void)
{
ESP_LOGI(TAG, "[APP] Startup..");
ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " 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();
return 0;
}

View File

@ -0,0 +1,8 @@
CONFIG_GCOV_ENABLED=y
CONFIG_IDF_TARGET="linux"
CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
CONFIG_WEBSOCKET_URI_FROM_STRING=y
CONFIG_WEBSOCKET_URI_FROM_STDIN=n

View File

@ -0,0 +1,8 @@
CONFIG_IDF_TARGET="linux"
CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
CONFIG_WEBSOCKET_URI_FROM_STRING=y
CONFIG_WEBSOCKET_URI_FROM_STDIN=n
CONFIG_EXAMPLE_CONNECT_WIFI=n

View File

@ -2,9 +2,9 @@
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS "..")
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 "../../../common_components/protocol_examples_common")
list(APPEND EXTRA_COMPONENT_DIRS "../../../../common_components/protocol_examples_common")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(websocket_example)

View File

@ -20,4 +20,11 @@ menu "Example Configuration"
help
URL of websocket endpoint this example connects to and sends echo
if CONFIG_IDF_TARGET = "linux"
config GCOV_ENABLED
bool "Coverage analyzer"
default n
help
Enables coverage analyzing for host tests.
endif
endmenu

View File

@ -1,3 +1,5 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_TARGET_LINUX=n
CONFIG_WEBSOCKET_URI_FROM_STDIN=y
CONFIG_WEBSOCKET_URI_FROM_STRING=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y