Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the ESP32 Echo Server use Inet #534

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions examples/wifi-echo/esp32/main/EchoClient.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
*
* Copyright (c) 2020 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, softwarEchoe
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "tcpip_adapter.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>

#define PORT CONFIG_ECHO_PORT
#define RX_LEN 128
#define ADDR_LEN 128

#define HOST_IP_ADDR CONFIG_ECHO_HOST_IP

static const char * TAG = "echo_client";
static const char * PAYLOAD = "Message from echo client!";

static void udp_client_task(void * pvParameters)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we plan to use InetLayer for client (maybe in a follow on PR)?

Copy link
Contributor Author

@sagar-apple sagar-apple Apr 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally chose not to use Inet on the first pass of this Client. I wanted the client to emulate a pure socket echo client. Mainly to demonstrate that the Server will respond to anything, not just things delivered via CHIP components.

Like you suggested, I might migrate it in a follow-up PR if we see fit.

{
char rx_buffer[RX_LEN];
char host_ip[] = HOST_IP_ADDR;
int addr_family = 0;
int ip_protocol = 0;

while (1)
{
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(PORT);
addr_family = AF_INET;
ip_protocol = IPPROTO_IP;

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, sending to %s:%d", HOST_IP_ADDR, PORT);

while (1)
{
int err = sendto(sock, PAYLOAD, strlen(PAYLOAD), 0, (struct sockaddr *) &dest_addr, sizeof(dest_addr));
if (err < 0)
{
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
break;
}
ESP_LOGI(TAG, "Message sent");

struct sockaddr_in source_addr; // Large enough for both IPv4 or IPv6
socklen_t socklen = sizeof(source_addr);
int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *) &source_addr, &socklen);

// Error occurred during receiving
if (len < 0)
{
ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
continue;
}
// Data received
else
{
rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
if (strncmp(rx_buffer, PAYLOAD, strlen(PAYLOAD)) == 0)
{
ESP_LOGI(TAG, "Received expected message...");
}
}

vTaskDelay(5000 / portTICK_PERIOD_MS);
}

if (sock != -1)
{
ESP_LOGE(TAG, "Shutting down socket and restarting...");
shutdown(sock, 0);
close(sock);
}
}
vTaskDelete(NULL);
}

// The echo client assumes the platform's networking has been setup already
void startClient(void)
{
xTaskCreate(udp_client_task, "udp_client", 4096, (void *) AF_INET, 5, NULL);
}
174 changes: 75 additions & 99 deletions examples/wifi-echo/esp32/main/EchoServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,125 +31,101 @@
#include "lwip/sys.h"
#include <lwip/netdb.h>

#include <inet/UDPEndPoint.h>
#include <inet/InetError.h>
#include <inet/InetLayer.h>
#include <inet/IPAddress.h>
#include <system/SystemPacketBuffer.h>
#include <support/ErrorStr.h>
#include <platform/CHIPDeviceLayer.h>

#define PORT CONFIG_ECHO_PORT
#define RX_LEN 128
#define ADDR_LEN 128

static const char * TAG = "echo server";
static const char * TAG = "echo_server";

using namespace ::chip;
using namespace ::chip::Inet;

static void udp_server_task(void * pvParameters)
// UDP Endpoint Callbacks
static void echo(IPEndPointBasis * endpoint, System::PacketBuffer * buffer, const IPPacketInfo * packet_info)
{
char rx_buffer[RX_LEN];
char addr_str[ADDR_LEN];
int addr_family = (int) pvParameters;
int ip_protocol = 0;
struct sockaddr_in6 dest_addr;
bool status = endpoint != NULL && buffer != NULL && packet_info != NULL;

while (1)
if (status)
{
char src_addr[INET_ADDRSTRLEN];
char dest_addr[INET_ADDRSTRLEN];

if (addr_family == AF_INET)
{
struct sockaddr_in * dest_addr_ip4 = (struct sockaddr_in *) &dest_addr;
dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr_ip4->sin_family = AF_INET;
dest_addr_ip4->sin_port = htons(PORT);
ip_protocol = IPPROTO_IP;
}
else if (addr_family == AF_INET6)
{
bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un));
dest_addr.sin6_family = AF_INET6;
dest_addr.sin6_port = htons(PORT);
ip_protocol = IPPROTO_IPV6;
}
packet_info->SrcAddress.ToString(src_addr, sizeof(src_addr));
packet_info->DestAddress.ToString(dest_addr, sizeof(dest_addr));

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");
ESP_LOGI(TAG, "UDP packet received from %s:%u to %s:%u (%zu bytes)", src_addr, packet_info->SrcPort, dest_addr,
packet_info->DestPort, static_cast<size_t>(buffer->DataLength()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: rather than calling buffer->DataLength() repeatedly, I'd assign it to a const value at the top of the block and then use that const value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I'll push a follow-up.


#if defined(CONFIG_ECHO_IPV4) && defined(CONFIG_ECHO_IPV6)
if (addr_family == AF_INET6)
{
// Note that by default IPV6 binds to both protocols, it is must be disabled
// if both protocols used at the same time (used in CI)
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
}
#endif
// attempt to print the incoming message
char msg_buffer[buffer->DataLength() + 1];
msg_buffer[buffer->DataLength()] = 0; // Null-terminate whatever we received and treat like a string...
memcpy(msg_buffer, buffer->Start(), buffer->DataLength());
ESP_LOGI(TAG, "Client sent: \"%s\"", msg_buffer);

int err = bind(sock, (struct sockaddr *) &dest_addr, sizeof(dest_addr));
if (err < 0)
// Attempt to echo back
UDPEndPoint * udp_endpoint = static_cast<UDPEndPoint *>(endpoint);
INET_ERROR err = udp_endpoint->SendTo(packet_info->SrcAddress, packet_info->SrcPort, buffer);
if (err != INET_NO_ERROR)
{
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
// Avoid looping hard if binding fails continuously
vTaskDelay(50 / portTICK_PERIOD_MS);
continue;
ESP_LOGE(TAG, "Unable to echo back to client: %s", ErrorStr(err));
// Note the failure status
status = !status;
}
ESP_LOGI(TAG, "Socket bound, port %d", PORT);

while (1)
else
{

ESP_LOGI(TAG, "Waiting for data");
struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6
socklen_t socklen = sizeof(source_addr);
int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *) &source_addr, &socklen);

// Error occurred 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 (source_addr.sin6_family == PF_INET)
{
inet_ntoa_r(((struct sockaddr_in *) &source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);
}
else if (source_addr.sin6_family == PF_INET6)
{
inet6_ntoa_r(source_addr.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 *) &source_addr, sizeof(source_addr));
if (err < 0)
{
ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
break;
}
}
ESP_LOGI(TAG, "Echo sent");
}
}

if (sock != -1)
if (!status)
{
ESP_LOGE(TAG, "Received data but couldn't process it...");

// SendTo calls Free on the buffer without an AddRef, if SendTo was not called, free the buffer.
if (buffer != NULL)
{
ESP_LOGE(TAG, "Shutting down socket and restarting...");
shutdown(sock, 0);
close(sock);
System::PacketBuffer::Free(buffer);
}
}
vTaskDelete(NULL);
}

static void error(IPEndPointBasis * ep, INET_ERROR error, const IPPacketInfo * pi)
{
ESP_LOGE(TAG, "ERROR: %s\n Got UDP error", ErrorStr(error));
}

// The echo server assumes the platform's networking has been setup already
void startServer(void)
void startServer(UDPEndPoint * endpoint)
{
#ifdef CONFIG_ECHO_IPV4
xTaskCreate(udp_server_task, "udp_server", 4096, (void *) AF_INET, 5, NULL);
#endif
#ifdef CONFIG_ECHO_IPV6
xTaskCreate(udp_server_task, "udp_server", 4096, (void *) AF_INET6, 5, NULL);
#endif
ESP_LOGI(TAG, "Trying to get Inet");
INET_ERROR err = DeviceLayer::InetLayer.NewUDPEndPoint(&endpoint);
if (err != INET_NO_ERROR)
{
ESP_LOGE(TAG, "ERROR: %s\n Couldn't create UDP Endpoint, server will not start.", ErrorStr(err));
return;
}

endpoint->OnMessageReceived = echo;
endpoint->OnReceiveError = error;

err = endpoint->Bind(kIPAddressType_IPv4, IPAddress::Any, PORT);
if (err != INET_NO_ERROR)
{
ESP_LOGE(TAG, "Socket unable to bind: Error %s", ErrorStr(err));
return;
}

err = endpoint->Listen();
if (err != INET_NO_ERROR)
{
ESP_LOGE(TAG, "Socket unable to Listen: Error %s", ErrorStr(err));
return;
}
ESP_LOGI(TAG, "Echo Server Listening on PORT:%d...", PORT);
}
20 changes: 12 additions & 8 deletions examples/wifi-echo/esp32/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,12 @@ menu "WiFi Echo Demo"
bool "M5Stack"
endchoice

config ECHO_IPV4
bool "IPV4"
default y

config ECHO_IPV6
bool "IPV6"
default n
select EXAMPLE_CONNECT_IPV6
config USE_ECHO_CLIENT
bool "Enable the built-in Echo Client"
default "y"
help
This enables a local FreeRTOS Echo Client so that the end-to-end echo server can be
tested easily

config ECHO_PORT
int "Port"
Expand All @@ -51,4 +49,10 @@ menu "WiFi Echo Demo"
help
Local port the example server will listen on.

config ECHO_HOST_IP
string "IPV4 address"
default "127.0.0.1"
help
The IPV4 Address of the ECHO Server.

endmenu
9 changes: 7 additions & 2 deletions examples/wifi-echo/esp32/main/wifi-echo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
using namespace ::chip;
using namespace ::chip::DeviceLayer;

extern void startServer(void);
extern void startServer(UDPEndPoint * endpoint);
extern void startClient(void);

#if CONFIG_DEVICE_TYPE_M5STACK

Expand Down Expand Up @@ -150,7 +151,11 @@ extern "C" void app_main()
statusLED.Init(STATUS_LED_GPIO_NUM);

// Start the Echo Server
startServer();
UDPEndPoint * sEndpoint = NULL;
startServer(sEndpoint);
#if CONFIG_USE_ECHO_CLIENT
startClient();
#endif

// Run the UI Loop
while (true)
Expand Down