From 7d8a77c61bfa04992883ebbb744dba94680fea5f Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Fri, 14 Apr 2023 21:37:30 -0400 Subject: [PATCH] Introduce the tcp_client example This will be the basis for or WiFi connectivity and stratum client --- CMakeLists.txt | 6 +- README.md | 72 ++++++++++++++++++++++ example_test.py | 133 +++++++++++++++++++++++++++++++++++++++++ main/CMakeLists.txt | 36 +++++------ main/Kconfig.projbuild | 48 +++++++++++++++ main/main.c | 24 -------- main/tcp_client.c | 133 +++++++++++++++++++++++++++++++++++++++++ sdkconfig.ci | 2 + 8 files changed, 411 insertions(+), 43 deletions(-) create mode 100755 README.md create mode 100755 example_test.py delete mode 100755 main/main.c create mode 100755 main/tcp_client.c create mode 100755 sdkconfig.ci diff --git a/CMakeLists.txt b/CMakeLists.txt index bcfe27c5b..b3d1afc00 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,5 +2,9 @@ # 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(I2c_test) +project(tcp_client) diff --git a/README.md b/README.md new file mode 100755 index 000000000..0ac216d4a --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | + + +# TCP Client example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +The application creates a TCP socket and tries to connect to the server with predefined IP address and port number. When a connection is successfully established, the application sends message and waits for the answer. After the server's reply, application prints received reply as ASCII text, waits for 2 seconds and sends another message. + +## How to use example + +In order to create TCP server that communicates with TCP Client example, choose one of the following options. + +There are many host-side tools which can be used to interact with the UDP/TCP server/client. +One command line tool is [netcat](http://netcat.sourceforge.net) which can send and receive many kinds of packets. +Note: please replace `192.168.0.167 3333` with desired IPV4/IPV6 address (displayed in monitor console) and port number in the following command. + +In addition to those tools, simple Python scripts can be found under sockets/scripts directory. Every script is designed to interact with one of the examples. + +### TCP server using netcat +``` +nc -l 192.168.0.167 3333 +``` + +### Python scripts +Script example_test.py could be used as a counter part to the tcp-client project, ip protocol name (IPv4 or IPv6) shall be stated as argument. Example: + +``` +python example_test.py IPv4 +``` +Note that this script is used in automated tests, as well, so the IDF test framework packages need to be imported; +please add `$IDF_PATH/tools/ci/python_packages` to `PYTHONPATH`. + +## Hardware Required + +This example can be run on any commonly available ESP32 development board. + +## Configure the project + +``` +idf.py menuconfig +``` + +Set following parameters under Example Configuration Options: + +* Set `IP version` of example to be IPV4 or IPV6. + +* Set `IPV4 Address` in case your chose IP version IPV4 above. + +* Set `IPV6 Address` in case your chose IP version IPV6 above. + +* Set `Port` number that represents remote port the example will connect to. + +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 details. + +## Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + + +## Troubleshooting + +Start server first, to receive data sent from the client (application). diff --git a/example_test.py b/example_test.py new file mode 100755 index 000000000..2109907c3 --- /dev/null +++ b/example_test.py @@ -0,0 +1,133 @@ +# 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 os +import re +import socket +import sys +from builtins import input +from threading import Event, Thread + +import ttfw_idf +from common_test_methods import (get_env_config_variable, get_host_ip4_by_dest_ip, get_host_ip6_by_dest_ip, + get_my_interface_by_dest_ip) + +# ----------- Config ---------- +PORT = 3333 +# ------------------------------- + + +class TcpServer: + + def __init__(self, port, family_addr, persist=False): + self.port = port + self.socket = socket.socket(family_addr, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.settimeout(60.0) + self.shutdown = Event() + self.persist = persist + self.family_addr = family_addr + + def __enter__(self): + try: + self.socket.bind(('', self.port)) + except socket.error as e: + print('Bind failed:{}'.format(e)) + raise + self.socket.listen(1) + + print('Starting server on port={} family_addr={}'.format(self.port, self.family_addr)) + self.server_thread = Thread(target=self.run_server) + self.server_thread.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self.persist: + sock = socket.socket(self.family_addr, socket.SOCK_STREAM) + sock.connect(('localhost', self.port)) + sock.sendall(b'Stop', ) + sock.close() + self.shutdown.set() + self.shutdown.set() + self.server_thread.join() + self.socket.close() + + def run_server(self): + while not self.shutdown.is_set(): + try: + conn, address = self.socket.accept() # accept new connection + print('Connection from: {}'.format(address)) + conn.setblocking(1) + data = conn.recv(1024) + if not data: + return + data = data.decode() + print('Received data: ' + data) + reply = 'OK: ' + data + conn.send(reply.encode()) + conn.close() + except socket.error as e: + print('Running server failed:{}'.format(e)) + raise + if not self.persist: + break + + +@ttfw_idf.idf_example_test(env_tag='wifi_router') +def test_examples_protocol_socket_tcpclient(env, extra_data): + """ + steps: + 1. join AP + 2. have the board connect to the server + 3. send and receive data + """ + dut1 = env.get_dut('tcp_client', 'examples/protocols/sockets/tcp_client', dut_class=ttfw_idf.ESP32DUT) + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, 'tcp_client.bin') + bin_size = os.path.getsize(binary_file) + ttfw_idf.log_performance('tcp_client_bin_size', '{}KB'.format(bin_size // 1024)) + + # start test + dut1.start_app() + if dut1.app.get_sdkconfig_config_value('CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN'): + dut1.expect('Please input ssid password:') + env_name = 'wifi_router' + ap_ssid = get_env_config_variable(env_name, 'ap_ssid') + ap_password = get_env_config_variable(env_name, 'ap_password') + dut1.write(f'{ap_ssid} {ap_password}') + + ipv4 = dut1.expect(re.compile(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]'), timeout=30)[0] + ipv6_r = r':'.join((r'[0-9a-fA-F]{4}',) * 8) # expect all 8 octets from IPv6 (assumes it's printed in the long form) + ipv6 = dut1.expect(re.compile(r' IPv6 address: ({})'.format(ipv6_r)), timeout=30)[0] + print('Connected with IPv4={} and IPv6={}'.format(ipv4, ipv6)) + + my_interface = get_my_interface_by_dest_ip(ipv4) + # test IPv4 + with TcpServer(PORT, socket.AF_INET): + server_ip = get_host_ip4_by_dest_ip(ipv4) + print('Connect tcp client to server IP={}'.format(server_ip)) + dut1.write(server_ip) + dut1.expect(re.compile(r'OK: Message from ESP32')) + # test IPv6 + with TcpServer(PORT, socket.AF_INET6): + server_ip = get_host_ip6_by_dest_ip(ipv6, my_interface) + print('Connect tcp client to server IP={}'.format(server_ip)) + dut1.write(server_ip) + dut1.expect(re.compile(r'OK: Message from ESP32')) + + +if __name__ == '__main__': + if sys.argv[1:] and sys.argv[1].startswith('IPv'): # if additional arguments provided: + # Usage: example_test.py + family_addr = socket.AF_INET6 if sys.argv[1] == 'IPv6' else socket.AF_INET + with TcpServer(PORT, family_addr, persist=True) as s: + print(input('Press Enter stop the server...')) + else: + test_examples_protocol_socket_tcpclient() diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index cb717b502..5e465cdb0 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,23 +1,23 @@ -idf_component_register(SRCS -"pretty.c" -"crc.c" -"serial.c" -"system.c" -"adc.c" -"INA260.c" -"EMC2101.c" -"main.c" -"led_controller.c" -"DS4432U.c" -"EMC2101.c" -"INA260.c" -"adc.c" -"oled.c" -"fonts.c" -"system.c" -"bm1397.c" +idf_component_register(SRCS +"pretty.c" +"crc.c" +"serial.c" +"system.c" +"adc.c" +"INA260.c" +"EMC2101.c" +"led_controller.c" +"DS4432U.c" +"EMC2101.c" +"INA260.c" +"adc.c" +"oled.c" +"fonts.c" +"system.c" +"bm1397.c" "serial" "bm1397.c" "crc.c" "pretty.c" +"tcp_client.c" INCLUDE_DIRS ".") diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index dc9568c77..59513e0c7 100755 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -14,4 +14,52 @@ menu "Example Configuration" help GPIO number for I2C Master data line. + choice EXAMPLE_IP_MODE + prompt "IP Version" + depends on EXAMPLE_SOCKET_IP_INPUT_STRING + help + Example can use either IPV4 or IPV6. + + config EXAMPLE_IPV4 + bool "IPV4" + + config EXAMPLE_IPV6 + bool "IPV6" + + endchoice + + config EXAMPLE_IPV4_ADDR + string "IPV4 Address" + default "192.168.0.165" + depends on EXAMPLE_IPV4 + help + The example will connect to this IPV4 address. + + config EXAMPLE_IPV6_ADDR + string "IPV6 Address" + default "FE80::30AD:E57B:C212:68AD" + depends on EXAMPLE_IPV6 + help + The example will connect to this IPV6 address. + + config EXAMPLE_PORT + int "Port" + range 0 65535 + default 3333 + help + The remote port to which the client example will connect to. + + choice EXAMPLE_SOCKET_IP_INPUT + prompt "Socket example source" + default EXAMPLE_SOCKET_IP_INPUT_STRING + help + Selects the input source of the IP used in the example. + + config EXAMPLE_SOCKET_IP_INPUT_STRING + bool "From string" + + config EXAMPLE_SOCKET_IP_INPUT_STDIN + bool "From stdin" + endchoice + endmenu diff --git a/main/main.c b/main/main.c deleted file mode 100755 index 2416dba40..000000000 --- a/main/main.c +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include "esp_log.h" -#include "driver/i2c.h" -#include "driver/gpio.h" - -#include -#include - -#include "system.h" -#include "serial.h" - -static const char *TAG = "main"; - -TaskHandle_t sysTaskHandle = NULL; -TaskHandle_t serialTaskHandle = NULL; - -void app_main(void) { - ESP_LOGI(TAG, "Welcome to the bitaxe!"); - - xTaskCreate(SysTask, "System_Task", 4096, NULL, 10, &sysTaskHandle); - xTaskCreate(SerialTask, "serial_test", 4096, NULL, 10, &serialTaskHandle); - -} diff --git a/main/tcp_client.c b/main/tcp_client.c new file mode 100755 index 000000000..15dd936e4 --- /dev/null +++ b/main/tcp_client.c @@ -0,0 +1,133 @@ +/* BSD Socket API 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 +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "protocol_examples_common.h" +#include "addr_from_stdin.h" +#include "lwip/err.h" +#include "lwip/sockets.h" + +#include "system.h" +#include "serial.h" + +#if defined(CONFIG_EXAMPLE_IPV4) +#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR +#elif defined(CONFIG_EXAMPLE_IPV6) +#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR +#else +#define HOST_IP_ADDR "" +#endif + +#define PORT CONFIG_EXAMPLE_PORT + +static const char *TAG = "example"; +static const char *payload = "Message from ESP32 "; + +TaskHandle_t sysTaskHandle = NULL; +TaskHandle_t serialTaskHandle = NULL; + +static void tcp_client_task(void *pvParameters) +{ + char rx_buffer[128]; + char host_ip[] = HOST_IP_ADDR; + int addr_family = 0; + int ip_protocol = 0; + + while (1) { +#if defined(CONFIG_EXAMPLE_IPV4) + struct sockaddr_in dest_addr; + dest_addr.sin_addr.s_addr = inet_addr(host_ip); + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(PORT); + addr_family = AF_INET; + ip_protocol = IPPROTO_IP; +#elif defined(CONFIG_EXAMPLE_IPV6) + struct sockaddr_in6 dest_addr = { 0 }; + inet6_aton(host_ip, &dest_addr.sin6_addr); + dest_addr.sin6_family = AF_INET6; + dest_addr.sin6_port = htons(PORT); + dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE); + addr_family = AF_INET6; + ip_protocol = IPPROTO_IPV6; +#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN) + struct sockaddr_storage dest_addr = { 0 }; + ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_STREAM, &ip_protocol, &addr_family, &dest_addr)); +#endif + int sock = socket(addr_family, SOCK_STREAM, ip_protocol); + if (sock < 0) { + ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT); + + int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6)); + if (err != 0) { + ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Successfully connected"); + + while (1) { + int err = send(sock, payload, strlen(payload), 0); + if (err < 0) { + ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno); + break; + } + + int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); + // Error occurred during receiving + if (len < 0) { + ESP_LOGE(TAG, "recv failed: errno %d", errno); + break; + } + // Data received + else { + rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string + ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip); + ESP_LOGI(TAG, "%s", rx_buffer); + } + + vTaskDelay(2000 / portTICK_PERIOD_MS); + } + + if (sock != -1) { + ESP_LOGE(TAG, "Shutting down socket and restarting..."); + shutdown(sock, 0); + close(sock); + } + } + vTaskDelete(NULL); +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Welcome to the bitaxe!"); + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + + xTaskCreate(SysTask, "System_Task", 4096, NULL, 10, &sysTaskHandle); + xTaskCreate(SerialTask, "serial_test", 4096, NULL, 10, &serialTaskHandle); + xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL); +} diff --git a/sdkconfig.ci b/sdkconfig.ci new file mode 100755 index 000000000..5ab5e1522 --- /dev/null +++ b/sdkconfig.ci @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y +CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN=y