examples: Asio tests consolidation to use loopback interface

Closes IDF-3072
This commit is contained in:
Suren Gabrielyan 2021-07-02 08:58:47 +04:00
parent b9ce1ed06d
commit a06357ab62
28 changed files with 457 additions and 626 deletions

View File

@ -7,4 +7,4 @@ cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(asio_chat_client)
project(asio_chat)

View File

@ -2,7 +2,7 @@
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := asio_chat_server
PROJECT_NAME := asio_chat
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common

View File

@ -0,0 +1,62 @@
# Asio chat client and server examples
(See the README.md file in the upper level 'examples' directory for more information about examples.)
The application aims to demonstrate a simple use of Asio library in different modes.
In project settings it could be configured to run either a Asio chat server, a Asio chat client, or both.
## How to use example
The example is configured by default as an Asio chat client.
Note that the example uses string representation of IP addresses and ports.
You can find the upstream asio chat implementation [here] https://github.com/chriskohlhoff/asio/tree/master/asio/src/examples/cpp11/chat
### Asio Client
In the client mode, the example connects to the configured address, sends the message, which was inserted as an input in the terminal, and receives a response.
### Asio Server
In the server mode, Asio chat server with a specified port number is created and being polled till a connection request from the client arrives.
Chat server echoes a message (received from any client) to all connected clients.
## Configure the project
```
idf.py menuconfig
```
Set following parameters under Example Configuration Options:
* Set `EXAMPLE_CHAT_SERVER` to use the example as an ASIO chat server
* Configure `EXAMPLE_CHAT_SERVER_BIND_PORT` to the port number.
* Set `EXAMPLE_CHAT_CLIENT` to use the example as an ASIO chat client
* Configure `EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS` to a string representation of the address to connect the client to.
* Configure `EXAMPLE_CHAT_CLIENT_CONNECT_PORT` to the port number.
* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more d etails.
## Running the example in server mode
- Configure the example according "Configure the project" section.
- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
- Wait for the board to connect to WiFi or Ethernet (note the IP address).
- Connect to the server using multiple clients, for example using any option below.
- build and run asio chat client on your host machine
- run chat_client asio example on ESP platform
- since chat messages consists of ASCII size and message, it is possible to
netcat `nc IP PORT` and type for example ` 4ABC<CR>` to transmit 'ABC\n'
## Running the example in client mode
- Configure the example according "Configure the project" section.
- Start chat server either on host machine or as another ESP device running chat_server example.
- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
- Wait for the board to connect to WiFi or Ethernet.
- 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.

View File

@ -0,0 +1,29 @@
# 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.
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals
import re
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
def test_examples_asio_chat(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None
msg = 'asio-chat: received hi'
dut = env.get_dut('asio_chat', 'examples/protocols/asio/asio_chat')
# start the test and expect the client to receive back it's original data
dut.start_app()
dut.expect(re.compile(r'{}'.format('Waiting for input')), timeout=30)
dut.write(msg)
dut.write('exit')
dut.expect(re.compile(r'{}'.format(msg)), timeout=30)
if __name__ == '__main__':
test_examples_asio_chat()

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "asio_chat.cpp"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,39 @@
menu "Example Configuration"
config EXAMPLE_CHAT_SERVER
bool "Asio example chat server"
default n
help
This example will setup a chat server, binds it to the specified address
and starts listening.
if EXAMPLE_CHAT_SERVER
config EXAMPLE_CHAT_SERVER_BIND_PORT
string "Asio example server bind port"
default "3344"
help
Server listener's socket would be bound to this port.
endif
config EXAMPLE_CHAT_CLIENT
bool "Asio example chat client"
default y
help
This example will setup an asio chat client.
and sends the data.
if EXAMPLE_CHAT_CLIENT
config EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS
string "Client connection address"
default "192.168.0.1"
help
Client's socket would connect to this address/host.
config EXAMPLE_CHAT_CLIENT_CONNECT_PORT
string "Client connection port"
default "3344"
help
Client's connection port.
endif
endmenu

View File

@ -0,0 +1,119 @@
/* ASIO chat server client example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "protocol_examples_common.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "server.hpp"
#include "client.hpp"
#include <thread>
#include <pthread.h>
using asio::ip::tcp;
static const char *TAG = "asio-chat";
// This variable is necessary for `python test` execution, it provides synchronisation between server/client(as server should be started before client)
std::mutex server_ready;
#ifdef CONFIG_EXAMPLE_CHAT_CLIENT
static void get_string(char *line, size_t size)
{
int count = 0;
while (count < size) {
int c = fgetc(stdin);
if (c == '\n') {
line[count] = '\0';
break;
} else if (c > 0 && c < 127) {
line[count] = c;
++count;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void start_client(void)
{
const std::string port(CONFIG_EXAMPLE_CHAT_CLIENT_CONNECT_PORT);
const std::string name(CONFIG_EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS);
asio::io_context io_context;
char line[128];
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(name, port);
chat_client c(io_context, endpoints);
#ifdef CONFIG_EXAMPLE_CHAT_SERVER
std::lock_guard<std::mutex> guard(server_ready);
#endif
std::thread t([&io_context]() { try {
io_context.run();
} catch (const std::exception &e) {
ESP_LOGE(TAG, "Exception occured during client thread execution %s", e.what());
}
catch (...) {
ESP_LOGE(TAG, "Unknown exception");
}});
do {
ESP_LOGI(TAG, "CLIENT: Waiting for input");
get_string(line, sizeof(line));
chat_message msg;
msg.body_length(std::strlen(line));
std::memcpy(msg.body(), line, msg.body_length());
msg.encode_header();
c.write(msg);
sleep(1);
} while (strcmp(line, "exit") != 0);
c.close();
t.join();
}
#endif // CONFIG_EXAMPLE_CHAT_CLIENT
extern "C" void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
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());
try {
#ifdef CONFIG_EXAMPLE_CHAT_SERVER
asio::io_context io_context;
chat_server server(io_context, tcp::endpoint(tcp::v4(), std::atoi(CONFIG_EXAMPLE_CHAT_SERVER_BIND_PORT)));
std::thread t = std::thread([&io_context]() { // Chat server starting here
try {
io_context.run();
} catch (const std::exception &e) {
ESP_LOGE(TAG, "Exception occured during server thread execution %s", e.what());
}
catch (...) {
ESP_LOGE(TAG, "Unknown exception");
}});;
#endif
#ifdef CONFIG_EXAMPLE_CHAT_CLIENT
start_client();
#endif
#ifdef CONFIG_EXAMPLE_CHAT_SERVER
t.join();
#endif
} catch (const std::exception &e) {
ESP_LOGE(TAG, "Exception occured during run %s", e.what());
} catch (...) {
ESP_LOGE(TAG, "Unknown exception");
}
ESP_ERROR_CHECK(example_disconnect());
}

View File

@ -0,0 +1,126 @@
//
// client.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_CLIENT_HPP
#define CHAT_CLIENT_HPP
#include <deque>
#include "asio.hpp"
#include "chat_message.hpp"
typedef std::deque<chat_message> chat_message_queue;
class chat_client
{
public:
chat_client(asio::io_context& io_context,
const asio::ip::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 asio::ip::tcp::resolver::results_type& endpoints)
{
asio::async_connect(socket_, endpoints,
[this](std::error_code ec, asio::ip::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)
{
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_;
asio::ip::tcp::socket socket_;
chat_message read_msg_;
chat_message_queue write_msgs_;
};
#endif // CHAT_CLIENT_HPP

View File

@ -1,5 +1,5 @@
//
// chat_server.cpp
// server.hpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
@ -8,28 +8,25 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <cstdlib>
#include <deque>
#include <iostream>
#ifndef CHAT_SERVER_HPP
#define CHAT_SERVER_HPP
#include <list>
#include <memory>
#include <set>
#include <deque>
#include <utility>
#include "asio.hpp"
#include "chat_message.hpp"
#include "protocol_examples_common.h"
#include "esp_event.h"
#include "nvs_flash.h"
using asio::ip::tcp;
//----------------------------------------------------------------------
typedef std::deque<chat_message> chat_message_queue;
extern std::mutex server_ready;
//----------------------------------------------------------------------
class chat_participant
{
public:
@ -74,12 +71,13 @@ private:
//----------------------------------------------------------------------
class chat_session
: public chat_participant,
public std::enable_shared_from_this<chat_session>
{
public:
chat_session(tcp::socket socket, chat_room& room)
chat_session(asio::ip::tcp::socket socket, chat_room& room)
: socket_(std::move(socket)),
room_(room)
{
@ -117,56 +115,57 @@ private:
{
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_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)
{
ESP_LOGD("asio-chat:", "%s", read_msg_.body());
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());
}
});
}
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_;
};
asio::ip::tcp::socket socket_;
chat_room& room_;
chat_message read_msg_;
chat_message_queue write_msgs_;
};
//----------------------------------------------------------------------
@ -174,7 +173,7 @@ class chat_server
{
public:
chat_server(asio::io_context& io_context,
const tcp::endpoint& endpoint)
const asio::ip::tcp::endpoint& endpoint)
: acceptor_(io_context, endpoint)
{
do_accept();
@ -183,49 +182,21 @@ public:
private:
void do_accept()
{
std::lock_guard<std::mutex> guard(server_ready);
acceptor_.async_accept(
[this](std::error_code ec, tcp::socket socket)
{
if (!ec)
{
std::make_shared<chat_session>(std::move(socket), room_)->start();
}
[this](std::error_code ec, asio::ip::tcp::socket socket)
{
if (!ec)
{
std::make_shared<chat_session>(std::move(socket), room_)->start();
}
do_accept();
});
do_accept();
});
}
tcp::acceptor acceptor_;
asio::ip::tcp::acceptor acceptor_;
chat_room room_;
};
//----------------------------------------------------------------------
extern "C" void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
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());
/* This helper function configures blocking UART I/O */
ESP_ERROR_CHECK(example_configure_stdin_stdout());
asio::io_context io_context;
std::list<chat_server> servers;
{
tcp::endpoint endpoint(tcp::v4(), std::atoi(CONFIG_EXAMPLE_PORT));
servers.emplace_back(io_context, endpoint);
}
std::cout << "ASIO engine is up and running" << std::endl;
io_context.run();
}
#endif // CHAT_SERVER_HPP

View File

@ -0,0 +1,6 @@
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=n
CONFIG_EXAMPLE_CHAT_CLIENT=y
CONFIG_EXAMPLE_CHAT_SERVER=y
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
CONFIG_EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS="localhost"

View File

@ -0,0 +1,2 @@
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@ -1,9 +0,0 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := asio_chat_client
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
include $(IDF_PATH)/make/project.mk

View File

@ -1,20 +0,0 @@
# Asio chat client example
Simple Asio chat client using WiFi STA or Ethernet.
## Example workflow
- Wi-Fi or Ethernet connection is established, and IP address is obtained.
- Asio chat client connects to the corresponding server whose port number and IP are defined through the project configuration menu.
- Chat client receives all messages from other chat clients, also it sends message received from stdin using `idf.py -p PORT monitor`.
## Running the example
- Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
- Set server IP address and port number in menuconfig, "Example configuration".
- Start chat server either on host machine or as another ESP device running chat_server example.
- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
- Wait for the board to connect to WiFi or Ethernet.
- 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.

View File

@ -1,94 +0,0 @@
import os
import re
import socket
import time
from threading import Thread
import ttfw_idf
global g_client_response
global g_msg_to_client
g_client_response = b''
g_msg_to_client = b' 3XYZ'
def get_my_ip():
s1 = socket.socket(socket.AF_INET, socket.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.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(600)
s.bind((my_ip, port))
s.listen(1)
q,addr = s.accept()
print('connection accepted')
q.settimeout(30)
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')
@ttfw_idf.idf_example_test(env_tag='Example_WIFI_Protocols')
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', dut_class=ttfw_idf.ESP32DUT)
# check and log bin size
binary_file = os.path.join(dut1.app.binary_path, 'asio_chat_client.bin')
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance('asio_chat_client_size', '{}KB'.format(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()
dut1.expect(re.compile(r' IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)'), timeout=30)
# 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:].decode()) # 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 len(g_client_response) == 0:
time.sleep(1)
g_client_response = g_client_response.decode()
print(g_client_response)
# 6. evaluate host_server received this message
if (g_client_response[4:7] == 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[4:7], test_msg))
thread1.join()
if __name__ == '__main__':
test_examples_protocol_asio_chat_client()

View File

@ -1,2 +0,0 @@
idf_component_register(SRCS "chat_client.cpp"
INCLUDE_DIRS ".")

View File

@ -1,16 +0,0 @@
menu "Example Configuration"
config EXAMPLE_PORT
string "Asio example server port number"
default "2222"
help
Port number used by Asio example.
config EXAMPLE_SERVER_IP
string "Asio example server ip"
default "FROM_STDIN"
help
Asio example server ip for this client to connect to.
Leave default "FROM_STDIN" to enter the server address via serial terminal.
endmenu

View File

@ -1,183 +0,0 @@
//
// 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 <cstdlib>
#include <deque>
#include <iostream>
#include <thread>
#include "asio.hpp"
#include "chat_message.hpp"
#include "protocol_examples_common.h"
#include "esp_event.h"
#include "nvs_flash.h"
using asio::ip::tcp;
typedef std::deque<chat_message> 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);
extern "C" void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
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());
/* This helper function configures blocking UART I/O */
ESP_ERROR_CHECK(example_configure_stdin_stdout());
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") {
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();
ESP_ERROR_CHECK(example_disconnect());
}

View File

@ -1,7 +0,0 @@
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
#
# Partition Table
#
# Leave some room for larger apps without needing to reduce other features
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y

View File

@ -1,10 +0,0 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
# (Not part of the boilerplate)
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(asio_chat_server)

View File

@ -1,23 +0,0 @@
# Asio chat server example
Simple Asio chat server using WiFi STA or Ethernet.
## Example workflow
- Wi-Fi or Ethernet connection is established, and IP address is obtained.
- Asio chat server is started on port number defined through the project configuration.
- Chat server echoes a message (received from any client) to all connected clients.
## Running the example
- Open project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
- Set server port number in menuconfig, "Example configuration".
- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
- Wait for the board to connect to WiFi or Ethernet (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<CR>` to transmit 'ABC\n'
See the README.md file in the upper level 'examples' directory for more information about examples.

View File

@ -1,44 +0,0 @@
import os
import re
import socket
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_WIFI_Protocols')
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 = b' 4ABC\n'
dut1 = env.get_dut('chat_server', 'examples/protocols/asio/chat_server', dut_class=ttfw_idf.ESP32DUT)
# check and log bin size
binary_file = os.path.join(dut1.app.binary_path, 'asio_chat_server.bin')
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance('asio_chat_server_bin_size', '{}KB'.format(bin_size // 1024))
# 1. start test
dut1.start_app()
# 2. get the server IP address
data = dut1.expect(re.compile(r' IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)'), timeout=30)
# 3. create tcp client and connect to server
dut1.expect('ASIO engine is up and running', timeout=1)
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cli.settimeout(30)
cli.connect((data[0], 2222))
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()

View File

@ -1,2 +0,0 @@
idf_component_register(SRCS "chat_server.cpp"
INCLUDE_DIRS ".")

View File

@ -1,9 +0,0 @@
menu "Example Configuration"
config EXAMPLE_PORT
string "Asio example server port number"
default "2222"
help
Port number used by Asio example
endmenu

View File

@ -1,91 +0,0 @@
//
// 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 <cstdio>
#include <cstdlib>
#include <cstring>
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<int>(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

View File

@ -1,8 +0,0 @@
#
# 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.
#

View File

@ -1,7 +0,0 @@
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
#
# Partition Table
#
# Leave some room for larger apps without needing to reduce other features
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y