diff --git a/components/asio/asio b/components/asio/asio new file mode 160000 index 000000000..55efc179b --- /dev/null +++ b/components/asio/asio @@ -0,0 +1 @@ +Subproject commit 55efc179b76139c8f9b44bf22a4aba4803f7a7bd diff --git a/components/asio/component.mk b/components/asio/component.mk new file mode 100644 index 000000000..e024df3f3 --- /dev/null +++ b/components/asio/component.mk @@ -0,0 +1,6 @@ +COMPONENT_ADD_INCLUDEDIRS := asio/asio/include port/include +COMPONENT_PRIV_INCLUDEDIRS := private_include +COMPONENT_SRCDIRS := asio/asio/src +COMPONENT_OBJEXCLUDE := asio/asio/src/asio_ssl.o + +COMPONENT_SUBMODULES += asio diff --git a/components/asio/port/include/esp_asio_config.h b/components/asio/port/include/esp_asio_config.h new file mode 100644 index 000000000..accccad0d --- /dev/null +++ b/components/asio/port/include/esp_asio_config.h @@ -0,0 +1,45 @@ +// Copyright 2018 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. +#ifndef _ESP_ASIO_CONFIG_H_ +#define _ESP_ASIO_CONFIG_H_ + +// +// Enabling exceptions only when they are enabled in menuconfig +// +# include +# ifndef CONFIG_CXX_EXCEPTIONS +# define ASIO_NO_EXCEPTIONS +# endif // CONFIG_CXX_EXCEPTIONS + +// +// LWIP compatifility inet and address macros/functions +// +# define LWIP_COMPAT_SOCKET_INET 1 +# define LWIP_COMPAT_SOCKET_ADDR 1 + +// +// Specific ASIO feature flags +// +# define ASIO_DISABLE_SERIAL_PORT +# define ASIO_SEPARATE_COMPILATION +# define ASIO_STANDALONE +# define ASIO_NO_TYPEID +# define ASIO_DISABLE_SIGNAL +# define ASIO_HAS_PTHREADS +# define ASIO_DISABLE_EPOLL +# define ASIO_DISABLE_EVENTFD +# define ASIO_DISABLE_SIGNAL +# define ASIO_DISABLE_SIGACTION + +#endif // _ESP_ASIO_CONFIG_H_ diff --git a/components/asio/port/include/esp_exception.h b/components/asio/port/include/esp_exception.h new file mode 100644 index 000000000..3c5c04375 --- /dev/null +++ b/components/asio/port/include/esp_exception.h @@ -0,0 +1,39 @@ + +// Copyright 2018 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. +#ifndef _ESP_EXCEPTION_H_ +#define _ESP_EXCEPTION_H_ + +// +// This exception stub is enabled only if exceptions are disabled in menuconfig +// +#if !defined(CONFIG_CXX_EXCEPTIONS) && defined (ASIO_NO_EXCEPTIONS) + +#include "esp_log.h" + +// +// asio exception stub +// +namespace asio { +namespace detail { +template +void throw_exception(const Exception& e) +{ + ESP_LOGE("esp32_asio_exception", "Caught exception: %s!", e.what()); + abort(); +} +}} +#endif // CONFIG_CXX_EXCEPTIONS==1 && defined (ASIO_NO_EXCEPTIONS) + +#endif // _ESP_EXCEPTION_H_ diff --git a/docs/en/api-reference/protocols/asio.rst b/docs/en/api-reference/protocols/asio.rst new file mode 100644 index 000000000..635ef053f --- /dev/null +++ b/docs/en/api-reference/protocols/asio.rst @@ -0,0 +1,32 @@ +ASIO port +========= + +Overview +-------- +Asio is a cross-platform C++ library, see https://think-async.com. It provides a consistent asynchronous model using a modern C++ approach. + + +ASIO documentation +^^^^^^^^^^^^^^^^^^ +Please refer to the original asio documentation at https://think-async.com/Asio/Documentation. +Asio also comes with a number of examples which could be find under Documentation/Examples on that web site. + +Supported features +^^^^^^^^^^^^^^^^^^ +ESP platform port currently supports only network asynchronous socket operations; does not support serial port and ssl. +Internal asio settings for ESP include +- EXCEPTIONS: Supported, choice in menuconfig +- SIGNAL, SIGACTION: Not supported +- EPOLL, EVENTFD: Not supported +- TYPEID: Disabled by default, but supported in toolchain and asio (provided stdlib recompiled with -frtti) + +Application Example +------------------- +ESP examples are based on standard asio examples `examples/protocols/asio`: +- udp_echo_server +- tcp_echo_server +- chat_client +- chat_server +Please refer to the specific example README.md for details + + diff --git a/docs/zh_CN/api-reference/protocols/asio.rst b/docs/zh_CN/api-reference/protocols/asio.rst new file mode 100644 index 000000000..d1afa231e --- /dev/null +++ b/docs/zh_CN/api-reference/protocols/asio.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/protocols/asio.rst \ No newline at end of file diff --git a/examples/protocols/asio/chat_client/Makefile b/examples/protocols/asio/chat_client/Makefile new file mode 100644 index 000000000..8f651a3eb --- /dev/null +++ b/examples/protocols/asio/chat_client/Makefile @@ -0,0 +1,7 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := asio_chatclient + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/asio/chat_client/README.md b/examples/protocols/asio/chat_client/README.md new file mode 100644 index 000000000..0ab16f578 --- /dev/null +++ b/examples/protocols/asio/chat_client/README.md @@ -0,0 +1,19 @@ +# ASIO chat server example + +Simple asio chat client using WiFi STA + +## Example workflow + +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- Once connected and acquired IP address ASIO chat client connects to a corresponding server whose port number and ip are defined through `make menuconfig` +- Chat client receives all messages from other chat clients, also it sends message received from stdin using `make monitor` + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and server ip address and port number +- Start chat server either on host machine or as another ESP device running chat_server example +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connect to your access point +- Receive and send messages to/from other clients on stdin/stdout via serial terminal. + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/asio/chat_client/asio_chat_client_test.py b/examples/protocols/asio/chat_client/asio_chat_client_test.py new file mode 100644 index 000000000..a1cce97c5 --- /dev/null +++ b/examples/protocols/asio/chat_client/asio_chat_client_test.py @@ -0,0 +1,98 @@ +import re +import os +import sys +from socket import * +from threading import Thread +import time + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + +global g_client_response; +global g_msg_to_client; + +g_client_response = "" +g_msg_to_client = " 3XYZ" + +def get_my_ip(): + s1 = socket(AF_INET, SOCK_DGRAM) + s1.connect(("8.8.8.8", 80)) + my_ip = s1.getsockname()[0] + s1.close() + return my_ip + +def chat_server_sketch(my_ip): + global g_client_response + print("Starting the server on {}".format(my_ip)) + port=2222 + s=socket(AF_INET, SOCK_STREAM) + s.bind((my_ip, port)) + s.listen(1) + q,addr=s.accept() + print("connection accepted") + q.send(g_msg_to_client) + data = q.recv(1024) + # check if received initial empty message + if (len(data)>4): + g_client_response = data + else: + g_client_response = q.recv(1024) + print("received from client {}".format(g_client_response)) + s.close() + print("server closed") + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_asio_chat_client(env, extra_data): + """ + steps: | + 1. Test to start simple tcp server + 2. `dut1` joins AP + 3. Test injects server IP to `dut1`via stdin + 4. Test evaluates `dut1` receives a message server placed + 5. Test injects a message to `dut1` to be sent as chat_client message + 6. Test evaluates received test message in host server + """ + global g_client_response + global g_msg_to_client + test_msg="ABC" + dut1 = env.get_dut("chat_client", "examples/protocols/asio/chat_client") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "asio_chatclient.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("asio_chatclient_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("asio_chatclient_size", bin_size//1024) + # 1. start a tcp server on the host + host_ip = get_my_ip() + thread1 = Thread(target = chat_server_sketch, args = (host_ip,)) + thread1.start() + # 2. start the dut test and wait till client gets IP address + dut1.start_app() + data = dut1.expect(re.compile(r" sta ip: ([^,]+),")) + # 3. send host's IP to the client i.e. the `dut1` + dut1.write(host_ip) + # 4. client `dut1` should receive a message + dut1.expect(g_msg_to_client[4:]) # Strip out the front 4 bytes of message len (see chat_message protocol) + # 5. write test message from `dut1` chat_client to the server + dut1.write(test_msg) + while g_client_response == "": + time.sleep(1) + print(g_client_response) + # 6. evaluate host_server received this message + if (g_client_response[4:] == test_msg): + print("PASS: Received correct message") + pass + else: + print("Failure!") + raise ValueError('Wrong data received from asi tcp server: {} (expected:{})'.format(g_client_response, test_msg)) + thread1.join() + +if __name__ == '__main__': + test_examples_protocol_asio_chat_client() diff --git a/examples/protocols/asio/chat_client/components/component.mk b/examples/protocols/asio/chat_client/components/component.mk new file mode 100644 index 000000000..b23b0cb71 --- /dev/null +++ b/examples/protocols/asio/chat_client/components/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_SRCDIRS = . diff --git a/examples/protocols/asio/chat_client/components/wifi_asio.cpp b/examples/protocols/asio/chat_client/components/wifi_asio.cpp new file mode 100644 index 000000000..b90ce26ae --- /dev/null +++ b/examples/protocols/asio/chat_client/components/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.h" +#include "lwip/sys.h" + +#include + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL) ); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK(esp_wifi_start() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +} diff --git a/examples/protocols/asio/chat_client/main/Kconfig.projbuild b/examples/protocols/asio/chat_client/main/Kconfig.projbuild new file mode 100644 index 000000000..32e5f3fcb --- /dev/null +++ b/examples/protocols/asio/chat_client/main/Kconfig.projbuild @@ -0,0 +1,27 @@ +menu "Example Configuration" + +config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config EXAMPLE_PORT + string "asio example port number" + default "2222" + help + Port number used by ASIO example + +config EXAMPLE_SERVER_IP + string "asio example server ip for this client to connect to (leave defalut=FROM_STDIN to enter the server address via serial terminal)" + default "FROM_STDIN" + help + Please set the host name or ip address of corespondant server running + +endmenu diff --git a/examples/protocols/asio/chat_client/main/chat_client.cpp b/examples/protocols/asio/chat_client/main/chat_client.cpp new file mode 100644 index 000000000..e18d8d4e3 --- /dev/null +++ b/examples/protocols/asio/chat_client/main/chat_client.cpp @@ -0,0 +1,165 @@ +// +// chat_client.cpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include "asio.hpp" +#include "chat_message.hpp" + +using asio::ip::tcp; + +typedef std::deque chat_message_queue; + +class chat_client +{ +public: + chat_client(asio::io_context& io_context, + const tcp::resolver::results_type& endpoints) + : io_context_(io_context), + socket_(io_context) + { + do_connect(endpoints); + } + + void write(const chat_message& msg) + { + asio::post(io_context_, + [this, msg]() + { + bool write_in_progress = !write_msgs_.empty(); + write_msgs_.push_back(msg); + if (!write_in_progress) + { + do_write(); + } + }); + } + + void close() + { + asio::post(io_context_, [this]() { socket_.close(); }); + } + +private: + void do_connect(const tcp::resolver::results_type& endpoints) + { + asio::async_connect(socket_, endpoints, + [this](std::error_code ec, tcp::endpoint) + { + if (!ec) + { + do_read_header(); + } + }); + } + + void do_read_header() + { + asio::async_read(socket_, + asio::buffer(read_msg_.data(), chat_message::header_length), + [this](std::error_code ec, std::size_t /*length*/) + { + if (!ec && read_msg_.decode_header()) + { + do_read_body(); + } + else + { + socket_.close(); + } + }); + } + + void do_read_body() + { + asio::async_read(socket_, + asio::buffer(read_msg_.body(), read_msg_.body_length()), + [this](std::error_code ec, std::size_t /*length*/) + { + if (!ec) + { + std::cout.write(read_msg_.body(), read_msg_.body_length()); + std::cout << "\n"; + do_read_header(); + } + else + { + socket_.close(); + } + }); + } + + void do_write() + { + asio::async_write(socket_, + asio::buffer(write_msgs_.front().data(), + write_msgs_.front().length()), + [this](std::error_code ec, std::size_t /*length*/) + { + if (!ec) + { + write_msgs_.pop_front(); + if (!write_msgs_.empty()) + { + do_write(); + } + } + else + { + socket_.close(); + } + }); + } + +private: + asio::io_context& io_context_; + tcp::socket socket_; + chat_message read_msg_; + chat_message_queue write_msgs_; +}; + +void read_line(char * line, int max_chars); + + +void asio_main() +{ + std::string name(CONFIG_EXAMPLE_SERVER_IP); + std::string port(CONFIG_EXAMPLE_PORT); + char line[chat_message::max_body_length + 1] = { 0 }; + + if (name == "FROM_STDIN") { + std::cout << "Please enter ip address of chat server" << std::endl; + if (std::cin.getline(line, chat_message::max_body_length + 1)) { + name = line; + std::cout << "Chat server IP:" << name << std::endl; + } + } + + asio::io_context io_context; + tcp::resolver resolver(io_context); + auto endpoints = resolver.resolve(name, port); + + chat_client c(io_context, endpoints); + + std::thread t([&io_context](){ io_context.run(); }); + + while (std::cin.getline(line, chat_message::max_body_length + 1) && std::string(line) != "exit\n") { + chat_message msg; + msg.body_length(std::strlen(line)); + std::memcpy(msg.body(), line, msg.body_length()); + msg.encode_header(); + c.write(msg); + } + + c.close(); + t.join(); +} diff --git a/examples/protocols/asio/chat_client/main/chat_message.hpp b/examples/protocols/asio/chat_client/main/chat_message.hpp new file mode 100644 index 000000000..629105b05 --- /dev/null +++ b/examples/protocols/asio/chat_client/main/chat_message.hpp @@ -0,0 +1,91 @@ +// +// chat_message.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef CHAT_MESSAGE_HPP +#define CHAT_MESSAGE_HPP + +#include +#include +#include + +class chat_message +{ +public: + enum { header_length = 4 }; + enum { max_body_length = 512 }; + + chat_message() + : body_length_(0) + { + } + + const char* data() const + { + return data_; + } + + char* data() + { + return data_; + } + + std::size_t length() const + { + return header_length + body_length_; + } + + const char* body() const + { + return data_ + header_length; + } + + char* body() + { + return data_ + header_length; + } + + std::size_t body_length() const + { + return body_length_; + } + + void body_length(std::size_t new_length) + { + body_length_ = new_length; + if (body_length_ > max_body_length) + body_length_ = max_body_length; + } + + bool decode_header() + { + char header[header_length + 1] = ""; + std::strncat(header, data_, header_length); + body_length_ = std::atoi(header); + if (body_length_ > max_body_length) + { + body_length_ = 0; + return false; + } + return true; + } + + void encode_header() + { + char header[header_length + 1] = ""; + std::sprintf(header, "%4d", static_cast(body_length_)); + std::memcpy(data_, header, header_length); + } + +private: + char data_[header_length + max_body_length]; + std::size_t body_length_; +}; + +#endif // CHAT_MESSAGE_HPP diff --git a/examples/protocols/asio/chat_client/main/component.mk b/examples/protocols/asio/chat_client/main/component.mk new file mode 100644 index 000000000..61f8990c3 --- /dev/null +++ b/examples/protocols/asio/chat_client/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/protocols/asio/chat_client/sdkconfig.defaults b/examples/protocols/asio/chat_client/sdkconfig.defaults new file mode 100644 index 000000000..d99552e19 --- /dev/null +++ b/examples/protocols/asio/chat_client/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_MAIN_TASK_STACK_SIZE=8192 diff --git a/examples/protocols/asio/chat_server/Makefile b/examples/protocols/asio/chat_server/Makefile new file mode 100644 index 000000000..c69f109c9 --- /dev/null +++ b/examples/protocols/asio/chat_server/Makefile @@ -0,0 +1,7 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := asio_chatserver + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/asio/chat_server/README.md b/examples/protocols/asio/chat_server/README.md new file mode 100644 index 000000000..591efd977 --- /dev/null +++ b/examples/protocols/asio/chat_server/README.md @@ -0,0 +1,22 @@ +# ASIO chat server example + +Simple asio chat server using WiFi STA + +## Example workflow + +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- Once connected and acquired IP address, ASIO chat server is started on port number defined through `make menuconfig` +- Chat server echoes a message (received from any client) to all connected clients + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and port number +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connect to your access point (note the IP address) +- Connect to the server using multiple clients, for example using any option below + - build and run asi chat client on your host machine + - run chat_client asio example on ESP platform + - since chat message consist of ascii size and message, it is possible to + netcat `nc IP PORT` and type for example ` 4ABC` to transmit 'ABC\n' + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/asio/chat_server/asio_chat_server_test.py b/examples/protocols/asio/chat_server/asio_chat_server_test.py new file mode 100644 index 000000000..55746f1f9 --- /dev/null +++ b/examples/protocols/asio/chat_server/asio_chat_server_test.py @@ -0,0 +1,56 @@ +import re +import os +import sys +from socket import * + + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + + + + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_asio_chat_server(env, extra_data): + """ + steps: | + 1. join AP + 2. Start server + 3. Test connects to server and sends a test message + 4. Test evaluates received test message from server + """ + test_msg=" 4ABC\n" + dut1 = env.get_dut("chat_server", "examples/protocols/asio/chat_server") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "asio_chatserver.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("asio_chatserver_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("asio_chatserver_size", bin_size//1024) + # 1. start test + dut1.start_app() + # 2. get the server IP address + data = dut1.expect(re.compile(r" sta ip: ([^,]+),")) + # 3. create tcp client and connect to server + cli = socket(AF_INET,SOCK_STREAM) + cli.connect((data[0],80)) + cli.send(test_msg) + data = cli.recv(1024) + # 4. check the message received back from the server + if (data == test_msg): + print("PASS: Received correct message {}".format(data)) + pass + else: + print("Failure!") + raise ValueError('Wrong data received from asi tcp server: {} (expoected:{})'.format(data, test_msg)) + + +if __name__ == '__main__': + test_examples_protocol_asio_chat_server() diff --git a/examples/protocols/asio/chat_server/components/component.mk b/examples/protocols/asio/chat_server/components/component.mk new file mode 100644 index 000000000..b23b0cb71 --- /dev/null +++ b/examples/protocols/asio/chat_server/components/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_SRCDIRS = . diff --git a/examples/protocols/asio/chat_server/components/wifi_asio.cpp b/examples/protocols/asio/chat_server/components/wifi_asio.cpp new file mode 100644 index 000000000..b90ce26ae --- /dev/null +++ b/examples/protocols/asio/chat_server/components/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.h" +#include "lwip/sys.h" + +#include + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL) ); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK(esp_wifi_start() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +} diff --git a/examples/protocols/asio/chat_server/main/Kconfig.projbuild b/examples/protocols/asio/chat_server/main/Kconfig.projbuild new file mode 100644 index 000000000..c0d6fd9cd --- /dev/null +++ b/examples/protocols/asio/chat_server/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Example Configuration" + +config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config EXAMPLE_PORT + string "asio example port number" + default "80" + help + Port number used by ASIO example + +endmenu diff --git a/examples/protocols/asio/chat_server/main/chat_message.hpp b/examples/protocols/asio/chat_server/main/chat_message.hpp new file mode 100644 index 000000000..629105b05 --- /dev/null +++ b/examples/protocols/asio/chat_server/main/chat_message.hpp @@ -0,0 +1,91 @@ +// +// chat_message.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef CHAT_MESSAGE_HPP +#define CHAT_MESSAGE_HPP + +#include +#include +#include + +class chat_message +{ +public: + enum { header_length = 4 }; + enum { max_body_length = 512 }; + + chat_message() + : body_length_(0) + { + } + + const char* data() const + { + return data_; + } + + char* data() + { + return data_; + } + + std::size_t length() const + { + return header_length + body_length_; + } + + const char* body() const + { + return data_ + header_length; + } + + char* body() + { + return data_ + header_length; + } + + std::size_t body_length() const + { + return body_length_; + } + + void body_length(std::size_t new_length) + { + body_length_ = new_length; + if (body_length_ > max_body_length) + body_length_ = max_body_length; + } + + bool decode_header() + { + char header[header_length + 1] = ""; + std::strncat(header, data_, header_length); + body_length_ = std::atoi(header); + if (body_length_ > max_body_length) + { + body_length_ = 0; + return false; + } + return true; + } + + void encode_header() + { + char header[header_length + 1] = ""; + std::sprintf(header, "%4d", static_cast(body_length_)); + std::memcpy(data_, header, header_length); + } + +private: + char data_[header_length + max_body_length]; + std::size_t body_length_; +}; + +#endif // CHAT_MESSAGE_HPP diff --git a/examples/protocols/asio/chat_server/main/chat_server.cpp b/examples/protocols/asio/chat_server/main/chat_server.cpp new file mode 100644 index 000000000..05740f479 --- /dev/null +++ b/examples/protocols/asio/chat_server/main/chat_server.cpp @@ -0,0 +1,214 @@ +// +// chat_server.cpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include +#include +#include +#include "asio.hpp" +#include "chat_message.hpp" + +using asio::ip::tcp; + +//---------------------------------------------------------------------- + +typedef std::deque chat_message_queue; + +//---------------------------------------------------------------------- + +class chat_participant +{ +public: + virtual ~chat_participant() {} + virtual void deliver(const chat_message& msg) = 0; +}; + +typedef std::shared_ptr chat_participant_ptr; + +//---------------------------------------------------------------------- + +class chat_room +{ +public: + void join(chat_participant_ptr participant) + { + participants_.insert(participant); + for (auto msg: recent_msgs_) + participant->deliver(msg); + } + + void leave(chat_participant_ptr participant) + { + participants_.erase(participant); + } + + void deliver(const chat_message& msg) + { + recent_msgs_.push_back(msg); + while (recent_msgs_.size() > max_recent_msgs) + recent_msgs_.pop_front(); + + for (auto participant: participants_) + participant->deliver(msg); + } + +private: + std::set participants_; + enum { max_recent_msgs = 100 }; + chat_message_queue recent_msgs_; +}; + +//---------------------------------------------------------------------- + +class chat_session + : public chat_participant, + public std::enable_shared_from_this +{ +public: + chat_session(tcp::socket socket, chat_room& room) + : socket_(std::move(socket)), + room_(room) + { + } + + void start() + { + room_.join(shared_from_this()); + do_read_header(); + } + + void deliver(const chat_message& msg) + { + bool write_in_progress = !write_msgs_.empty(); + write_msgs_.push_back(msg); + if (!write_in_progress) + { + do_write(); + } + } + +private: + void do_read_header() + { + auto self(shared_from_this()); + asio::async_read(socket_, + asio::buffer(read_msg_.data(), chat_message::header_length), + [this, self](std::error_code ec, std::size_t /*length*/) + { + if (!ec && read_msg_.decode_header()) + { + do_read_body(); + } + else + { + room_.leave(shared_from_this()); + } + }); + } + + void do_read_body() + { + auto self(shared_from_this()); + asio::async_read(socket_, + asio::buffer(read_msg_.body(), read_msg_.body_length()), + [this, self](std::error_code ec, std::size_t /*length*/) + { + if (!ec) + { + room_.deliver(read_msg_); + do_read_header(); + } + else + { + room_.leave(shared_from_this()); + } + }); + } + + void do_write() + { + auto self(shared_from_this()); + asio::async_write(socket_, + asio::buffer(write_msgs_.front().data(), + write_msgs_.front().length()), + [this, self](std::error_code ec, std::size_t /*length*/) + { + if (!ec) + { + write_msgs_.pop_front(); + if (!write_msgs_.empty()) + { + do_write(); + } + } + else + { + room_.leave(shared_from_this()); + } + }); + } + + tcp::socket socket_; + chat_room& room_; + chat_message read_msg_; + chat_message_queue write_msgs_; +}; + +//---------------------------------------------------------------------- + +class chat_server +{ +public: + chat_server(asio::io_context& io_context, + const tcp::endpoint& endpoint) + : acceptor_(io_context, endpoint) + { + do_accept(); + } + +private: + void do_accept() + { + acceptor_.async_accept( + [this](std::error_code ec, tcp::socket socket) + { + if (!ec) + { + std::make_shared(std::move(socket), room_)->start(); + } + + do_accept(); + }); + } + + tcp::acceptor acceptor_; + chat_room room_; +}; + +//---------------------------------------------------------------------- + +int asio_main() +{ + asio::io_context io_context; + + std::list servers; + + { + tcp::endpoint endpoint(tcp::v4(), std::atoi(CONFIG_EXAMPLE_PORT)); + servers.emplace_back(io_context, endpoint); + } + + io_context.run(); + + return 0; +} diff --git a/examples/protocols/asio/chat_server/main/component.mk b/examples/protocols/asio/chat_server/main/component.mk new file mode 100644 index 000000000..61f8990c3 --- /dev/null +++ b/examples/protocols/asio/chat_server/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/protocols/asio/chat_server/sdkconfig.defaults b/examples/protocols/asio/chat_server/sdkconfig.defaults new file mode 100644 index 000000000..d99552e19 --- /dev/null +++ b/examples/protocols/asio/chat_server/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_MAIN_TASK_STACK_SIZE=8192 diff --git a/examples/protocols/asio/tcp_echo_server/Makefile b/examples/protocols/asio/tcp_echo_server/Makefile new file mode 100644 index 000000000..2a2839a54 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/Makefile @@ -0,0 +1,7 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := asio_tcp_echoserver + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/asio/tcp_echo_server/README.md b/examples/protocols/asio/tcp_echo_server/README.md new file mode 100644 index 000000000..a32fd93ea --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/README.md @@ -0,0 +1,18 @@ +# ASIO tcp echo server example + +Simple asio echo server using WiFi STA + +## Example workflow + +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- Once connected and acquired IP address, ASIO tcp server is started on port number defined through `make menuconfig` +- Server receives and echoes back messages transmitted from client + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and port number +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connect to your access point (note the IP address) +- You can now send a tcp message and check it is repeated, for example using netcat `nc IP PORT` + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/asio/tcp_echo_server/asio_tcp_server_test.py b/examples/protocols/asio/tcp_echo_server/asio_tcp_server_test.py new file mode 100644 index 000000000..1ab432e62 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/asio_tcp_server_test.py @@ -0,0 +1,59 @@ +import re +import os +import sys +from socket import * + + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + + + + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_asio_tcp_server(env, extra_data): + """ + steps: | + 1. join AP + 2. Start server + 3. Test connects to server and sends a test message + 4. Test evaluates received test message from server + 5. Test evaluates received test message on server stdout + """ + test_msg="echo message from client to server" + dut1 = env.get_dut("tcp_echo_server", "examples/protocols/asio/tcp_echo_server") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "asio_tcp_echoserver.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("asio_tcp_echoserver_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("asio_tcp_echoserver_size", bin_size//1024) + # 1. start test + dut1.start_app() + # 2. get the server IP address + data = dut1.expect(re.compile(r" sta ip: ([^,]+),")) + # 3. create tcp client and connect to server + cli = socket(AF_INET,SOCK_STREAM) + cli.connect((data[0],80)) + cli.send(test_msg) + data = cli.recv(1024) + # 4. check the message received back from the server + if (data == test_msg): + print("PASS: Received correct message") + pass + else: + print("Failure!") + raise ValueError('Wrong data received from asi tcp server: {} (expoected:{})'.format(data, test_msg)) + # 5. check the client message appears also on server terminal + dut1.expect(test_msg) + + +if __name__ == '__main__': + test_examples_protocol_asio_tcp_server() diff --git a/examples/protocols/asio/tcp_echo_server/components/component.mk b/examples/protocols/asio/tcp_echo_server/components/component.mk new file mode 100644 index 000000000..b23b0cb71 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/components/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_SRCDIRS = . diff --git a/examples/protocols/asio/tcp_echo_server/components/wifi_asio.cpp b/examples/protocols/asio/tcp_echo_server/components/wifi_asio.cpp new file mode 100644 index 000000000..b90ce26ae --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/components/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.h" +#include "lwip/sys.h" + +#include + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL) ); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK(esp_wifi_start() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +} diff --git a/examples/protocols/asio/tcp_echo_server/main/Kconfig.projbuild b/examples/protocols/asio/tcp_echo_server/main/Kconfig.projbuild new file mode 100644 index 000000000..c0d6fd9cd --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Example Configuration" + +config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config EXAMPLE_PORT + string "asio example port number" + default "80" + help + Port number used by ASIO example + +endmenu diff --git a/examples/protocols/asio/tcp_echo_server/main/component.mk b/examples/protocols/asio/tcp_echo_server/main/component.mk new file mode 100644 index 000000000..8c8d52abc --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/main/component.mk @@ -0,0 +1,9 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# + diff --git a/examples/protocols/asio/tcp_echo_server/main/echo_server.cpp b/examples/protocols/asio/tcp_echo_server/main/echo_server.cpp new file mode 100644 index 000000000..22f171f53 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/main/echo_server.cpp @@ -0,0 +1,91 @@ +#include "asio.hpp" +#include +#include + +using asio::ip::tcp; + +class session + : public std::enable_shared_from_this +{ +public: + session(tcp::socket socket) + : socket_(std::move(socket)) + { + } + + void start() + { + do_read(); + } + +private: + void do_read() + { + auto self(shared_from_this()); + socket_.async_read_some(asio::buffer(data_, max_length), + [this, self](std::error_code ec, std::size_t length) + { + if (!ec) + { + std::cout << data_ << std::endl; + do_write(length); + } + }); + } + + void do_write(std::size_t length) + { + auto self(shared_from_this()); + asio::async_write(socket_, asio::buffer(data_, length), + [this, self](std::error_code ec, std::size_t length) + { + if (!ec) + { + do_read(); + } + }); + } + + tcp::socket socket_; + enum { max_length = 1024 }; + char data_[max_length]; +}; + +class server +{ +public: + server(asio::io_context& io_context, short port) + : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) + { + do_accept(); + } + +private: + void do_accept() + { + acceptor_.async_accept( + [this](std::error_code ec, tcp::socket socket) + { + if (!ec) + { + std::make_shared(std::move(socket))->start(); + } + + do_accept(); + }); + } + + tcp::acceptor acceptor_; +}; + + +void asio_main() +{ + + asio::io_context io_context; + + server s(io_context, std::atoi(CONFIG_EXAMPLE_PORT)); + + io_context.run(); + +} \ No newline at end of file diff --git a/examples/protocols/asio/tcp_echo_server/sdkconfig.defaults b/examples/protocols/asio/tcp_echo_server/sdkconfig.defaults new file mode 100644 index 000000000..d99552e19 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_MAIN_TASK_STACK_SIZE=8192 diff --git a/examples/protocols/asio/udp_echo_server/Makefile b/examples/protocols/asio/udp_echo_server/Makefile new file mode 100644 index 000000000..c659aeae0 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/Makefile @@ -0,0 +1,7 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := asio_udp_echoserver + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/asio/udp_echo_server/README.md b/examples/protocols/asio/udp_echo_server/README.md new file mode 100644 index 000000000..6cf2c2ba3 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/README.md @@ -0,0 +1,18 @@ +# ASIO udp echo server example + +Simple asio echo server using WiFi STA + +## Example workflow + +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- Once connected and acquired IP address, ASIO udp server is started on port number defined through `make menuconfig` +- Server receives and echoes back messages transmitted from client + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and port number +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connect to your access point (note the IP address) +- You can now send a udp message and check it is repeated, for example using netcat `nc -u IP PORT` + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/asio/udp_echo_server/asio_udp_server_test.py b/examples/protocols/asio/udp_echo_server/asio_udp_server_test.py new file mode 100644 index 000000000..490a8007a --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/asio_udp_server_test.py @@ -0,0 +1,58 @@ +import re +import os +import sys +from socket import * + + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + + + + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_asio_udp_server(env, extra_data): + """ + steps: | + 1. join AP + 2. Start server + 3. Test connects to server and sends a test message + 4. Test evaluates received test message from server + 5. Test evaluates received test message on server stdout + """ + test_msg="echo message from client to server" + dut1 = env.get_dut("udp_echo_server", "examples/protocols/asio/udp_echo_server") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "asio_udp_echoserver.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("asio_udp_echoserver_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("asio_udp_echoserver_size", bin_size//1024) + # 1. start test + dut1.start_app() + # 2. get the server IP address + data = dut1.expect(re.compile(r" sta ip: ([^,]+),")) + # 3. create tcp client and connect to server + cli = socket(AF_INET, SOCK_DGRAM) + cli.connect((data[0], 80)) + cli.send(test_msg) + data = cli.recv(1024) + # 4. check the message received back from the server + if (data == test_msg): + print("PASS: Received correct message") + pass + else: + print("Failure!") + raise ValueError('Wrong data received from asi udp server: {} (expoected:{})'.format(data, test_msg)) + # 5. check the client message appears also on server terminal + dut1.expect(test_msg) + +if __name__ == '__main__': + test_examples_protocol_asio_udp_server() diff --git a/examples/protocols/asio/udp_echo_server/components/component.mk b/examples/protocols/asio/udp_echo_server/components/component.mk new file mode 100644 index 000000000..b23b0cb71 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/components/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_SRCDIRS = . diff --git a/examples/protocols/asio/udp_echo_server/components/wifi_asio.cpp b/examples/protocols/asio/udp_echo_server/components/wifi_asio.cpp new file mode 100644 index 000000000..b90ce26ae --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/components/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.h" +#include "lwip/sys.h" + +#include + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL) ); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK(esp_wifi_start() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +} diff --git a/examples/protocols/asio/udp_echo_server/main/Kconfig.projbuild b/examples/protocols/asio/udp_echo_server/main/Kconfig.projbuild new file mode 100644 index 000000000..c0d6fd9cd --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Example Configuration" + +config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config EXAMPLE_PORT + string "asio example port number" + default "80" + help + Port number used by ASIO example + +endmenu diff --git a/examples/protocols/asio/udp_echo_server/main/component.mk b/examples/protocols/asio/udp_echo_server/main/component.mk new file mode 100644 index 000000000..61f8990c3 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/protocols/asio/udp_echo_server/main/udp_echo_server.cpp b/examples/protocols/asio/udp_echo_server/main/udp_echo_server.cpp new file mode 100644 index 000000000..5b6607d73 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/main/udp_echo_server.cpp @@ -0,0 +1,69 @@ +// +// async_udp_echo_server.cpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include + +#include "asio.hpp" + +using asio::ip::udp; + +class server +{ +public: + server(asio::io_context& io_context, short port) + : socket_(io_context, udp::endpoint(udp::v4(), port)) + { + do_receive(); + } + + void do_receive() + { + socket_.async_receive_from( + asio::buffer(data_, max_length), sender_endpoint_, + [this](std::error_code ec, std::size_t bytes_recvd) + { + if (!ec && bytes_recvd > 0) + { + std::cout << data_ << std::endl; + do_send(bytes_recvd); + } + else + { + do_receive(); + } + }); + } + + void do_send(std::size_t length) + { + socket_.async_send_to( + asio::buffer(data_, length), sender_endpoint_, + [this](std::error_code /*ec*/, std::size_t bytes /*bytes_sent*/) + { + do_receive(); + }); + } + +private: + udp::socket socket_; + udp::endpoint sender_endpoint_; + enum { max_length = 1024 }; + char data_[max_length]; +}; + +void asio_main() +{ + asio::io_context io_context; + + server s(io_context, std::atoi(CONFIG_EXAMPLE_PORT)); + + io_context.run(); +} diff --git a/examples/protocols/asio/udp_echo_server/sdkconfig.defaults b/examples/protocols/asio/udp_echo_server/sdkconfig.defaults new file mode 100644 index 000000000..d99552e19 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_MAIN_TASK_STACK_SIZE=8192 diff --git a/examples/protocols/asio/wifi_init/wifi_asio.cpp b/examples/protocols/asio/wifi_init/wifi_asio.cpp new file mode 100644 index 000000000..b90ce26ae --- /dev/null +++ b/examples/protocols/asio/wifi_init/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.h" +#include "lwip/sys.h" + +#include + +/* The examples use simple WiFi configuration that you can set via + 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL) ); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK(esp_wifi_start() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +}