From e565f7b371ab80f0562fa722c01747668f01c1c8 Mon Sep 17 00:00:00 2001 From: Abhik Roy Date: Thu, 7 Dec 2023 20:26:47 +1100 Subject: [PATCH] feat(console): Console for runtime wifi configuration --- .github/workflows/console_cmd_wifi__build.yml | 32 ++ .github/workflows/publish-docs-component.yml | 1 + components/console_cmd_wifi/.cz.yaml | 8 + components/console_cmd_wifi/CMakeLists.txt | 4 + components/console_cmd_wifi/README.md | 44 +++ components/console_cmd_wifi/console_wifi.c | 276 ++++++++++++++++++ components/console_cmd_wifi/console_wifi.h | 25 ++ .../examples/wifi-basic/CMakeLists.txt | 8 + .../examples/wifi-basic/main/CMakeLists.txt | 2 + .../wifi-basic/main/idf_component.yml | 6 + .../examples/wifi-basic/main/wifi-basic.c | 32 ++ .../examples/wifi-basic/pytest_wifi-basic.py | 14 + components/console_cmd_wifi/idf_component.yml | 10 + 13 files changed, 462 insertions(+) create mode 100644 .github/workflows/console_cmd_wifi__build.yml create mode 100644 components/console_cmd_wifi/.cz.yaml create mode 100644 components/console_cmd_wifi/CMakeLists.txt create mode 100644 components/console_cmd_wifi/README.md create mode 100644 components/console_cmd_wifi/console_wifi.c create mode 100644 components/console_cmd_wifi/console_wifi.h create mode 100644 components/console_cmd_wifi/examples/wifi-basic/CMakeLists.txt create mode 100644 components/console_cmd_wifi/examples/wifi-basic/main/CMakeLists.txt create mode 100644 components/console_cmd_wifi/examples/wifi-basic/main/idf_component.yml create mode 100644 components/console_cmd_wifi/examples/wifi-basic/main/wifi-basic.c create mode 100644 components/console_cmd_wifi/examples/wifi-basic/pytest_wifi-basic.py create mode 100644 components/console_cmd_wifi/idf_component.yml diff --git a/.github/workflows/console_cmd_wifi__build.yml b/.github/workflows/console_cmd_wifi__build.yml new file mode 100644 index 00000000000..fd2822082eb --- /dev/null +++ b/.github/workflows/console_cmd_wifi__build.yml @@ -0,0 +1,32 @@ +name: "console_cmd_wifi: build-tests" + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, labeled] + +jobs: + build_console_cmd_wifi: + if: contains(github.event.pull_request.labels.*.name, 'console') || github.event_name == 'push' + name: Build + strategy: + matrix: + idf_ver: ["latest", "release-v5.0"] + idf_target: ["esp32"] + test: [ { app: example, path: "components/console_cmd_wifi/examples" }] + runs-on: ubuntu-20.04 + container: espressif/idf:${{ matrix.idf_ver }} + steps: + - name: Checkout esp-protocols + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }} + shell: bash + working-directory: ${{matrix.test.path}} + run: | + ${IDF_PATH}/install.sh --enable-pytest + . ${IDF_PATH}/export.sh + python $IDF_PATH/tools/ci/ci_build_apps.py . --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app diff --git a/.github/workflows/publish-docs-component.yml b/.github/workflows/publish-docs-component.yml index b27a9e82825..0dc1a8ba18f 100644 --- a/.github/workflows/publish-docs-component.yml +++ b/.github/workflows/publish-docs-component.yml @@ -96,5 +96,6 @@ jobs: components/console_simple_init; components/console_cmd_ping; components/console_cmd_ifconfig; + components/console_cmd_wifi; namespace: "espressif" api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} diff --git a/components/console_cmd_wifi/.cz.yaml b/components/console_cmd_wifi/.cz.yaml new file mode 100644 index 00000000000..d67e425a3e0 --- /dev/null +++ b/components/console_cmd_wifi/.cz.yaml @@ -0,0 +1,8 @@ +--- +commitizen: + bump_message: 'bump(console): $current_version -> $new_version' + pre_bump_hooks: python ../../ci/changelog.py console_cmd_wifi + tag_format: console_cmd_wifi-v$version + version: 0.0.9 + version_files: + - idf_component.yml diff --git a/components/console_cmd_wifi/CMakeLists.txt b/components/console_cmd_wifi/CMakeLists.txt new file mode 100644 index 00000000000..53cc8d1a8dd --- /dev/null +++ b/components/console_cmd_wifi/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "console_wifi.c" + INCLUDE_DIRS "." + PRIV_REQUIRES esp_netif console esp_wifi + WHOLE_ARCHIVE) diff --git a/components/console_cmd_wifi/README.md b/components/console_cmd_wifi/README.md new file mode 100644 index 00000000000..7cb00f63119 --- /dev/null +++ b/components/console_cmd_wifi/README.md @@ -0,0 +1,44 @@ +# Console command wifi +The component offers a console with a command that enables runtime wifi configuration for any example project. + +## API + +### Steps to enable console in an example code: +1. Add this component to your project using ```idf.py add-dependency``` command. +2. In the main file of the example, add the following line: + ```c + #include "console_wifi.h" + ``` +3. Ensure esp-netif and NVS flash is initialized and default event loop is created in your app_main(): + ```c + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_err_t ret = nvs_flash_init(); //Initialize NVS + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + ``` +4. In your app_main() function, add the following line as the last line: + ```c + ESP_ERROR_CHECK(console_cmd_init()); // Initialize console + + // Register all plugin command added to your project + ESP_ERROR_CHECK(console_cmd_all_register()); + + // To register only wifi command skip calling console_cmd_all_register() + ESP_ERROR_CHECK(console_cmd_wifi_register()); + + ESP_ERROR_CHECK(console_cmd_start()); // Start console + ``` + +## Suported command: + +### wifi: +* ```wifi help```: Prints the help text for all wifi commands +* ```wifi show network```: Scans and displays upto 10 available wifi networks. +* ```wifi show sta```: Shows the details of wifi station. +* ```wifi sta join ```: Station joins the given wifi network. +* ```wifi sta join ```: Station joins the given unsecured wifi network. +* ```wifi sta leave```: Station leaves the wifi network. diff --git a/components/console_cmd_wifi/console_wifi.c b/components/console_cmd_wifi/console_wifi.c new file mode 100644 index 00000000000..6ad949e6271 --- /dev/null +++ b/components/console_cmd_wifi/console_wifi.c @@ -0,0 +1,276 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_netif.h" +#include "esp_console.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif_net_stack.h" +#include "lwip/ip6.h" +#include "lwip/opt.h" +#include "esp_wifi.h" +#include "console_wifi.h" + + +#define DEFAULT_SCAN_LIST_SIZE 10 + +/** + * Static registration of this plugin is achieved by defining the plugin description + * structure and placing it into .console_cmd_desc section. + * The name of the section and its placement is determined by linker.lf file in 'plugins' component. + */ +static const console_cmd_plugin_desc_t __attribute__((section(".console_cmd_desc"), used)) PLUGIN = { + .name = "console_cmd_wifi", + .plugin_regd_fn = &console_cmd_wifi_register +}; + +typedef struct wifi_op_t { + char *name; + esp_err_t (*operation)(struct wifi_op_t *self, int argc, char *argv[]); + int arg_cnt; + int start_index; + char *help; +} wifi_op_t; + +static esp_err_t wifi_help_op(wifi_op_t *self, int argc, char *argv[]); +static esp_err_t wifi_show_op(wifi_op_t *self, int argc, char *argv[]); +static esp_err_t wifi_sta_join_op(wifi_op_t *self, int argc, char *argv[]); +static esp_err_t wifi_sta_leave_op(wifi_op_t *self, int argc, char *argv[]); + +static const char *TAG = "console_wifi"; + +#define JOIN_TIMEOUT_MS (10000) + +static EventGroupHandle_t wifi_event_group; +static const int STA_STARTED_BIT = BIT0; +static const int CONNECTED_BIT = BIT1; + +static wifi_op_t cmd_list[] = { + {.name = "help", .operation = wifi_help_op, .arg_cnt = 2, .start_index = 1, .help = "wifi help: Prints the help text for all wifi commands"}, + {.name = "show", .operation = wifi_show_op, .arg_cnt = 3, .start_index = 1, .help = "wifi show network/sta: Scans and displays all available wifi APs./ Shows the details of wifi station."}, + {.name = "join", .operation = wifi_sta_join_op, .arg_cnt = 4, .start_index = 2, .help = "wifi sta join : Station joins the given wifi network."}, + {.name = "join", .operation = wifi_sta_join_op, .arg_cnt = 5, .start_index = 2, .help = "wifi sta join : Station joins the given unsecured wifi network."}, + {.name = "leave", .operation = wifi_sta_leave_op, .arg_cnt = 3, .start_index = 2, .help = "wifi sta leave: Station leaves the wifi network."}, +}; + +static void event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + xEventGroupSetBits(wifi_event_group, STA_STARTED_BIT); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + } +} + +static esp_err_t wifi_help_op(wifi_op_t *self, int argc, char *argv[]) +{ + int cmd_count = sizeof(cmd_list) / sizeof(cmd_list[0]); + + for (int i = 0; i < cmd_count; i++) { + if ((cmd_list[i].help != NULL) && (strlen(cmd_list[i].help) != 0)) { + printf(" %s\n", cmd_list[i].help); + } + } + + return ESP_OK; +} + +uint8_t wifi_connection_status = 0; + +void wifi_init(void) +{ + static bool init_flag = false; + if (init_flag) { + return; + } + wifi_event_group = xEventGroupCreate(); + + esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); + assert(sta_netif); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_START, &event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL)); + + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + + ESP_ERROR_CHECK(esp_wifi_start()); + + int bits = xEventGroupWaitBits(wifi_event_group, STA_STARTED_BIT, + pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS); + + if ((bits & STA_STARTED_BIT) == 0) { + ESP_LOGE(TAG, "Error: Wifi Connection timed out"); + return; + } + + init_flag = true; +} + +/* Initialize Wi-Fi as sta and set scan method */ +static void wifi_scan(void) +{ + uint16_t number = DEFAULT_SCAN_LIST_SIZE; + wifi_ap_record_t ap_info[DEFAULT_SCAN_LIST_SIZE]; + uint16_t ap_count = 0; + + memset(ap_info, 0, sizeof(ap_info)); + + wifi_init(); + + esp_wifi_scan_start(NULL, true); + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info)); + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count)); + + ESP_LOGI(TAG, "Showing Wifi networks"); + ESP_LOGI(TAG, "*********************"); + for (int i = 0; i < number; i++) { + ESP_LOGI(TAG, "RSSI: %d\tChannel: %d\tSSID: %s", ap_info[i].rssi, ap_info[i].primary, ap_info[i].ssid); + } + ESP_LOGI(TAG, "Total APs scanned = %u, actual AP number ap_info holds = %u", ap_count, number); +} + +static esp_err_t wifi_show_op(wifi_op_t *self, int argc, char *argv[]) +{ + if (!strcmp("network", argv[self->start_index + 1])) { + wifi_scan(); + return ESP_OK; + } + + if (!strcmp("sta", argv[self->start_index + 1])) { + { + wifi_config_t wifi_config = { 0 }; + + wifi_init(); + ESP_ERROR_CHECK(esp_wifi_get_config(WIFI_IF_STA, &wifi_config)); + + ESP_LOGI(TAG, "Showing Joind AP details:"); + ESP_LOGI(TAG, "*************************"); + ESP_LOGI(TAG, "SSID: %s", wifi_config.sta.ssid); + ESP_LOGI(TAG, "Password: %s", wifi_config.sta.password); + ESP_LOGI(TAG, "Channel: %d", wifi_config.sta.channel); + ESP_LOGI(TAG, "bssid: %02x:%02x:%02x:%02x:%02x:%02x", wifi_config.sta.bssid[0], + wifi_config.sta.bssid[1], wifi_config.sta.bssid[2], wifi_config.sta.bssid[3], + wifi_config.sta.bssid[4], wifi_config.sta.bssid[5]); + } + return ESP_OK; + } + + return ESP_OK; +} + +static esp_err_t wifi_sta_join_op(wifi_op_t *self, int argc, char *argv[]) +{ + wifi_config_t wifi_config = { 0 }; + + if (strcmp("sta", argv[self->start_index - 1])) { + ESP_LOGE(TAG, "Error: Invalid command\n"); + ESP_LOGE(TAG, "%s\n", self->help); + return ESP_FAIL; + } + + strlcpy((char *) wifi_config.sta.ssid, argv[self->start_index + 1], sizeof(wifi_config.sta.ssid)); + if (self->arg_cnt == 5) { + strlcpy((char *) wifi_config.sta.password, argv[self->start_index + 2], sizeof(wifi_config.sta.password)); + } + + wifi_init(); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + esp_wifi_connect(); + + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, + pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS); + + if ((bits & CONNECTED_BIT) == 0) { + ESP_LOGE(TAG, "Error: Wifi Connection timed out"); + return ESP_OK; + } + + return ESP_OK; +} + +static esp_err_t wifi_sta_leave_op(wifi_op_t *self, int argc, char *argv[]) +{ + wifi_config_t wifi_config = { 0 }; + + if (strcmp("sta", argv[self->start_index - 1])) { + ESP_LOGE(TAG, "Error: Invalid command\n"); + ESP_LOGE(TAG, "%s\n", self->help); + return ESP_FAIL; + } + + esp_wifi_disconnect(); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + + return ESP_OK; +} + +/* handle 'wifi' command */ +static esp_err_t do_cmd_wifi(int argc, char **argv) +{ + int cmd_count = sizeof(cmd_list) / sizeof(cmd_list[0]); + wifi_op_t cmd; + + for (int i = 0; i < cmd_count; i++) { + cmd = cmd_list[i]; + + if (argc < cmd.start_index + 1) { + continue; + } + + if (!strcmp(cmd.name, argv[cmd.start_index])) { + /* Get interface for eligible commands */ + if (cmd.arg_cnt == argc) { + if (cmd.operation != NULL) { + if (cmd.operation(&cmd, argc, argv) != ESP_OK) { + ESP_LOGE(TAG, "Usage:\n%s", cmd.help); + return 0; + } + } + return ESP_OK; + } + } + } + + ESP_LOGE(TAG, "Command not available"); + + return ESP_OK; +} + +/** + * @brief Registers the wifi command. + * + * @return + * - esp_err_t + */ +esp_err_t console_cmd_wifi_register(void) +{ + esp_err_t ret; + esp_console_cmd_t command = { + .command = "wifi", + .help = "Command for wifi configuration and monitoring\n For more info run 'wifi help'", + .func = &do_cmd_wifi + }; + + ret = esp_console_cmd_register(&command); + if (ret) { + ESP_LOGE(TAG, "Unable to register wifi"); + } + + return ret; +} diff --git a/components/console_cmd_wifi/console_wifi.h b/components/console_cmd_wifi/console_wifi.h new file mode 100644 index 00000000000..fb89e31f651 --- /dev/null +++ b/components/console_cmd_wifi/console_wifi.h @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "console_simple_init.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Registers the wifi command. + * + * @return + * - esp_err_t + */ +esp_err_t console_cmd_wifi_register(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/console_cmd_wifi/examples/wifi-basic/CMakeLists.txt b/components/console_cmd_wifi/examples/wifi-basic/CMakeLists.txt new file mode 100644 index 00000000000..4d8520e5356 --- /dev/null +++ b/components/console_cmd_wifi/examples/wifi-basic/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five 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.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(wifi-basic) diff --git a/components/console_cmd_wifi/examples/wifi-basic/main/CMakeLists.txt b/components/console_cmd_wifi/examples/wifi-basic/main/CMakeLists.txt new file mode 100644 index 00000000000..1221a0fef33 --- /dev/null +++ b/components/console_cmd_wifi/examples/wifi-basic/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "wifi-basic.c" + INCLUDE_DIRS ".") diff --git a/components/console_cmd_wifi/examples/wifi-basic/main/idf_component.yml b/components/console_cmd_wifi/examples/wifi-basic/main/idf_component.yml new file mode 100644 index 00000000000..eb9d8863ecb --- /dev/null +++ b/components/console_cmd_wifi/examples/wifi-basic/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + idf: + version: ">=5.0" + console_cmd_wifi: + version: "*" + override_path: '../../../' diff --git a/components/console_cmd_wifi/examples/wifi-basic/main/wifi-basic.c b/components/console_cmd_wifi/examples/wifi-basic/main/wifi-basic.c new file mode 100644 index 00000000000..9e1028ec3ea --- /dev/null +++ b/components/console_cmd_wifi/examples/wifi-basic/main/wifi-basic.c @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "esp_netif.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "console_wifi.h" + +void app_main(void) +{ + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_err_t ret = nvs_flash_init(); //Initialize NVS + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + // Initialize console REPL + ESP_ERROR_CHECK(console_cmd_init()); + + ESP_ERROR_CHECK(console_cmd_wifi_register()); + + // start console REPL + ESP_ERROR_CHECK(console_cmd_start()); + +} diff --git a/components/console_cmd_wifi/examples/wifi-basic/pytest_wifi-basic.py b/components/console_cmd_wifi/examples/wifi-basic/pytest_wifi-basic.py new file mode 100644 index 00000000000..f552eb8309c --- /dev/null +++ b/components/console_cmd_wifi/examples/wifi-basic/pytest_wifi-basic.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 + +# -*- coding: utf-8 -*- + +import pytest + + +@pytest.mark.esp32 +def test_examples_ifconfig_command(dut): + dut.expect('esp>', timeout=30) + dut.write('wifi show network') + dut.expect(r'Showing Wifi networks', timeout=30) + dut.expect('esp>', timeout=30) diff --git a/components/console_cmd_wifi/idf_component.yml b/components/console_cmd_wifi/idf_component.yml new file mode 100644 index 00000000000..ec99ca47bb7 --- /dev/null +++ b/components/console_cmd_wifi/idf_component.yml @@ -0,0 +1,10 @@ +version: 0.0.9 +url: https://github.com/espressif/esp-protocols/tree/master/components/console_cmd_wifi +description: The component offers a console that enables runtime wifi configuration and monitoring. +dependencies: + idf: + version: '>=5.0' + espressif/console_simple_init: + version: '>=1.1.0' + override_path: '../console_simple_init' + public: true