diff --git a/examples/protocols/sockets/README.md b/examples/protocols/sockets/README.md new file mode 100644 index 00000000..9fbc850d --- /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 00000000..5a91e177 --- /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 00000000..9030fada --- /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 00000000..ac6eeaac --- /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 00000000..94fda6a5 --- /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 00000000..22aa37f1 --- /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 00000000..b390190f --- /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 00000000..be9e9f2d --- /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 00000000..be11f295 --- /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 00000000..a7e12f36 --- /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/sockets/tcp_client/main/component.mk b/examples/protocols/sockets/tcp_client/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/examples/protocols/sockets/tcp_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/tcp_client/main/tcp_client.c b/examples/protocols/sockets/tcp_client/main/tcp_client.c new file mode 100644 index 00000000..9faaa8c6 --- /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 00000000..3514e60f --- /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 00000000..e965b909 --- /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 00000000..f43dc079 --- /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 00000000..844a7157 --- /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 00000000..47211138 --- /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 00000000..a98f634e --- /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 00000000..4f2c07d3 --- /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 00000000..ee1d3f53 --- /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 00000000..34849eee --- /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 00000000..80994e2c --- /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 00000000..aadad8c2 --- /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 00000000..3aff37d8 --- /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 00000000..a98f634e --- /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 00000000..39a1d3a9 --- /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/sockets/udp_multicast/CMakeLists.txt b/examples/protocols/sockets/udp_multicast/CMakeLists.txt new file mode 100644 index 00000000..fe4b8fe8 --- /dev/null +++ b/examples/protocols/sockets/udp_multicast/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(udp-multicast) diff --git a/examples/protocols/sockets/udp_multicast/Makefile b/examples/protocols/sockets/udp_multicast/Makefile new file mode 100644 index 00000000..60f3d59d --- /dev/null +++ b/examples/protocols/sockets/udp_multicast/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-multicast + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/sockets/udp_multicast/README.md b/examples/protocols/sockets/udp_multicast/README.md new file mode 100644 index 00000000..8a937afc --- /dev/null +++ b/examples/protocols/sockets/udp_multicast/README.md @@ -0,0 +1,66 @@ +# UDP Multicast Example + +This example shows how to use the IPV4 & IPV6 UDP multicast features via the BSD-style sockets interface. + +## Behaviour + +The behaviour of the example is: + +* Listens to specified multicast addresses (one IPV4 and/or one IPV6). +* Print any UDP packets received as ASCII text. +* If no packets are received it will periodicially (after 2.5 seconds) send its own plaintext packet(s) to the multicast address(es). + +## Configuration + +The "Example Configuration" menu "make menuconfig" allows you to configure the details of the example: + +* WiFi SSD & Password +* IP Mode: IPV4 & IPV6 dual, IPV4 only, or IPv6 only. +* Multicast addresses for IPV4 and/or IPV6. +* Enable multicast socket loopback (ie should the socket receive its own multicast transmissions.) +* Change the interface to add the multicast group on (default interface, or WiFi STA interface.) Both methods are valid. + +## Implementation Details + +In IPV4 & IPV6 dual mode, an IPV6 socket is created and the "dual mode" options described in [RFC4038](https://tools.ietf.org/html/rfc4038) are used to bind it to the default address for both IPV4 & IPV6 and join both the configured IPV4 & IPV6 multicast groups. Otherwise, a single socket of the appropriate type is created. + +The socket is always bound to the default address, so it will also receive unicast packets. If you only want to receive multicast packets for a particular address, `bind()` to that multicast address instead. + +## Host Tools + +There are many host-side tools which can be used to interact with the UDP multicast example. One command line tool is [socat](http://www.dest-unreach.org/socat/) which can send and receive many kinds of packets. + +### Send IPV4 multicast via socat + +``` +echo "Hi there, IPv4!" | socat STDIO UDP4-DATAGRAM:232.10.11.12:3333,ip-multicast-if=(host_ip_addr) +``` + +Replace `232.10.11.12:3333` with the IPV4 multicast address and port, and `(host_ip_addr)` with the host's IP address (used to find the interface to send the multicast packet on.) + +### Receive IPV4 multicast via socat + +``` +socat STDIO UDP4-RECVFROM:3333,ip-add-membership=232.10.11.12:(host_ip_addr) +``` + +Replace `:3333` and `232.10.11.12` with the port and IPV4 multicast address, respectively. Replace `(host_ip_addr)` with the host IP address, used to find the interface to listen on. + +(The `,ip-add-membership=...` clause may not be necessary, depending on your network configuration.) + +### Send IPV6 multicast via socat + +``` +echo "Hi there, IPV6!" | socat STDIO UDP6-DATAGRAM:[ff02::fc]:3333 +``` + +Replace `[ff02::fc]:3333` with the IPV6 multicast address and port, respectively. + +### Receive IPV6 multicast via socat + +At time of writing this is not possible without patching socat. Use a different tool or programming language to receive IPV6 multicast packets. + +## About Examples + +See the README.md file in the upper level 'examples' directory for general information about examples. + diff --git a/examples/protocols/sockets/udp_multicast/main/CMakeLists.txt b/examples/protocols/sockets/udp_multicast/main/CMakeLists.txt new file mode 100644 index 00000000..d3caeb6e --- /dev/null +++ b/examples/protocols/sockets/udp_multicast/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "udp_multicast_example_main.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/protocols/sockets/udp_multicast/main/Kconfig.projbuild b/examples/protocols/sockets/udp_multicast/main/Kconfig.projbuild new file mode 100644 index 00000000..cf862e13 --- /dev/null +++ b/examples/protocols/sockets/udp_multicast/main/Kconfig.projbuild @@ -0,0 +1,91 @@ +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 "Multicast IP type" + help + Example can multicast IPV4, IPV6, or both. + +config EXAMPLE_IPV4_V6 + bool "IPV4 & IPV6" + select EXAMPLE_IPV4 + select EXAMPLE_IPV6 + +config EXAMPLE_IPV4_ONLY + bool "IPV4" + select EXAMPLE_IPV4 + +config EXAMPLE_IPV6_ONLY + bool "IPV6" + select EXAMPLE_IPV6 + +endchoice + +config EXAMPLE_IPV4 + bool +config EXAMPLE_IPV6 + bool + +config EXAMPLE_MULTICAST_IPV4_ADDR + string "Multicast IPV4 Address (send & receive)" + default "232.10.11.12" + depends on EXAMPLE_IPV4 + help + IPV4 multicast address. Example will both send to and listen to this address. + +config EXAMPLE_MULTICAST_IPV6_ADDR + string "Multicast IPV6 Address (send & receive)" + default "FF02::FC" + depends on EXAMPLE_IPV6 + help + IPV6 multicast address. Example will both send to and listen to this address. + + The default FF02::FC address is a link-local multicast address. Consult IPV6 specifications or documentation for information about meaning of different IPV6 multicast ranges. + +config EXAMPLE_PORT + int "Multicast port (send & receive)" + range 0 65535 + default 3333 + help + Multicast port the example will both send & receive UDP packets on. + +config EXAMPLE_LOOPBACK + bool "Multicast loopback" + help + Enables IP_MULTICAST_LOOP/IPV6_MULTICAST_LOOP options, meaning + that packets transmitted from the device are also received by the + device itself. + +config EXAMPLE_MULTICAST_TTL + int "Multicast packet TTL" + range 1 255 + help + Sets TTL field of multicast packets. Separate from uni- & broadcast TTL. + +choice EXAMPLE_MULTICAST_IF + prompt "Multicast Interface" + help + Multicast socket can bind to default interface, or all interfaces. + +config EXAMPLE_MULTICAST_LISTEN_DEFAULT_IF + bool "Default interface" + +config EXAMPLE_MULTICAST_LISTEN_STA_IF + bool "WiFi STA interface" + +endchoice + +endmenu 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 00000000..a98f634e --- /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/sockets/udp_multicast/main/udp_multicast_example_main.c b/examples/protocols/sockets/udp_multicast/main/udp_multicast_example_main.c new file mode 100644 index 00000000..a21c24a6 --- /dev/null +++ b/examples/protocols/sockets/udp_multicast/main/udp_multicast_example_main.c @@ -0,0 +1,553 @@ +/* UDP MultiCast Send/Receive 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 UDP_PORT CONFIG_EXAMPLE_PORT + +#define MULTICAST_LOOPBACK CONFIG_EXAMPLE_LOOPBACK + +#define MULTICAST_TTL CONFIG_EXAMPLE_MULTICAST_TTL + +#define MULTICAST_IPV4_ADDR CONFIG_EXAMPLE_MULTICAST_IPV4_ADDR +#define MULTICAST_IPV6_ADDR CONFIG_EXAMPLE_MULTICAST_IPV6_ADDR + +#define LISTEN_DEFAULT_IF CONFIG_EXAMPLE_LISTEN_DEFAULT_IF + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + we use two - one for IPv4 "got ip", and + one for IPv6 "got ip". */ +const int IPV4_GOTIP_BIT = BIT0; +const int IPV6_GOTIP_BIT = BIT1; + +static const char *TAG = "multicast"; +#ifdef CONFIG_EXAMPLE_IPV4 +static const char *V4TAG = "mcast-ipv4"; +#endif +#ifdef CONFIG_EXAMPLE_IPV6 +static const char *V6TAG = "mcast-ipv6"; +#endif + +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_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); + 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); + 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() ); +} + +#ifdef CONFIG_EXAMPLE_IPV4 +/* Add a socket, either IPV4-only or IPV6 dual mode, to the IPV4 + multicast group */ +static int socket_add_ipv4_multicast_group(int sock, bool assign_source_if) +{ + struct ip_mreq imreq = { 0 }; + struct in_addr iaddr = { 0 }; + int err = 0; + // Configure source interface +#if USE_DEFAULT_IF + imreq.imr_interface.s_addr = IPADDR_ANY; +#else + tcpip_adapter_ip_info_t ip_info = { 0 }; + err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); + if (err != ESP_OK) { + ESP_LOGE(V4TAG, "Failed to get IP address info. Error 0x%x", err); + goto err; + } + inet_addr_from_ip4addr(&iaddr, &ip_info.ip); +#endif + // Configure multicast address to listen to + err = inet_aton(MULTICAST_IPV4_ADDR, &imreq.imr_multiaddr.s_addr); + if (err != 1) { + ESP_LOGE(V4TAG, "Configured IPV4 multicast address '%s' is invalid.", MULTICAST_IPV4_ADDR); + goto err; + } + ESP_LOGI(TAG, "Configured IPV4 Multicast address %s", inet_ntoa(imreq.imr_multiaddr.s_addr)); + if (!IP_MULTICAST(ntohl(imreq.imr_multiaddr.s_addr))) { + ESP_LOGW(V4TAG, "Configured IPV4 multicast address '%s' is not a valid multicast address. This will probably not work.", MULTICAST_IPV4_ADDR); + } + + if (assign_source_if) { + // Assign the IPv4 multicast source interface, via its IP + // (only necessary if this socket is IPV4 only) + err = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &iaddr, + sizeof(struct in_addr)); + if (err < 0) { + ESP_LOGE(V4TAG, "Failed to set IP_MULTICAST_IF. Error %d", errno); + goto err; + } + } + + err = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &imreq, sizeof(struct ip_mreq)); + if (err < 0) { + ESP_LOGE(V4TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno); + goto err; + } + + err: + return err; +} +#endif /* CONFIG_EXAMPLE_IPV4 */ + +#ifdef CONFIG_EXAMPLE_IPV4_ONLY +static int create_multicast_ipv4_socket() +{ + struct sockaddr_in saddr = { 0 }; + int sock = -1; + int err = 0; + + sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sock < 0) { + ESP_LOGE(V4TAG, "Failed to create socket. Error %d", errno); + return -1; + } + + // Bind the socket to any address + saddr.sin_family = PF_INET; + saddr.sin_port = htons(UDP_PORT); + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)); + if (err < 0) { + ESP_LOGE(V4TAG, "Failed to bind socket. Error %d", errno); + goto err; + } + + + // Assign multicast TTL (set separately from normal interface TTL) + uint8_t ttl = MULTICAST_TTL; + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(uint8_t)); + if (err < 0) { + ESP_LOGE(V4TAG, "Failed to set IP_MULTICAST_TTL. Error %d", errno); + goto err; + } + +#if MULTICAST_LOOPBACK + // select whether multicast traffic should be received by this device, too + // (if setsockopt() is not called, the default is no) + uint8_t loopback_val = MULTICAST_LOOPBACK; + err = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, + &loopback_val, sizeof(uint8_t)); + if (err < 0) { + ESP_LOGE(V4TAG, "Failed to set IP_MULTICAST_LOOP. Error %d", errno); + goto err; + } +#endif + + // this is also a listening socket, so add it to the multicast + // group for listening... + err = socket_add_ipv4_multicast_group(sock, true); + if (err < 0) { + goto err; + } + + // All set, socket is configured for sending and receiving + return sock; + +err: + close(sock); + return -1; +} +#endif /* CONFIG_EXAMPLE_IPV4_ONLY */ + +#ifdef CONFIG_EXAMPLE_IPV6 +static int create_multicast_ipv6_socket() +{ + struct sockaddr_in6 saddr = { 0 }; + struct in6_addr if_inaddr = { 0 }; + struct ip6_addr if_ipaddr = { 0 }; + struct ip6_mreq v6imreq = { 0 }; + int sock = -1; + int err = 0; + + sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IPV6); + if (sock < 0) { + ESP_LOGE(V6TAG, "Failed to create socket. Error %d", errno); + return -1; + } + + // Bind the socket to any address + saddr.sin6_family = AF_INET6; + saddr.sin6_port = htons(UDP_PORT); + bzero(&saddr.sin6_addr.un, sizeof(saddr.sin6_addr.un)); + err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6)); + if (err < 0) { + ESP_LOGE(V6TAG, "Failed to bind socket. Error %d", errno); + goto err; + } + + // Selct the interface to use as multicast source for this socket. +#if USE_DEFAULT_IF + bzero(&if_inaddr.un, sizeof(if_inaddr.un)); +#else + // Read interface adapter link-local address and use it + // to bind the multicast IF to this socket. + // + // (Note the interface may have other non-LL IPV6 addresses as well, + // but it doesn't matter in this context as the address is only + // used to identify the interface.) + err = tcpip_adapter_get_ip6_linklocal(TCPIP_ADAPTER_IF_STA, &if_ipaddr); + inet6_addr_from_ip6addr(&if_inaddr, &if_ipaddr); + if (err != ESP_OK) { + ESP_LOGE(V6TAG, "Failed to get IPV6 LL address. Error 0x%x", err); + goto err; + } +#endif + + // Assign the multicast source interface, via its IP + err = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_inaddr, + sizeof(struct in6_addr)); + if (err < 0) { + ESP_LOGE(V6TAG, "Failed to set IPV6_MULTICAST_IF. Error %d", errno); + goto err; + } + + // Assign multicast TTL (set separately from normal interface TTL) + uint8_t ttl = MULTICAST_TTL; + setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(uint8_t)); + if (err < 0) { + ESP_LOGE(V6TAG, "Failed to set IPV6_MULTICAST_HOPS. Error %d", errno); + goto err; + } + +#if MULTICAST_LOOPBACK + // select whether multicast traffic should be received by this device, too + // (if setsockopt() is not called, the default is no) + uint8_t loopback_val = MULTICAST_LOOPBACK; + err = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, + &loopback_val, sizeof(uint8_t)); + if (err < 0) { + ESP_LOGE(V6TAG, "Failed to set IPV6_MULTICAST_LOOP. Error %d", errno); + goto err; + } +#endif + + // this is also a listening socket, so add it to the multicast + // group for listening... + + // Configure source interface +#if USE_DEFAULT_IF + v6imreq.imr_interface.s_addr = IPADDR_ANY; +#else + inet6_addr_from_ip6addr(&v6imreq.ipv6mr_interface, &if_ipaddr); +#endif +#ifdef CONFIG_EXAMPLE_IPV6 + // Configure multicast address to listen to + err = inet6_aton(MULTICAST_IPV6_ADDR, &v6imreq.ipv6mr_multiaddr); + if (err != 1) { + ESP_LOGE(V6TAG, "Configured IPV6 multicast address '%s' is invalid.", MULTICAST_IPV6_ADDR); + goto err; + } + ESP_LOGI(TAG, "Configured IPV6 Multicast address %s", inet6_ntoa(v6imreq.ipv6mr_multiaddr)); + ip6_addr_t multi_addr; + inet6_addr_to_ip6addr(&multi_addr, &v6imreq.ipv6mr_multiaddr); + if (!ip6_addr_ismulticast(&multi_addr)) { + ESP_LOGW(V6TAG, "Configured IPV6 multicast address '%s' is not a valid multicast address. This will probably not work.", MULTICAST_IPV6_ADDR); + } + + err = setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, + &v6imreq, sizeof(struct ip6_mreq)); + if (err < 0) { + ESP_LOGE(V6TAG, "Failed to set IPV6_ADD_MEMBERSHIP. Error %d", errno); + goto err; + } +#endif + +#if CONFIG_EXAMPLE_IPV4_V6 + // Add the common IPV4 config options + err = socket_add_ipv4_multicast_group(sock, false); + if (err < 0) { + goto err; + } +#endif + +#if CONFIG_EXAMPLE_IPV4_V6 + int only = 0; +#else + int only = 1; /* IPV6-only socket */ +#endif + err = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &only, sizeof(int)); + if (err < 0) { + ESP_LOGE(V6TAG, "Failed to set IPV6_V6ONLY. Error %d", errno); + goto err; + } + ESP_LOGI(TAG, "Socket set IPV6-only"); + + // All set, socket is configured for sending and receiving + return sock; + +err: + close(sock); + return -1; +} +#endif + +static void mcast_example_task(void *pvParameters) +{ + while (1) { + /* Wait for all the IPs we care about to be set + */ + uint32_t bits = 0; +#ifdef CONFIG_EXAMPLE_IPV4 + bits |= IPV4_GOTIP_BIT; +#endif +#ifdef CONFIG_EXAMPLE_IPV6 + bits |= IPV6_GOTIP_BIT; +#endif + ESP_LOGI(TAG, "Waiting for AP connection..."); + xEventGroupWaitBits(wifi_event_group, bits, false, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connected to AP"); + + int sock; + +#ifdef CONFIG_EXAMPLE_IPV4_ONLY + sock = create_multicast_ipv4_socket(); + if (sock < 0) { + ESP_LOGE(TAG, "Failed to create IPv4 multicast socket"); + } +#else + sock = create_multicast_ipv6_socket(); + if (sock < 0) { + ESP_LOGE(TAG, "Failed to create IPv6 multicast socket"); + } +#endif + + if (sock < 0) { + // Nothing to do! + vTaskDelay(5 / portTICK_PERIOD_MS); + continue; + } + +#ifdef CONFIG_EXAMPLE_IPV4 + // set destination multicast addresses for sending from these sockets + struct sockaddr_in sdestv4 = { + .sin_family = PF_INET, + .sin_port = htons(UDP_PORT), + }; + // We know this inet_aton will pass because we did it above already + inet_aton(MULTICAST_IPV4_ADDR, &sdestv4.sin_addr.s_addr); +#endif + +#ifdef CONFIG_EXAMPLE_IPV6 + struct sockaddr_in6 sdestv6 = { + .sin6_family = PF_INET6, + .sin6_port = htons(UDP_PORT), + }; + // We know this inet_aton will pass because we did it above already + inet6_aton(MULTICAST_IPV6_ADDR, &sdestv6.sin6_addr); +#endif + + // Loop waiting for UDP received, and sending UDP packets if we don't + // see any. + int err = 1; + while (err > 0) { + struct timeval tv = { + .tv_sec = 2, + .tv_usec = 0, + }; + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(sock, &rfds); + + int s = select(sock + 1, &rfds, NULL, NULL, &tv); + if (s < 0) { + ESP_LOGE(TAG, "Select failed: errno %d", errno); + err = -1; + break; + } + else if (s > 0) { + if (FD_ISSET(sock, &rfds)) { + // Incoming datagram received + char recvbuf[48]; + char raddr_name[32] = { 0 }; + + struct sockaddr_in6 raddr; // Large enough for both IPv4 or IPv6 + socklen_t socklen = sizeof(raddr); + int len = recvfrom(sock, recvbuf, sizeof(recvbuf)-1, 0, + (struct sockaddr *)&raddr, &socklen); + if (len < 0) { + ESP_LOGE(TAG, "multicast recvfrom failed: errno %d", errno); + err = -1; + break; + } + + // Get the sender's address as a string +#ifdef CONFIG_EXAMPLE_IPV4 + if (raddr.sin6_family == PF_INET) { + inet_ntoa_r(((struct sockaddr_in *)&raddr)->sin_addr.s_addr, + raddr_name, sizeof(raddr_name)-1); + } +#endif +#ifdef CONFIG_EXAMPLE_IPV6 + if (raddr.sin6_family == PF_INET6) { + inet6_ntoa_r(raddr.sin6_addr, raddr_name, sizeof(raddr_name)-1); + } +#endif + ESP_LOGI(TAG, "received %d bytes from %s:", len, raddr_name); + + recvbuf[len] = 0; // Null-terminate whatever we received and treat like a string... + ESP_LOGI(TAG, "%s", recvbuf); + } + } + else { // s == 0 + // Timeout passed with no incoming data, so send something! + static int send_count; + const char sendfmt[] = "Multicast #%d sent by ESP32\n"; + char sendbuf[48]; + char addrbuf[32] = { 0 }; + int len = snprintf(sendbuf, sizeof(sendbuf), sendfmt, send_count++); + if (len > sizeof(sendbuf)) { + ESP_LOGE(TAG, "Overflowed multicast sendfmt buffer!!"); + send_count = 0; + err = -1; + break; + } + + struct addrinfo hints = { + .ai_flags = AI_PASSIVE, + .ai_socktype = SOCK_DGRAM, + }; + struct addrinfo *res; + +#ifdef CONFIG_EXAMPLE_IPV4 // Send an IPv4 multicast packet + +#ifdef CONFIG_EXAMPLE_IPV4_ONLY + hints.ai_family = AF_INET; // For an IPv4 socket +#else + hints.ai_family = AF_INET6; // For an IPv4 socket with V4 mapped addresses + hints.ai_flags |= AI_V4MAPPED; +#endif + int err = getaddrinfo(CONFIG_EXAMPLE_MULTICAST_IPV4_ADDR, + NULL, + &hints, + &res); + if (err < 0) { + ESP_LOGE(TAG, "getaddrinfo() failed for IPV4 destination address. error: %d", err); + break; + } +#ifdef CONFIG_EXAMPLE_IPV4_ONLY + ((struct sockaddr_in *)res->ai_addr)->sin_port = htons(UDP_PORT); + inet_ntoa_r(((struct sockaddr_in *)res->ai_addr)->sin_addr, addrbuf, sizeof(addrbuf)-1); + ESP_LOGI(TAG, "Sending to IPV4 multicast address %s:%d...", addrbuf, UDP_PORT); +#else + ((struct sockaddr_in6 *)res->ai_addr)->sin6_port = htons(UDP_PORT); + inet6_ntoa_r(((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, addrbuf, sizeof(addrbuf)-1); + ESP_LOGI(TAG, "Sending to IPV6 (V4 mapped) multicast address %s port %d (%s)...", addrbuf, UDP_PORT, CONFIG_EXAMPLE_MULTICAST_IPV4_ADDR); +#endif + err = sendto(sock, sendbuf, len, 0, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + if (err < 0) { + ESP_LOGE(TAG, "IPV4 sendto failed. errno: %d", errno); + break; + } +#endif +#ifdef CONFIG_EXAMPLE_IPV6 + hints.ai_family = AF_INET6; + hints.ai_protocol = 0; + err = getaddrinfo(CONFIG_EXAMPLE_MULTICAST_IPV6_ADDR, + NULL, + &hints, + &res); + if (err < 0) { + ESP_LOGE(TAG, "getaddrinfo() failed for IPV6 destination address. error: %d", err); + break; + } + + struct sockaddr_in6 *s6addr = (struct sockaddr_in6 *)res->ai_addr; + s6addr->sin6_port = htons(UDP_PORT); + inet6_ntoa_r(s6addr->sin6_addr, addrbuf, sizeof(addrbuf)-1); + ESP_LOGI(TAG, "Sending to IPV6 multicast address %s port %d...", addrbuf, UDP_PORT); + err = sendto(sock, sendbuf, len, 0, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + if (err < 0) { + ESP_LOGE(TAG, "IPV6 sendto failed. errno: %d", errno); + break; + } +#endif + } + } + + ESP_LOGE(TAG, "Shutting down socket and restarting..."); + shutdown(sock, 0); + close(sock); + } + +} + +void app_main() +{ + ESP_ERROR_CHECK( nvs_flash_init() ); + initialise_wifi(); + xTaskCreate(&mcast_example_task, "mcast_task", 4096, NULL, 5, NULL); +} diff --git a/examples/protocols/sockets/udp_server/CMakeLists.txt b/examples/protocols/sockets/udp_server/CMakeLists.txt new file mode 100644 index 00000000..d1edab0f --- /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 00000000..36552c9b --- /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 00000000..8fe64a73 --- /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 00000000..55a6bebd --- /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 00000000..47211138 --- /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 00000000..a98f634e --- /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 00000000..336388f7 --- /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); +}