diff --git a/examples/protocols/sockets/README.md b/examples/protocols/sockets/README.md new file mode 100644 index 000000000..9fbc850db --- /dev/null +++ b/examples/protocols/sockets/README.md @@ -0,0 +1,102 @@ + +# BSD Socket API Examples + +This directory contains simple examples demonstrating BSD Socket API. +Each example, contains README.md file with mode detailed informations about that particular example. +For more general informations about all examples, see the README.md file in the upper level 'examples' directory. +Examples: + +* UDP Client - The application creates UDP socket and sends message to the predefined port and IP address. After the server's reply, the application prints received reply as ASCII text, waits for 2 seconds and sends another message. + +* UDP Server - The application creates UDP socket with the specified port number and waits for the data to be received. Received data are printed as ASCII text and retransmitted back to the client. + +* TCP Client - 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. + +* TCP Server - The application creates a TCP socket with the specified port number and waits for a connection request from the client. After accepting a request from the client, connection between server and client is established and the application waits for some data to be received from the client. Received data are printed as ASCII text and retransmitted back to the client. + +* UDP Multicast - The application shows how to use the IPV4 & IPV6 UDP multicast features via the BSD-style sockets interface. + +Standard BSD API documentation: +http://pubs.opengroup.org/onlinepubs/007908799/xnsix.html + +Other references: +https://csperkins.org/teaching/2007-2008/networked-systems/lecture04.pdf +http://wiki.treck.com/Introduction_to_BSD_Sockets + + +## Host tools + +There are many host-side tools which can be used to interact with the UDP/TCP server/client example. +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 commands. + +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. + +### Send UDP packet via netcat +``` +echo "Hello from PC" | nc -w1 -u 192.168.0.167 3333 +``` + +### Receive UDP packet via netcat +``` +echo "Hello from PC" | nc -w1 -u 192.168.0.167 3333 +``` + +### UDP client using netcat +``` +nc -u 192.168.0.167 3333 +``` + +### UDP server using netcat +``` +nc -u -l 192.168.0.167 -p 3333 +``` + +### TCP client using netcat +``` +nc 192.168.0.167 3333 +``` + +### TCP server using netcat +``` +nc -l 192.168.0.167 -p 3333 +``` + +### Python scripts +Each script contains port number, IP version (IPv4 or IPv6) and IP address (only clients) that has to be altered to match the values used by the application. Example: + +``` +PORT = 3333; +IP_VERSION = 'IPv4' +IPV4 = '192.168.0.167' +IPV6 = 'FE80::32AE:A4FF:FE80:5288' +``` + +## Hardware Required + +This example can be run on any commonly available ESP32 development board. + +## Configure the project + +``` +make menuconfig +``` + +* Set serial port under Serial Flasher Options. + +* Specific configuration for each example can be found in its README.md file. + +## Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 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. + + + diff --git a/examples/protocols/sockets/scripts/tcpclient.py b/examples/protocols/sockets/scripts/tcpclient.py new file mode 100644 index 000000000..5a91e177c --- /dev/null +++ b/examples/protocols/sockets/scripts/tcpclient.py @@ -0,0 +1,51 @@ +# 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 builtins import input +import socket +import sys + +# ----------- Config ---------- +PORT = 3333; +IP_VERSION = 'IPv4' +IPV4 = '192.168.0.167' +IPV6 = 'FE80::32AE:A4FF:FE80:5288' +# ------------------------------- + +if IP_VERSION == 'IPv4': + family_addr = socket.AF_INET + host = IPV4 +elif IP_VERSION == 'IPv6': + family_addr = socket.AF_INET6 + host = IPV6 +else: + print('IP_VERSION must be IPv4 or IPv6') + sys.exit(1) + +try: + sock = socket.socket(family_addr, socket.SOCK_STREAM) +except socket.error as msg: + print('Could not create socket: ' + str(msg[0]) + ': ' + msg[1]) + sys.exit(1); + +try: + sock.connect((host, PORT)) +except socket.error as msg: + print('Could not open socket: ', msg) + sock.close() + sys.exit(1); + +while True: + msg = input('Enter message to send: ') + assert isinstance(msg, str) + msg = msg.encode() + sock.sendall(msg) + data = sock.recv(1024) + if not data: break; + print( 'Reply: ' + data.decode()) +sock.close() \ No newline at end of file diff --git a/examples/protocols/sockets/scripts/tcpserver.py b/examples/protocols/sockets/scripts/tcpserver.py new file mode 100644 index 000000000..9030fada1 --- /dev/null +++ b/examples/protocols/sockets/scripts/tcpserver.py @@ -0,0 +1,53 @@ +# 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 -*- + +import socket +import sys + +# ----------- Config ---------- +IP_VERSION = 'IPv4' +PORT = 3333; +# ------------------------------- + +if IP_VERSION == 'IPv4': + family_addr = socket.AF_INET +elif IP_VERSION == 'IPv6': + family_addr = socket.AF_INET6 +else: + print('IP_VERSION must be IPv4 or IPv6') + sys.exit(1) + + +try: + sock = socket.socket(family_addr, socket.SOCK_STREAM) +except socket.error as msg: + print('Error: ' + str(msg[0]) + ': ' + msg[1]) + sys.exit(1) + +print('Socket created') + +try: + sock.bind(('', PORT)) + print('Socket binded') + sock.listen(1) + print('Socket listening') + conn, addr = sock.accept() + print('Connected by', addr) +except socket.error as msg: + print('Error: ' + str(msg[0]) + ': ' + msg[1]) + sock.close() + sys.exit(1) + +while True: + data = conn.recv(128) + if not data: break + data = data.decode() + print('Received data: ' + data) + reply = 'OK: ' + data + conn.send(reply.encode()) +conn.close() diff --git a/examples/protocols/sockets/scripts/udpclient.py b/examples/protocols/sockets/scripts/udpclient.py new file mode 100644 index 000000000..ac6eeaace --- /dev/null +++ b/examples/protocols/sockets/scripts/udpclient.py @@ -0,0 +1,46 @@ +# 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 builtins import input +import socket +import sys + +# ----------- Config ---------- +PORT = 3333 +IP_VERSION = 'IPv4' +IPV4 = '192.168.0.167' +IPV6 = 'FE80::32AE:A4FF:FE80:5288' +# ------------------------------- + +if IP_VERSION == 'IPv4': + host = IPV4 + family_addr = socket.AF_INET +elif IP_VERSION == 'IPv6': + host = IPV6 + family_addr = socket.AF_INET6 +else: + print('IP_VERSION must be IPv4 or IPv6') + sys.exit(1) + + +try: + sock = socket.socket(family_addr, socket.SOCK_DGRAM) +except socket.error as msg: + print('Failed to create socket') + sys.exit() + +while True: + msg = input('Enter message to send : ') + try: + sock.sendto(msg.encode(), (host, PORT)) + reply, addr = sock.recvfrom(128) + if not reply: break + print('Reply[' + addr[0] + ':' + str(addr[1]) + '] - ' + str(reply)) + except socket.error as msg: + print('Error Code : ' + str(msg[0]) + ' Message: ' + msg[1]) + sys.exit() \ No newline at end of file diff --git a/examples/protocols/sockets/scripts/udpserver.py b/examples/protocols/sockets/scripts/udpserver.py new file mode 100644 index 000000000..94fda6a5a --- /dev/null +++ b/examples/protocols/sockets/scripts/udpserver.py @@ -0,0 +1,51 @@ +# 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 -*- + +import socket +import sys + +# ----------- Config ---------- +IP_VERSION = 'IPv4' +PORT = 3333; +# ------------------------------- + +if IP_VERSION == 'IPv4': + family_addr = socket.AF_INET +elif IP_VERSION == 'IPv6': + family_addr = socket.AF_INET6 +else: + print('IP_VERSION must be IPv4 or IPv6') + sys.exit(1) + + +try : + sock = socket.socket(family_addr, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +except socket.error as msg : + print('Failed to create socket. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]) + sys.exit() + +try: + sock.bind(('', PORT)) +except socket.error as msg: + print('Bind failed. Error: ' + str(msg[0]) + ': ' + msg[1]) + sys.exit() + +while True: + try : + print('Waiting for data...') + data, addr = sock.recvfrom(1024) + if not data: break + data = data.decode() + print('Reply[' + addr[0] + ':' + str(addr[1]) + '] - ' + data) + reply = 'OK ' + data + sock.sendto(reply.encode(), addr) + except socket.error as msg: + print('Error Code : ' + str(msg[0]) + ' Message ' + msg[1]) + +sock.close() diff --git a/examples/protocols/sockets/tcp_client/CMakeLists.txt b/examples/protocols/sockets/tcp_client/CMakeLists.txt new file mode 100644 index 000000000..22aa37f1c --- /dev/null +++ b/examples/protocols/sockets/tcp_client/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(tcp_client) diff --git a/examples/protocols/sockets/tcp_client/Makefile b/examples/protocols/sockets/tcp_client/Makefile new file mode 100644 index 000000000..b390190f2 --- /dev/null +++ b/examples/protocols/sockets/tcp_client/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := tcp_client + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/sockets/tcp_client/README.md b/examples/protocols/sockets/tcp_client/README.md new file mode 100644 index 000000000..be9e9f2d4 --- /dev/null +++ b/examples/protocols/sockets/tcp_client/README.md @@ -0,0 +1,74 @@ + +# 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 -p 3333 +``` + +### Python scripts +Script tcpserver.py contains configuration for port number and IP version (IPv4 or IPv6) that has to be altered to match the values used by the application. Example: + +``` +IP_VERSION = 'IPv4' +PORT = 3333; +``` + +## Hardware Required + +This example can be run on any commonly available ESP32 development board. + +## Configure the project + +``` +make menuconfig +``` + +Set following parameter under Serial Flasher Options: + +* Set `Default serial port`. + +Set following parameters under Example Configuration Options: + +* Set `WiFi SSID` of the Router (Access-Point). + +* Set `WiFi Password` of the Router (Access-Point). + +* 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. + +## Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 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/examples/protocols/sockets/tcp_client/main/CMakeLists.txt b/examples/protocols/sockets/tcp_client/main/CMakeLists.txt new file mode 100644 index 000000000..be11f2950 --- /dev/null +++ b/examples/protocols/sockets/tcp_client/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "tcp_client.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/protocols/sockets/tcp_client/main/Kconfig.projbuild b/examples/protocols/sockets/tcp_client/main/Kconfig.projbuild new file mode 100644 index 000000000..a7e12f363 --- /dev/null +++ b/examples/protocols/sockets/tcp_client/main/Kconfig.projbuild @@ -0,0 +1,50 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + Can be left blank if the network has no security set. + +choice EXAMPLE_IP_MODE + prompt "IP Version" + 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. + +endmenu diff --git a/examples/protocols/udp_multicast/main/component.mk b/examples/protocols/sockets/tcp_client/main/component.mk similarity index 100% rename from examples/protocols/udp_multicast/main/component.mk rename to examples/protocols/sockets/tcp_client/main/component.mk diff --git a/examples/protocols/sockets/tcp_client/main/tcp_client.c b/examples/protocols/sockets/tcp_client/main/tcp_client.c new file mode 100644 index 000000000..9faaa8c65 --- /dev/null +++ b/examples/protocols/sockets/tcp_client/main/tcp_client.c @@ -0,0 +1,192 @@ +/* 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_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" + +#include "lwip/err.h" +#include "lwip/sockets.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_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#ifdef CONFIG_EXAMPLE_IPV4 +#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR +#else +#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR +#endif + +#define PORT CONFIG_EXAMPLE_PORT + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +const int IPV4_GOTIP_BIT = BIT0; +const int IPV6_GOTIP_BIT = BIT1; + +static const char *TAG = "example"; +static const char *payload = "Message from ESP32 "; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); + break; + case SYSTEM_EVENT_STA_CONNECTED: + /* enable ipv6 */ + tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, IPV4_GOTIP_BIT); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, IPV4_GOTIP_BIT); + xEventGroupClearBits(wifi_event_group, IPV6_GOTIP_BIT); + break; + case SYSTEM_EVENT_AP_STA_GOT_IP6: + xEventGroupSetBits(wifi_event_group, IPV6_GOTIP_BIT); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP6"); + + char *ip6 = ip6addr_ntoa(&event->event_info.got_ip6.ip6_info.ip); + ESP_LOGI(TAG, "IPv6: %s", ip6); + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + 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) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + 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() ); +} + +static void wait_for_ip() +{ + uint32_t bits = IPV4_GOTIP_BIT | IPV6_GOTIP_BIT ; + + ESP_LOGI(TAG, "Waiting for AP connection..."); + xEventGroupWaitBits(wifi_event_group, bits, false, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connected to AP"); +} + +static void tcp_client_task(void *pvParameters) +{ + char rx_buffer[128]; + char addr_str[128]; + int addr_family; + int ip_protocol; + + while (1) { + +#ifdef CONFIG_EXAMPLE_IPV4 + struct sockaddr_in destAddr; + destAddr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR); + destAddr.sin_family = AF_INET; + destAddr.sin_port = htons(PORT); + addr_family = AF_INET; + ip_protocol = IPPROTO_IP; + inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1); +#else // IPV6 + struct sockaddr_in6 destAddr; + inet6_aton(HOST_IP_ADDR, &destAddr.sin6_addr); + destAddr.sin6_family = AF_INET6; + destAddr.sin6_port = htons(PORT); + addr_family = AF_INET6; + ip_protocol = IPPROTO_IPV6; + inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1); +#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"); + + int err = connect(sock, (struct sockaddr *)&destAddr, sizeof(destAddr)); + if (err != 0) { + ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno); + } + ESP_LOGI(TAG, "Successfully connected"); + + while (1) { + int err = send(sock, payload, strlen(payload), 0); + if (err < 0) { + ESP_LOGE(TAG, "Error occured during sending: errno %d", errno); + break; + } + + int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); + // Error occured 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, addr_str); + 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() +{ + ESP_ERROR_CHECK( nvs_flash_init() ); + initialise_wifi(); + wait_for_ip(); + + xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL); +} diff --git a/examples/protocols/sockets/tcp_server/CMakeLists.txt b/examples/protocols/sockets/tcp_server/CMakeLists.txt new file mode 100644 index 000000000..3514e60fc --- /dev/null +++ b/examples/protocols/sockets/tcp_server/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(tcp_server) diff --git a/examples/protocols/sockets/tcp_server/Makefile b/examples/protocols/sockets/tcp_server/Makefile new file mode 100644 index 000000000..e965b9093 --- /dev/null +++ b/examples/protocols/sockets/tcp_server/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := tcp_server + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/sockets/tcp_server/README.md b/examples/protocols/sockets/tcp_server/README.md new file mode 100644 index 000000000..f43dc0791 --- /dev/null +++ b/examples/protocols/sockets/tcp_server/README.md @@ -0,0 +1,72 @@ + +# TCP Server example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +The application creates a TCP socket with the specified port number and waits for a connection request from the client. After accepting a request from the client, connection between server and client is established and the application waits for some data to be received from the client. Received data are printed as ASCII text and retransmitted back to the client. + +## How to use example + +In order to create TCP client that communicates with TCP server 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 client using netcat +``` +nc 192.168.0.167 3333 +``` + +### Python scripts +Script tcpclient.py contains configuration for port number, IP version (IPv4 or IPv6) and IP address that has to be altered to match the values used by the application. Example: + +``` +PORT = 3333; +IP_VERSION = 'IPv4' +IPV4 = '192.168.0.167' +IPV6 = 'FE80::32AE:A4FF:FE80:5288' +``` + +## Hardware Required + +This example can be run on any commonly available ESP32 development board. + +## Configure the project + +``` +make menuconfig +``` + +Set following parameter under Serial Flasher Options: + +* Set `Default serial port`. + +Set following parameters under Example Configuration Options: + +* Set `WiFi SSID` of the Router (Access-Point). + +* Set `WiFi Password` of the Router (Access-Point). + +* Set `IP version` of the example to be IPV4 or IPV6. + +* Set `Port` number of the socket, that server example will create. + +## Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 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/examples/protocols/sockets/tcp_server/main/CMakeLists.txt b/examples/protocols/sockets/tcp_server/main/CMakeLists.txt new file mode 100644 index 000000000..844a71574 --- /dev/null +++ b/examples/protocols/sockets/tcp_server/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "tcp_server.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/protocols/sockets/tcp_server/main/Kconfig.projbuild b/examples/protocols/sockets/tcp_server/main/Kconfig.projbuild new file mode 100644 index 000000000..472111381 --- /dev/null +++ b/examples/protocols/sockets/tcp_server/main/Kconfig.projbuild @@ -0,0 +1,36 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + Can be left blank if the network has no security set. + +choice EXAMPLE_IP_MODE + prompt "IP Version" + help + Example can use either IPV4 or IPV6. + +config EXAMPLE_IPV4 + bool "IPV4" + +config EXAMPLE_IPV6 + bool "IPV6" + +endchoice + +config EXAMPLE_PORT + int "Port" + range 0 65535 + default 3333 + help + Local port the example server will listen on. + +endmenu diff --git a/examples/protocols/sockets/tcp_server/main/component.mk b/examples/protocols/sockets/tcp_server/main/component.mk new file mode 100644 index 000000000..a98f634ea --- /dev/null +++ b/examples/protocols/sockets/tcp_server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/protocols/sockets/tcp_server/main/tcp_server.c b/examples/protocols/sockets/tcp_server/main/tcp_server.c new file mode 100644 index 000000000..4f2c07d32 --- /dev/null +++ b/examples/protocols/sockets/tcp_server/main/tcp_server.c @@ -0,0 +1,212 @@ +/* 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_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" + +#include "lwip/err.h" +#include "lwip/sockets.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_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#define PORT CONFIG_EXAMPLE_PORT + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +const int IPV4_GOTIP_BIT = BIT0; +const int IPV6_GOTIP_BIT = BIT1; + +static const char *TAG = "example"; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); + break; + case SYSTEM_EVENT_STA_CONNECTED: + /* enable ipv6 */ + tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, IPV4_GOTIP_BIT); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, IPV4_GOTIP_BIT); + xEventGroupClearBits(wifi_event_group, IPV6_GOTIP_BIT); + break; + case SYSTEM_EVENT_AP_STA_GOT_IP6: + xEventGroupSetBits(wifi_event_group, IPV6_GOTIP_BIT); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP6"); + + char *ip6 = ip6addr_ntoa(&event->event_info.got_ip6.ip6_info.ip); + ESP_LOGI(TAG, "IPv6: %s", ip6); + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + 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) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + 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() ); +} + +static void wait_for_ip() +{ + uint32_t bits = IPV4_GOTIP_BIT | IPV6_GOTIP_BIT ; + + ESP_LOGI(TAG, "Waiting for AP connection..."); + xEventGroupWaitBits(wifi_event_group, bits, false, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connected to AP"); +} + +static void tcp_server_task(void *pvParameters) +{ + char rx_buffer[128]; + char addr_str[128]; + int addr_family; + int ip_protocol; + + while (1) { + +#ifdef CONFIG_EXAMPLE_IPV4 + struct sockaddr_in destAddr; + destAddr.sin_addr.s_addr = htonl(INADDR_ANY); + destAddr.sin_family = AF_INET; + destAddr.sin_port = htons(PORT); + addr_family = AF_INET; + ip_protocol = IPPROTO_IP; + inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1); +#else // IPV6 + struct sockaddr_in6 destAddr; + bzero(&destAddr.sin6_addr.un, sizeof(destAddr.sin6_addr.un)); + destAddr.sin6_family = AF_INET6; + destAddr.sin6_port = htons(PORT); + addr_family = AF_INET6; + ip_protocol = IPPROTO_IPV6; + inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1); +#endif + + int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol); + if (listen_sock < 0) { + ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Socket created"); + + int err = bind(listen_sock, (struct sockaddr *)&destAddr, sizeof(destAddr)); + if (err != 0) { + ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Socket binded"); + + err = listen(listen_sock, 1); + if (err != 0) { + ESP_LOGE(TAG, "Error occured during listen: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Socket listening"); + + struct sockaddr_in6 sourceAddr; // Large enough for both IPv4 or IPv6 + uint addrLen = sizeof(sourceAddr); + int sock = accept(listen_sock, (struct sockaddr *)&sourceAddr, &addrLen); + if (sock < 0) { + ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Socket accepted"); + + while (1) { + int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); + // Error occured during receiving + if (len < 0) { + ESP_LOGE(TAG, "recv failed: errno %d", errno); + break; + } + // Connection closed + else if (len == 0) { + ESP_LOGI(TAG, "Connection closed"); + break; + } + // Data received + else { + // Get the sender's ip address as string + if (sourceAddr.sin6_family == PF_INET) { + inet_ntoa_r(((struct sockaddr_in *)&sourceAddr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); + } else if (sourceAddr.sin6_family == PF_INET6) { + inet6_ntoa_r(sourceAddr.sin6_addr, addr_str, sizeof(addr_str) - 1); + } + + rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string + ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str); + ESP_LOGI(TAG, "%s", rx_buffer); + + int err = send(sock, rx_buffer, len, 0); + if (err < 0) { + ESP_LOGE(TAG, "Error occured during sending: errno %d", errno); + break; + } + } + } + + if (sock != -1) { + ESP_LOGE(TAG, "Shutting down socket and restarting..."); + shutdown(sock, 0); + close(sock); + } + } + vTaskDelete(NULL); +} + +void app_main() +{ + ESP_ERROR_CHECK( nvs_flash_init() ); + initialise_wifi(); + wait_for_ip(); + + xTaskCreate(tcp_server_task, "tcp_server", 4096, NULL, 5, NULL); +} diff --git a/examples/protocols/sockets/udp_client/CMakeLists.txt b/examples/protocols/sockets/udp_client/CMakeLists.txt new file mode 100644 index 000000000..ee1d3f532 --- /dev/null +++ b/examples/protocols/sockets/udp_client/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(udp_client) diff --git a/examples/protocols/sockets/udp_client/Makefile b/examples/protocols/sockets/udp_client/Makefile new file mode 100644 index 000000000..34849eee2 --- /dev/null +++ b/examples/protocols/sockets/udp_client/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := udp_client + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/sockets/udp_client/README.md b/examples/protocols/sockets/udp_client/README.md new file mode 100644 index 000000000..80994e2c0 --- /dev/null +++ b/examples/protocols/sockets/udp_client/README.md @@ -0,0 +1,84 @@ + +# UDP Client example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +The application creates UDP socket and sends message to the predefined port and IP address. After the server's reply, the application prints received reply as ASCII text, waits for 2 seconds and sends another message. + +## How to use example + +In order to create UDP server that communicates with UDP 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 commands. + +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. + +### Send UDP packet via netcat +``` +echo "Hello from PC" | nc -w1 -u 192.168.0.167 3333 +``` + +### Receive UDP packet via netcat +``` +echo "Hello from PC" | nc -w1 -u 192.168.0.167 3333 +``` + +### UDP server using netcat +``` +nc -u -l 192.168.0.167 -p 3333 +``` + +### Python scripts +Script udpserver.py contains configuration for port number and IP version (IPv4 or IPv6) that has to be altered to match the values used by the application. Example: + +``` +IP_VERSION = 'IPv4' +PORT = 3333; +``` + +## Hardware Required + +This example can be run on any commonly available ESP32 development board. + +## Configure the project + +``` +make menuconfig +``` + +Set following parameter under Serial Flasher Options: + +* Set `Default serial port`. + +Set following parameters under Example Configuration Options: + +* Set `WiFi SSID` of the Router (Access-Point). + +* Set `WiFi Password` of the Router (Access-Point). + +* 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 send data and receive data from. + +## Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 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/examples/protocols/sockets/udp_client/main/CMakeLists.txt b/examples/protocols/sockets/udp_client/main/CMakeLists.txt new file mode 100644 index 000000000..aadad8c20 --- /dev/null +++ b/examples/protocols/sockets/udp_client/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "udp_client.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/protocols/sockets/udp_client/main/Kconfig.projbuild b/examples/protocols/sockets/udp_client/main/Kconfig.projbuild new file mode 100644 index 000000000..3aff37d8a --- /dev/null +++ b/examples/protocols/sockets/udp_client/main/Kconfig.projbuild @@ -0,0 +1,50 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + Can be left blank if the network has no security set. + +choice EXAMPLE_IP_MODE + prompt "IP Version" + 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 + IPV4 address to which the client example will send data. + +config EXAMPLE_IPV6_ADDR + string "IPV6 Address" + default "FE80::30AD:E57B:C212:68AD" + depends on EXAMPLE_IPV6 + help + IPV6 address to which the client example will send data. + +config EXAMPLE_PORT + int "Port" + range 0 65535 + default 3333 + help + The remote port to which the client example will send data. + +endmenu diff --git a/examples/protocols/sockets/udp_client/main/component.mk b/examples/protocols/sockets/udp_client/main/component.mk new file mode 100644 index 000000000..a98f634ea --- /dev/null +++ b/examples/protocols/sockets/udp_client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/protocols/sockets/udp_client/main/udp_client.c b/examples/protocols/sockets/udp_client/main/udp_client.c new file mode 100644 index 000000000..39a1d3a91 --- /dev/null +++ b/examples/protocols/sockets/udp_client/main/udp_client.c @@ -0,0 +1,191 @@ +/* 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_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" + +#include "lwip/err.h" +#include "lwip/sockets.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_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#ifdef CONFIG_EXAMPLE_IPV4 +#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR +#else +#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR +#endif + +#define PORT CONFIG_EXAMPLE_PORT + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +const int IPV4_GOTIP_BIT = BIT0; +const int IPV6_GOTIP_BIT = BIT1; + +static const char *TAG = "example"; +static const char *payload = "Message from ESP32 "; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); + break; + case SYSTEM_EVENT_STA_CONNECTED: + /* enable ipv6 */ + tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, IPV4_GOTIP_BIT); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, IPV4_GOTIP_BIT); + xEventGroupClearBits(wifi_event_group, IPV6_GOTIP_BIT); + break; + case SYSTEM_EVENT_AP_STA_GOT_IP6: + xEventGroupSetBits(wifi_event_group, IPV6_GOTIP_BIT); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP6"); + + char *ip6 = ip6addr_ntoa(&event->event_info.got_ip6.ip6_info.ip); + ESP_LOGI(TAG, "IPv6: %s", ip6); + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + 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) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + 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() ); +} + +static void wait_for_ip() +{ + uint32_t bits = IPV4_GOTIP_BIT | IPV6_GOTIP_BIT ; + + ESP_LOGI(TAG, "Waiting for AP connection..."); + xEventGroupWaitBits(wifi_event_group, bits, false, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connected to AP"); +} + +static void udp_client_task(void *pvParameters) +{ + char rx_buffer[128]; + char addr_str[128]; + int addr_family; + int ip_protocol; + + while (1) { + +#ifdef CONFIG_EXAMPLE_IPV4 + struct sockaddr_in destAddr; + destAddr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR); + destAddr.sin_family = AF_INET; + destAddr.sin_port = htons(PORT); + addr_family = AF_INET; + ip_protocol = IPPROTO_IP; + inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1); +#else // IPV6 + struct sockaddr_in6 destAddr; + inet6_aton(HOST_IP_ADDR, &destAddr.sin6_addr); + destAddr.sin6_family = AF_INET6; + destAddr.sin6_port = htons(PORT); + addr_family = AF_INET6; + ip_protocol = IPPROTO_IPV6; + inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1); +#endif + + int sock = socket(addr_family, SOCK_DGRAM, ip_protocol); + if (sock < 0) { + ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Socket created"); + + while (1) { + + int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&destAddr, sizeof(destAddr)); + if (err < 0) { + ESP_LOGE(TAG, "Error occured during sending: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Message sent"); + + struct sockaddr_in sourceAddr; // Large enough for both IPv4 or IPv6 + socklen_t socklen = sizeof(sourceAddr); + int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&sourceAddr, &socklen); + + // Error occured during receiving + if (len < 0) { + ESP_LOGE(TAG, "recvfrom 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, addr_str); + 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() +{ + ESP_ERROR_CHECK( nvs_flash_init() ); + initialise_wifi(); + wait_for_ip(); + + xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL); +} diff --git a/examples/protocols/udp_multicast/CMakeLists.txt b/examples/protocols/sockets/udp_multicast/CMakeLists.txt similarity index 100% rename from examples/protocols/udp_multicast/CMakeLists.txt rename to examples/protocols/sockets/udp_multicast/CMakeLists.txt diff --git a/examples/protocols/udp_multicast/Makefile b/examples/protocols/sockets/udp_multicast/Makefile similarity index 100% rename from examples/protocols/udp_multicast/Makefile rename to examples/protocols/sockets/udp_multicast/Makefile diff --git a/examples/protocols/udp_multicast/README.md b/examples/protocols/sockets/udp_multicast/README.md similarity index 100% rename from examples/protocols/udp_multicast/README.md rename to examples/protocols/sockets/udp_multicast/README.md diff --git a/examples/protocols/udp_multicast/main/CMakeLists.txt b/examples/protocols/sockets/udp_multicast/main/CMakeLists.txt similarity index 100% rename from examples/protocols/udp_multicast/main/CMakeLists.txt rename to examples/protocols/sockets/udp_multicast/main/CMakeLists.txt diff --git a/examples/protocols/udp_multicast/main/Kconfig.projbuild b/examples/protocols/sockets/udp_multicast/main/Kconfig.projbuild similarity index 100% rename from examples/protocols/udp_multicast/main/Kconfig.projbuild rename to examples/protocols/sockets/udp_multicast/main/Kconfig.projbuild diff --git a/examples/protocols/sockets/udp_multicast/main/component.mk b/examples/protocols/sockets/udp_multicast/main/component.mk new file mode 100644 index 000000000..a98f634ea --- /dev/null +++ b/examples/protocols/sockets/udp_multicast/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/protocols/udp_multicast/main/udp_multicast_example_main.c b/examples/protocols/sockets/udp_multicast/main/udp_multicast_example_main.c similarity index 100% rename from examples/protocols/udp_multicast/main/udp_multicast_example_main.c rename to examples/protocols/sockets/udp_multicast/main/udp_multicast_example_main.c diff --git a/examples/protocols/sockets/udp_server/CMakeLists.txt b/examples/protocols/sockets/udp_server/CMakeLists.txt new file mode 100644 index 000000000..d1edab0fc --- /dev/null +++ b/examples/protocols/sockets/udp_server/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(udp_server) diff --git a/examples/protocols/sockets/udp_server/Makefile b/examples/protocols/sockets/udp_server/Makefile new file mode 100644 index 000000000..36552c9bf --- /dev/null +++ b/examples/protocols/sockets/udp_server/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := udp_server + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/sockets/udp_server/README.md b/examples/protocols/sockets/udp_server/README.md new file mode 100644 index 000000000..8fe64a73e --- /dev/null +++ b/examples/protocols/sockets/udp_server/README.md @@ -0,0 +1,82 @@ + +# UDP Server example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +The application creates UDP socket with the specified port number and waits for the data to be received. Received data are printed as ASCII text and retransmitted back to the client. + +## How to use example + +In order to create UDP client that communicates with UDP server 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 commands. + +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. + +### Send UDP packet via netcat +``` +echo "Hello from PC" | nc -w1 -u 192.168.0.167 3333 +``` + +### Receive UDP packet via netcat +``` +echo "Hello from PC" | nc -w1 -u 192.168.0.167 3333 +``` + +### UDP client using netcat +``` +nc -u 192.168.0.167 3333 +``` + +### Python scripts +Script udpclient.py contains configuration for port number, IP version (IPv4 or IPv6) and IP address that has to be altered to match the values used by the application. Example: + +``` +PORT = 3333; +IP_VERSION = 'IPv4' +IPV4 = '192.168.0.167' +IPV6 = 'FE80::32AE:A4FF:FE80:5288' +``` + +## Hardware Required + +This example can be run on any commonly available ESP32 development board. + +## Configure the project + +``` +make menuconfig +``` + +Set following parameter under Serial Flasher Options: + +* Set `Default serial port`. + +Set following parameters under Example Configuration Options: + +* Set `WiFi SSID` of the Router (Access-Point). + +* Set `WiFi Password` of the Router (Access-Point). + +* Set `IP version` of the example to be IPV4 or IPV6. + +* Set `Port` number that represents remote port the example will create. + +## Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 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/examples/protocols/sockets/udp_server/main/CMakeLists.txt b/examples/protocols/sockets/udp_server/main/CMakeLists.txt new file mode 100644 index 000000000..55a6bebdc --- /dev/null +++ b/examples/protocols/sockets/udp_server/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "udp_server.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/protocols/sockets/udp_server/main/Kconfig.projbuild b/examples/protocols/sockets/udp_server/main/Kconfig.projbuild new file mode 100644 index 000000000..472111381 --- /dev/null +++ b/examples/protocols/sockets/udp_server/main/Kconfig.projbuild @@ -0,0 +1,36 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + Can be left blank if the network has no security set. + +choice EXAMPLE_IP_MODE + prompt "IP Version" + help + Example can use either IPV4 or IPV6. + +config EXAMPLE_IPV4 + bool "IPV4" + +config EXAMPLE_IPV6 + bool "IPV6" + +endchoice + +config EXAMPLE_PORT + int "Port" + range 0 65535 + default 3333 + help + Local port the example server will listen on. + +endmenu diff --git a/examples/protocols/sockets/udp_server/main/component.mk b/examples/protocols/sockets/udp_server/main/component.mk new file mode 100644 index 000000000..a98f634ea --- /dev/null +++ b/examples/protocols/sockets/udp_server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/protocols/sockets/udp_server/main/udp_server.c b/examples/protocols/sockets/udp_server/main/udp_server.c new file mode 100644 index 000000000..336388f70 --- /dev/null +++ b/examples/protocols/sockets/udp_server/main/udp_server.c @@ -0,0 +1,195 @@ +/* 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_loop.h" +#include "esp_log.h" +#include "nvs_flash.h" + +#include "lwip/err.h" +#include "lwip/sockets.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_WIFI_SSID CONFIG_WIFI_SSID +#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD + +#define PORT CONFIG_EXAMPLE_PORT + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +const int IPV4_GOTIP_BIT = BIT0; +const int IPV6_GOTIP_BIT = BIT1; + +static const char *TAG = "example"; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); + break; + case SYSTEM_EVENT_STA_CONNECTED: + /* enable ipv6 */ + tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, IPV4_GOTIP_BIT); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, IPV4_GOTIP_BIT); + xEventGroupClearBits(wifi_event_group, IPV6_GOTIP_BIT); + break; + case SYSTEM_EVENT_AP_STA_GOT_IP6: + xEventGroupSetBits(wifi_event_group, IPV6_GOTIP_BIT); + ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP6"); + + char *ip6 = ip6addr_ntoa(&event->event_info.got_ip6.ip6_info.ip); + ESP_LOGI(TAG, "IPv6: %s", ip6); + default: + break; + } + return ESP_OK; +} + +static void initialise_wifi(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + 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) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + wifi_config_t wifi_config = { + .sta = { + .ssid = EXAMPLE_WIFI_SSID, + .password = EXAMPLE_WIFI_PASS, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + 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() ); +} + +static void wait_for_ip() +{ + uint32_t bits = IPV4_GOTIP_BIT | IPV6_GOTIP_BIT ; + + ESP_LOGI(TAG, "Waiting for AP connection..."); + xEventGroupWaitBits(wifi_event_group, bits, false, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connected to AP"); +} + +static void udp_server_task(void *pvParameters) +{ + char rx_buffer[128]; + char addr_str[128]; + int addr_family; + int ip_protocol; + + while (1) { + +#ifdef CONFIG_EXAMPLE_IPV4 + struct sockaddr_in destAddr; + destAddr.sin_addr.s_addr = htonl(INADDR_ANY); + destAddr.sin_family = AF_INET; + destAddr.sin_port = htons(PORT); + addr_family = AF_INET; + ip_protocol = IPPROTO_IP; + inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1); +#else // IPV6 + struct sockaddr_in6 destAddr; + bzero(&destAddr.sin6_addr.un, sizeof(destAddr.sin6_addr.un)); + destAddr.sin6_family = AF_INET6; + destAddr.sin6_port = htons(PORT); + addr_family = AF_INET6; + ip_protocol = IPPROTO_IPV6; + inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1); +#endif + + int sock = socket(addr_family, SOCK_DGRAM, ip_protocol); + if (sock < 0) { + ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); + break; + } + ESP_LOGI(TAG, "Socket created"); + + int err = bind(sock, (struct sockaddr *)&destAddr, sizeof(destAddr)); + if (err < 0) { + ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); + } + ESP_LOGI(TAG, "Socket binded"); + + while (1) { + + ESP_LOGI(TAG, "Waiting for data"); + struct sockaddr_in6 sourceAddr; // Large enough for both IPv4 or IPv6 + socklen_t socklen = sizeof(sourceAddr); + int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&sourceAddr, &socklen); + + // Error occured during receiving + if (len < 0) { + ESP_LOGE(TAG, "recvfrom failed: errno %d", errno); + break; + } + // Data received + else { + // Get the sender's ip address as string + if (sourceAddr.sin6_family == PF_INET) { + inet_ntoa_r(((struct sockaddr_in *)&sourceAddr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); + } else if (sourceAddr.sin6_family == PF_INET6) { + inet6_ntoa_r(sourceAddr.sin6_addr, addr_str, sizeof(addr_str) - 1); + } + + rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string... + ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str); + ESP_LOGI(TAG, "%s", rx_buffer); + + int err = sendto(sock, rx_buffer, len, 0, (struct sockaddr *)&sourceAddr, sizeof(sourceAddr)); + if (err < 0) { + ESP_LOGE(TAG, "Error occured during sending: errno %d", errno); + break; + } + } + } + + if (sock != -1) { + ESP_LOGE(TAG, "Shutting down socket and restarting..."); + shutdown(sock, 0); + close(sock); + } + } + vTaskDelete(NULL); +} + +void app_main() +{ + ESP_ERROR_CHECK( nvs_flash_init() ); + initialise_wifi(); + wait_for_ip(); + + xTaskCreate(udp_server_task, "udp_server", 4096, NULL, 5, NULL); +}