asio: initial idf port of asio library without ssl

* Original commit: espressif/esp-idf@1ef13c524c
This commit is contained in:
David Cermak
2018-05-29 11:25:24 +02:00
committed by gabsuren
parent 51a50db0fd
commit 5472d5c52f
45 changed files with 2068 additions and 0 deletions

View File

@ -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

View File

@ -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.

View File

@ -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()

View File

@ -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 = .

View File

@ -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 <cstring>
/* 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();
}

View File

@ -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

View File

@ -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 <cstdlib>
#include <deque>
#include <iostream>
#include <thread>
#include "asio.hpp"
#include "chat_message.hpp"
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);
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();
}

View File

@ -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 <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

@ -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.
#

View File

@ -0,0 +1 @@
CONFIG_MAIN_TASK_STACK_SIZE=8192