Merge branch 'feature/add_ping_example' into 'master'

add ping example

Closes WIFI-1196 and IDF-1068

See merge request espressif/esp-idf!6340
This commit is contained in:
Angus Gratton
2019-10-24 11:37:38 +08:00
22 changed files with 1335 additions and 271 deletions

View File

@@ -10,6 +10,10 @@
#include "esp_event.h"
#include "esp_eth.h"
#include "esp_log.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "ping/ping_sock.h"
static const char *TAG = "esp_eth_test";
@@ -17,11 +21,14 @@ static const char *TAG = "esp_eth_test";
#define ETH_STOP_BIT BIT(1)
#define ETH_CONNECT_BIT BIT(2)
#define ETH_GOT_IP_BIT BIT(3)
#define ETH_PING_END_BIT BIT(4)
#define ETH_START_TIMEOUT_MS (10000)
#define ETH_CONNECT_TIMEOUT_MS (40000)
#define ETH_STOP_TIMEOUT_MS (10000)
#define ETH_GET_IP_TIMEOUT_MS (60000)
#define ETH_PING_DURATION_MS (5000)
#define ETH_PING_END_TIMEOUT_MS (ETH_PING_DURATION_MS * 2)
/** Event handler for Ethernet events */
static void eth_event_handler(void *arg, esp_event_base_t event_base,
@@ -66,6 +73,46 @@ static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
xEventGroupSetBits(eth_event_group, ETH_GOT_IP_BIT);
}
static void test_on_ping_success(esp_ping_handle_t hdl, void *args)
{
uint8_t ttl;
uint16_t seqno;
uint32_t elapsed_time, recv_len;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
printf("%d bytes from %s icmp_seq=%d ttl=%d time=%d ms\n",
recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time);
}
static void test_on_ping_timeout(esp_ping_handle_t hdl, void *args)
{
uint16_t seqno;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
printf("From %s icmp_seq=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno);
}
static void test_on_ping_end(esp_ping_handle_t hdl, void *args)
{
EventGroupHandle_t eth_event_group = (EventGroupHandle_t)args;
uint32_t transmitted;
uint32_t received;
uint32_t total_time_ms;
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
printf("%d packets transmitted, %d received, time %dms\n", transmitted, received, total_time_ms);
if (transmitted == received) {
xEventGroupSetBits(eth_event_group, ETH_PING_END_BIT);
}
}
TEST_CASE("esp32 ethernet io test", "[ethernet][test_env=UT_T2_Ethernet]")
{
TEST_ESP_OK(esp_event_loop_create_default());
@@ -163,6 +210,83 @@ TEST_CASE("esp32 ethernet dhcp test", "[ethernet][test_env=UT_T2_Ethernet]")
vEventGroupDelete(eth_event_group);
}
TEST_CASE("esp32 ethernet icmp test", "[ethernet][test_env=UT_T2_Ethernet]")
{
EventBits_t bits = 0;
EventGroupHandle_t eth_event_group = xEventGroupCreate();
TEST_ASSERT(eth_event_group != NULL);
test_case_uses_tcpip();
TEST_ESP_OK(esp_event_loop_create_default());
TEST_ESP_OK(tcpip_adapter_set_default_eth_handlers());
TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, eth_event_group));
TEST_ESP_OK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, eth_event_group));
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
esp_eth_handle_t eth_handle = NULL;
TEST_ESP_OK(esp_eth_driver_install(&eth_config, &eth_handle));
/* wait for IP lease */
bits = xEventGroupWaitBits(eth_event_group, ETH_GOT_IP_BIT, true, true, pdMS_TO_TICKS(ETH_GET_IP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_GOT_IP_BIT) == ETH_GOT_IP_BIT);
// Parse IP address
ip_addr_t target_addr;
struct addrinfo hint;
struct addrinfo *res = NULL;
memset(&hint, 0, sizeof(hint));
memset(&target_addr, 0, sizeof(target_addr));
/* convert URL to IP */
TEST_ASSERT(getaddrinfo("www.baidu.com", NULL, &hint, &res) == 0);
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
freeaddrinfo(res);
esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();
ping_config.target_addr = target_addr;
ping_config.count = 0; // ping in infinite mode
/* set callback functions */
esp_ping_callbacks_t cbs;
cbs.on_ping_success = test_on_ping_success;
cbs.on_ping_timeout = test_on_ping_timeout;
cbs.on_ping_end = test_on_ping_end;
cbs.cb_args = eth_event_group;
esp_ping_handle_t ping;
TEST_ESP_OK(esp_ping_new_session(&ping_config, &cbs, &ping));
/* start ping */
TEST_ESP_OK(esp_ping_start(ping));
/* ping for a while */
vTaskDelay(pdMS_TO_TICKS(ETH_PING_DURATION_MS));
/* stop ping */
TEST_ESP_OK(esp_ping_stop(ping));
/* wait for end of ping */
bits = xEventGroupWaitBits(eth_event_group, ETH_PING_END_BIT, true, true, pdMS_TO_TICKS(ETH_PING_END_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_PING_END_BIT) == ETH_PING_END_BIT);
/* restart ping */
TEST_ESP_OK(esp_ping_start(ping));
vTaskDelay(pdMS_TO_TICKS(ETH_PING_DURATION_MS));
TEST_ESP_OK(esp_ping_stop(ping));
bits = xEventGroupWaitBits(eth_event_group, ETH_PING_END_BIT, true, true, pdMS_TO_TICKS(ETH_PING_END_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_PING_END_BIT) == ETH_PING_END_BIT);
/* de-initialize ping process */
TEST_ESP_OK(esp_ping_delete_session(ping));
TEST_ESP_OK(esp_eth_driver_uninstall(eth_handle));
/* wait for connection stop */
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
// "check link timer callback" might owned the reference of phy object, make sure it has release it
vTaskDelay(pdMS_TO_TICKS(2000));
TEST_ESP_OK(phy->del(phy));
TEST_ESP_OK(mac->del(mac));
TEST_ESP_OK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, got_ip_event_handler));
TEST_ESP_OK(tcpip_adapter_clear_default_eth_handlers());
TEST_ESP_OK(esp_event_loop_delete_default());
vEventGroupDelete(eth_event_group);
}
#if CONFIG_ETH_USE_SPI_ETHERNET
TEST_CASE("dm9051 io test", "[ethernet][ignore]")
{

View File

@@ -10,6 +10,7 @@ set(srcs
"apps/dhcpserver/dhcpserver.c"
"apps/ping/esp_ping.c"
"apps/ping/ping.c"
"apps/ping/ping_sock.c"
"apps/sntp/sntp.c"
"lwip/src/api/api_lib.c"
"lwip/src/api/api_msg.c"

View File

@@ -5,7 +5,7 @@
*/
/*
* Redistribution and use in source and binary forms, with or without modification,
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
@@ -14,24 +14,24 @@
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* This file is part of the lwIP TCP/IP stack.
*
*
*/
/**
/**
* This is an example of a "ping" sender (with raw API and socket API).
* It can be used as a start point to maintain opened a network connection, or
* like a network "watchdog" for your device.
@@ -55,6 +55,7 @@
#if PING_USE_SOCKETS
#include "lwip/sockets.h"
#include "lwip/inet.h"
#include "ping/ping_sock.h"
#endif /* PING_USE_SOCKETS */
#ifdef ESP_PING
@@ -98,20 +99,17 @@
#define PING_RESULT(ping_ok)
#endif
/* ping variables */
static u16_t ping_seq_num;
static struct timeval ping_time;
#if ESP_PING
static sys_sem_t ping_sem = NULL;
static bool ping_init_flag = false;
#endif
#if !PING_USE_SOCKETS
static struct raw_pcb *ping_pcb;
#endif /* PING_USE_SOCKETS */
#define PING_TIME_DIFF_MS(_end, _start) ((uint32_t)(((_end).tv_sec - (_start).tv_sec) * 1000 + (_end.tv_usec - _start.tv_usec)/1000))
#define PING_TIME_DIFF_SEC(_end, _start) ((uint32_t)((_end).tv_sec - (_start).tv_sec))
#if PING_USE_SOCKETS
/* ping handle */
static esp_ping_handle_t ping = NULL;
#else
static struct raw_pcb *ping_pcb;
static u16_t ping_seq_num;
static struct timeval ping_time;
/** Prepare a echo ICMP request */
static void
ping_prepare_echo( struct icmp_echo_hdr *iecho, u16_t len)
@@ -133,213 +131,6 @@ ping_prepare_echo( struct icmp_echo_hdr *iecho, u16_t len)
iecho->chksum = inet_chksum(iecho, len);
}
#if PING_USE_SOCKETS
/* Ping using the socket ip */
static err_t
ping_send(int s, ip_addr_t *addr)
{
int err;
struct icmp_echo_hdr *iecho;
struct sockaddr_in to;
size_t ping_size;
#ifdef ESP_PING
size_t ping_data_len = 0;
esp_ping_get_target(PING_TARGET_DATA_LEN, &ping_data_len, sizeof(ping_data_len));
if (ping_data_len > 0) {
ping_size = sizeof(struct icmp_echo_hdr) + ping_data_len;
} else {
ping_size = sizeof(struct icmp_echo_hdr) + PING_DATA_SIZE;
}
#else
ping_size = sizeof(struct icmp_echo_hdr) + PING_DATA_SIZE;
#endif
LWIP_ASSERT("ping_size is too big", ping_size <= 0xffff);
LWIP_ASSERT("ping: expect IPv4 address", !IP_IS_V6(addr));
iecho = (struct icmp_echo_hdr *)mem_malloc((mem_size_t)ping_size);
if (!iecho) {
return ERR_MEM;
}
ping_prepare_echo(iecho, (u16_t)ping_size);
to.sin_len = sizeof(to);
to.sin_family = AF_INET;
inet_addr_from_ip4addr(&to.sin_addr, ip_2_ip4(addr));
err = sendto(s, iecho, ping_size, 0, (struct sockaddr*)&to, sizeof(to));
mem_free(iecho);
return (err ? ERR_OK : ERR_VAL);
}
static void
ping_recv(int s)
{
char buf[64];
int len;
struct sockaddr_in from;
struct ip_hdr *iphdr;
struct icmp_echo_hdr *iecho;
int fromlen = sizeof(from);
struct timeval now;
while((len = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, (socklen_t*)&fromlen)) > 0) {
if (len >= (int)(sizeof(struct ip_hdr)+sizeof(struct icmp_echo_hdr))) {
if (from.sin_family != AF_INET) {
/* Ping is not IPv4 */
LWIP_DEBUGF( PING_DEBUG, ("ping: invalid sin_family\n"));
} else {
ip4_addr_t fromaddr;
inet_addr_to_ip4addr(&fromaddr, &from.sin_addr);
iphdr = (struct ip_hdr *)buf;
iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4));
LWIP_DEBUGF( PING_DEBUG, ("ping: recv seq=%d ", ntohs(iecho->seqno)));
ip4_addr_debug_print(PING_DEBUG, &fromaddr);
gettimeofday(&now, NULL);
LWIP_DEBUGF( PING_DEBUG, (" %"U32_F" ms\n", PING_TIME_DIFF_MS(now, ping_time)));
if ((iecho->id == PING_ID) && (iecho->seqno == htons(ping_seq_num))) {
/* do some ping result processing */
#ifdef ESP_PING
esp_ping_result((ICMPH_TYPE(iecho) == ICMP_ER), len, PING_TIME_DIFF_MS(now, ping_time));
#else
PING_RESULT((ICMPH_TYPE(iecho) == ICMP_ER));
#endif
return;
} else {
LWIP_DEBUGF( PING_DEBUG, ("ping: drop\n"));
}
}
}
fromlen = sizeof(from);
}
gettimeofday(&now, NULL);
if (len == 0) {
LWIP_DEBUGF( PING_DEBUG, ("ping: recv - %"U32_F" ms - timeout\n", PING_TIME_DIFF_MS(now, ping_time)));
}
/* do some ping result processing */
#ifdef ESP_PING
esp_ping_result(0, len, PING_TIME_DIFF_MS(now, ping_time));
#else
PING_RESULT(0);
#endif
}
static void
ping_thread(void *arg)
{
uint32_t ping_timeout = PING_RCV_TIMEO;
uint32_t ping_delay = PING_DELAY;
ip_addr_t ping_target;
int ret;
int s;
#ifdef ESP_PING
uint32_t ping_count_cur = 0;
uint32_t ping_count_max = 3;
ip4_addr_t ipaddr;
int lev;
esp_ping_get_target(PING_TARGET_IP_ADDRESS_COUNT, &ping_count_max, sizeof(ping_count_max));
esp_ping_get_target(PING_TARGET_RCV_TIMEO, &ping_timeout, sizeof(ping_timeout));
esp_ping_get_target(PING_TARGET_DELAY_TIME, &ping_delay, sizeof(ping_delay));
esp_ping_get_target(PING_TARGET_IP_ADDRESS, &ipaddr.addr, sizeof(uint32_t));
ip_addr_copy_from_ip4(ping_target, ipaddr);
#else
ip_addr_copy_from_ip4(ping_target, PING_TARGET);
#endif
#if LWIP_SO_SNDRCVTIMEO_NONSTANDARD
int timeout = ping_timeout;
#else
struct timeval timeout;
timeout.tv_sec = ping_timeout/1000;
timeout.tv_usec = (ping_timeout%1000)*1000;
#endif
LWIP_UNUSED_ARG(arg);
if ((s = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP)) < 0) {
goto _exit_new_socket_failed;
}
ret = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
LWIP_ASSERT("setting receive timeout failed", ret == 0);
LWIP_UNUSED_ARG(ret);
#ifdef ESP_PING
int tos = 0;
esp_ping_get_target(PING_TARGET_IP_TOS, &tos, sizeof(int));
if (tos > 0) {
tos <<= 5;
ret = setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
LWIP_ASSERT("setting IP_TOS failed", ret == 0);
LWIP_UNUSED_ARG(ret);
}
#endif
while (1) {
#ifdef ESP_PING
if (ping_count_cur++ >= ping_count_max) {
goto _exit;
}
if (ping_init_flag == false) {
goto _exit;
}
#endif
if (ping_send(s, &ping_target) == ERR_OK) {
LWIP_DEBUGF( PING_DEBUG, ("ping: send seq=%d ", ping_seq_num));
ip_addr_debug_print(PING_DEBUG, &ping_target);
LWIP_DEBUGF( PING_DEBUG, ("\n"));
gettimeofday(&ping_time, NULL);
ping_recv(s);
} else {
LWIP_DEBUGF( PING_DEBUG, ("ping: send "));
ip_addr_debug_print(PING_DEBUG, &ping_target);
LWIP_DEBUGF( PING_DEBUG, (" - error\n"));
}
sys_msleep(ping_delay);
}
#ifdef ESP_PING
_exit:
close(s);
_exit_new_socket_failed:
esp_ping_result(PING_RES_FINISH, 0, 0);
SYS_ARCH_PROTECT(lev);
if (ping_init_flag) { /* Ping closed by this thread */
LWIP_DEBUGF( PING_DEBUG, ("ping: closed by self "));
if (ping_sem) {
sys_sem_free(&ping_sem);
}
ping_sem = NULL;
ping_init_flag = false;
SYS_ARCH_UNPROTECT(lev);
} else { /* Ping closed by task calls ping_deinit */
LWIP_DEBUGF( PING_DEBUG, ("ping: closed by other"));
SYS_ARCH_UNPROTECT(lev);
if (ping_sem) {
sys_sem_signal(&ping_sem);
}
}
vTaskDelete(NULL);
#endif
}
#else /* PING_USE_SOCKETS */
/* Ping using the raw ip */
static u8_t
ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr)
@@ -436,27 +227,72 @@ ping_send_now(void)
#endif /* PING_USE_SOCKETS */
/**
*
* Re-implement ping_init and ping_deinit with the APIs from ping_sock.h for back-ward compatibility sake.
* It's highly recommended that users should use the new APIs from ping_sock.h.
* ToDo: ping.h and esp_ping.h are deprecated now and should be removed in idf v5.x.
*
*/
static void
on_ping_success(esp_ping_handle_t hdl, void *args)
{
uint32_t elapsed_time, recv_len;
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
esp_ping_result(PING_RES_OK, recv_len, elapsed_time);
}
static void
on_ping_timeout(esp_ping_handle_t hdl, void *args)
{
uint32_t elapsed_time, recv_len;
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
esp_ping_result(PING_RES_TIMEOUT, recv_len, elapsed_time);
}
static void
on_ping_end(esp_ping_handle_t hdl, void *args)
{
esp_ping_result(PING_RES_FINISH, 0, 0);
esp_ping_delete_session(hdl);
}
int
ping_init(void)
{
int ret;
int lev;
SYS_ARCH_PROTECT(lev);
if (ping_init_flag) {
SYS_ARCH_UNPROTECT(lev);
/* Currently we only support one ping, call ping_deinit to kill the running ping before start new ping */
return ERR_INPROGRESS;
}
ret = sys_sem_new(&ping_sem, 0);
if (ERR_OK != ret) {
SYS_ARCH_UNPROTECT(lev);
return ERR_MEM;
}
ping_init_flag = true;
SYS_ARCH_UNPROTECT(lev);
#if PING_USE_SOCKETS
sys_thread_new("ping_thread", ping_thread, NULL, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
uint32_t tos = 0;
uint32_t ping_timeout = PING_RCV_TIMEO;
uint32_t ping_delay = PING_DELAY;
uint32_t ping_count_max = 3;
ip_addr_t ping_target;
ip4_addr_t ipaddr;
esp_ping_get_target(PING_TARGET_IP_ADDRESS_COUNT, &ping_count_max, sizeof(ping_count_max));
esp_ping_get_target(PING_TARGET_RCV_TIMEO, &ping_timeout, sizeof(ping_timeout));
esp_ping_get_target(PING_TARGET_DELAY_TIME, &ping_delay, sizeof(ping_delay));
esp_ping_get_target(PING_TARGET_IP_ADDRESS, &ipaddr.addr, sizeof(uint32_t));
esp_ping_get_target(PING_TARGET_IP_TOS, &tos, sizeof(tos));
ip_addr_copy_from_ip4(ping_target, ipaddr);
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
config.count = ping_count_max;
config.timeout_ms = ping_timeout;
config.interval_ms = ping_delay;
config.target_addr = ping_target;
config.tos = tos;
esp_ping_callbacks_t cbs = {
.on_ping_end = on_ping_end,
.on_ping_success = on_ping_success,
.on_ping_timeout = on_ping_timeout,
};
esp_ping_new_session(&config, &cbs, &ping);
esp_ping_start(ping);
#else /* PING_USE_SOCKETS */
ping_raw_init();
#endif /* PING_USE_SOCKETS */
@@ -466,24 +302,8 @@ ping_init(void)
void
ping_deinit(void)
{
int lev;
SYS_ARCH_PROTECT(lev);
if (ping_init_flag == false) {
SYS_ARCH_UNPROTECT(lev);
return;
}
ping_init_flag = false;
SYS_ARCH_UNPROTECT(lev);
if (ping_sem) {
sys_sem_wait(&ping_sem);
SYS_ARCH_PROTECT(lev);
sys_sem_free(&ping_sem);
ping_sem = NULL;
SYS_ARCH_UNPROTECT(lev);
}
esp_ping_stop(ping);
esp_ping_delete_session(ping);
}
#endif /* LWIP_IPV4 && LWIP_RAW */

View File

@@ -0,0 +1,371 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// 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, software
// 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 <stdlib.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lwip/opt.h"
#include "lwip/init.h"
#include "lwip/mem.h"
#include "lwip/icmp.h"
#include "lwip/netif.h"
#include "lwip/sys.h"
#include "lwip/timeouts.h"
#include "lwip/inet.h"
#include "lwip/inet_chksum.h"
#include "lwip/ip.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "esp_log.h"
#include "ping/ping_sock.h"
const static char *TAG = "ping_sock";
#define PING_CHECK(a, str, goto_tag, ret_value, ...) \
do \
{ \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
ret = ret_value; \
goto goto_tag; \
} \
} while (0)
#define PING_TIME_DIFF_MS(_end, _start) ((uint32_t)(((_end).tv_sec - (_start).tv_sec) * 1000 + \
((_end).tv_usec - (_start).tv_usec) / 1000))
#define PING_CHECK_START_TIMEOUT_MS (1000)
#define PING_FLAGS_INIT (1 << 0)
#define PING_FLAGS_START (1 << 1)
typedef struct {
int sock;
struct sockaddr_storage target_addr;
TaskHandle_t ping_task_hdl;
struct icmp_echo_hdr *packet_hdr;
ip_addr_t recv_addr;
uint32_t recv_len;
uint32_t icmp_pkt_size;
uint32_t count;
uint32_t transmitted;
uint32_t received;
uint32_t interval_ms;
uint32_t elapsed_time_ms;
uint32_t total_time_ms;
uint8_t ttl;
uint32_t flags;
void (*on_ping_success)(esp_ping_handle_t hdl, void *args);
void (*on_ping_timeout)(esp_ping_handle_t hdl, void *args);
void (*on_ping_end)(esp_ping_handle_t hdl, void *args);
void *cb_args;
} esp_ping_t;
static esp_err_t esp_ping_send(esp_ping_t *ep)
{
esp_err_t ret = ESP_OK;
ep->packet_hdr->seqno++;
/* generate checksum since "seqno" has changed */
ep->packet_hdr->chksum = 0;
ep->packet_hdr->chksum = inet_chksum(ep->packet_hdr, ep->icmp_pkt_size);
int sent = sendto(ep->sock, ep->packet_hdr, ep->icmp_pkt_size, 0,
(struct sockaddr *)&ep->target_addr, sizeof(ep->target_addr));
if (sent != ep->icmp_pkt_size) {
int opt_val;
socklen_t opt_len = sizeof(opt_val);
getsockopt(ep->sock, SOL_SOCKET, SO_ERROR, &opt_val, &opt_len);
ESP_LOGE(TAG, "send error=%d", opt_val);
ret = ESP_FAIL;
} else {
ep->transmitted++;
}
return ret;
}
static int esp_ping_receive(esp_ping_t *ep)
{
char buf[64]; // 64 bytes are enough to cover IP header and ICMP header
int len = 0;
struct sockaddr_storage from;
int fromlen = sizeof(from);
while ((len = recvfrom(ep->sock, buf, sizeof(buf), 0, (struct sockaddr *)&from, (socklen_t *)&fromlen)) > 0) {
if (len >= (int)(sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr))) {
ep->recv_len = (uint32_t)len;
if (from.ss_family == AF_INET) {
// IPv4
struct sockaddr_in *from4 = (struct sockaddr_in *)&from;
inet_addr_to_ip4addr(ip_2_ip4(&ep->recv_addr), &from4->sin_addr);
IP_SET_TYPE_VAL(ep->recv_addr, IPADDR_TYPE_V4);
} else {
// IPv6
struct sockaddr_in6 *from6 = (struct sockaddr_in6 *)&from;
inet6_addr_to_ip6addr(ip_2_ip6(&ep->recv_addr), &from6->sin6_addr);
IP_SET_TYPE_VAL(ep->recv_addr, IPADDR_TYPE_V6);
}
// Currently we only process IPv4
if (IP_IS_V4_VAL(ep->recv_addr)) {
struct ip_hdr *iphdr = (struct ip_hdr *)buf;
struct icmp_echo_hdr *iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4));
if ((iecho->id == ep->packet_hdr->id) && (iecho->seqno == ep->packet_hdr->seqno)) {
ep->received++;
ep->ttl = iphdr->_ttl;
return len;
}
}
}
fromlen = sizeof(from);
}
// if timeout, len will be -1
return len;
}
static void esp_ping_thread(void *args)
{
esp_ping_t *ep = (esp_ping_t *)(args);
TickType_t last_wake;
struct timeval start_time, end_time;
int recv_ret;
while (1) {
/* wait for ping start signal */
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(PING_CHECK_START_TIMEOUT_MS))) {
/* initialize runtime statistics */
ep->packet_hdr->seqno = 0;
ep->transmitted = 0;
ep->received = 0;
ep->total_time_ms = 0;
last_wake = xTaskGetTickCount();
while ((ep->flags & PING_FLAGS_START) && ((ep->count == 0) || (ep->packet_hdr->seqno < ep->count))) {
esp_ping_send(ep);
gettimeofday(&start_time, NULL);
recv_ret = esp_ping_receive(ep);
gettimeofday(&end_time, NULL);
ep->elapsed_time_ms = PING_TIME_DIFF_MS(end_time, start_time);
ep->total_time_ms += ep->elapsed_time_ms;
if (recv_ret >= 0) {
if (ep->on_ping_success) {
ep->on_ping_success((esp_ping_handle_t)ep, ep->cb_args);
}
} else {
if (ep->on_ping_timeout) {
ep->on_ping_timeout((esp_ping_handle_t)ep, ep->cb_args);
}
}
vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(ep->interval_ms)); // to get a more accurate delay
}
/* batch of ping operations finished */
if (ep->on_ping_end) {
ep->on_ping_end((esp_ping_handle_t)ep, ep->cb_args);
}
} else {
// check if ping has been de-initialized
if (!(ep->flags & PING_FLAGS_INIT)) {
break;
}
}
}
/* before exit task, free all resources */
if (ep->packet_hdr) {
free(ep->packet_hdr);
}
if (ep->sock > 0) {
close(ep->sock);
}
free(ep);
vTaskDelete(NULL);
}
esp_err_t esp_ping_new_session(const esp_ping_config_t *config, const esp_ping_callbacks_t *cbs, esp_ping_handle_t *hdl_out)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = NULL;
PING_CHECK(config, "ping config can't be null", err, ESP_ERR_INVALID_ARG);
PING_CHECK(hdl_out, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
ep = mem_calloc(1, sizeof(esp_ping_t));
PING_CHECK(ep, "no memory for esp_ping object", err, ESP_ERR_NO_MEM);
/* set INIT flag, so that ping task won't exit (must set before create ping task) */
ep->flags |= PING_FLAGS_INIT;
/* create ping thread */
BaseType_t xReturned = xTaskCreate(esp_ping_thread, "ping", config->task_stack_size, ep,
config->task_prio, &ep->ping_task_hdl);
PING_CHECK(xReturned == pdTRUE, "create ping task failed", err, ESP_ERR_NO_MEM);
/* callback functions */
if (cbs) {
ep->cb_args = cbs->cb_args;
ep->on_ping_end = cbs->on_ping_end;
ep->on_ping_timeout = cbs->on_ping_timeout;
ep->on_ping_success = cbs->on_ping_success;
}
/* set parameters for ping */
ep->recv_addr = config->target_addr;
ep->count = config->count;
ep->interval_ms = config->interval_ms;
ep->icmp_pkt_size = sizeof(struct icmp_echo_hdr) + config->data_size;
ep->packet_hdr = mem_calloc(1, ep->icmp_pkt_size);
PING_CHECK(ep->packet_hdr, "no memory for echo packet", err, ESP_ERR_NO_MEM);
/* set ICMP type and code field */
ep->packet_hdr->type = ICMP_ECHO;
ep->packet_hdr->code = 0;
/* ping id should be unique, treat task handle as ping ID */
ep->packet_hdr->id = ((uint32_t)ep->ping_task_hdl) & 0xFFFF;
/* fill the additional data buffer with some data */
char *d = (char *)(ep->packet_hdr) + sizeof(struct icmp_echo_hdr);
for (uint32_t i = 0; i < config->data_size; i++) {
d[i] = 'A' + i;
}
/* create socket */
if (IP_IS_V4(&config->target_addr) || ip6_addr_isipv4mappedipv6(ip_2_ip6(&config->target_addr))) {
ep->sock = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP);
} else {
ep->sock = socket(AF_INET6, SOCK_RAW, IP6_NEXTH_ICMP6);
}
PING_CHECK(ep->sock > 0, "create socket failed: %d", err, ESP_FAIL, ep->sock);
struct timeval timeout;
timeout.tv_sec = config->timeout_ms / 1000;
timeout.tv_usec = (config->timeout_ms % 1000) * 1000;
/* set receive timeout */
setsockopt(ep->sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
/* set tos */
setsockopt(ep->sock, IPPROTO_IP, IP_TOS, &config->tos, sizeof(config->tos));
/* set socket address */
if (IP_IS_V4(&config->target_addr)) {
struct sockaddr_in *to4 = (struct sockaddr_in *)&ep->target_addr;
to4->sin_family = AF_INET;
inet_addr_from_ip4addr(&to4->sin_addr, ip_2_ip4(&config->target_addr));
}
if (IP_IS_V6(&config->target_addr)) {
struct sockaddr_in6 *to6 = (struct sockaddr_in6 *)&ep->target_addr;
to6->sin6_family = AF_INET6;
inet6_addr_from_ip6addr(&to6->sin6_addr, ip_2_ip6(&config->target_addr));
}
/* return ping handle to user */
*hdl_out = (esp_ping_handle_t)ep;
return ESP_OK;
err:
if (ep) {
if (ep->sock > 0) {
close(ep->sock);
}
if (ep->packet_hdr) {
free(ep->packet_hdr);
}
if (ep->ping_task_hdl) {
vTaskDelete(ep->ping_task_hdl);
}
free(ep);
}
return ret;
}
esp_err_t esp_ping_delete_session(esp_ping_handle_t hdl)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = (esp_ping_t *)hdl;
PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
/* reset init flags, then ping task will exit */
ep->flags &= ~PING_FLAGS_INIT;
return ESP_OK;
err:
return ret;
}
esp_err_t esp_ping_start(esp_ping_handle_t hdl)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = (esp_ping_t *)hdl;
PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
ep->flags |= PING_FLAGS_START;
xTaskNotifyGive(ep->ping_task_hdl);
return ESP_OK;
err:
return ret;
}
esp_err_t esp_ping_stop(esp_ping_handle_t hdl)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = (esp_ping_t *)hdl;
PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
ep->flags &= ~PING_FLAGS_START;
return ESP_OK;
err:
return ret;
}
esp_err_t esp_ping_get_profile(esp_ping_handle_t hdl, esp_ping_profile_t profile, void *data, uint32_t size)
{
esp_err_t ret = ESP_OK;
esp_ping_t *ep = (esp_ping_t *)hdl;
const void *from = NULL;
uint32_t copy_size = 0;
PING_CHECK(ep, "ping handle can't be null", err, ESP_ERR_INVALID_ARG);
PING_CHECK(data, "profile data can't be null", err, ESP_ERR_INVALID_ARG);
switch (profile) {
case ESP_PING_PROF_SEQNO:
from = &ep->packet_hdr->seqno;
copy_size = sizeof(ep->packet_hdr->seqno);
break;
case ESP_PING_PROF_TTL:
from = &ep->ttl;
copy_size = sizeof(ep->ttl);
break;
case ESP_PING_PROF_REQUEST:
from = &ep->transmitted;
copy_size = sizeof(ep->transmitted);
break;
case ESP_PING_PROF_REPLY:
from = &ep->received;
copy_size = sizeof(ep->received);
break;
case ESP_PING_PROF_IPADDR:
from = &ep->recv_addr;
copy_size = sizeof(ep->recv_addr);
break;
case ESP_PING_PROF_SIZE:
from = &ep->recv_len;
copy_size = sizeof(ep->recv_len);
break;
case ESP_PING_PROF_TIMEGAP:
from = &ep->elapsed_time_ms;
copy_size = sizeof(ep->elapsed_time_ms);
break;
case ESP_PING_PROF_DURATION:
from = &ep->total_time_ms;
copy_size = sizeof(ep->total_time_ms);
break;
default:
PING_CHECK(false, "unknow profile: %d", err, ESP_ERR_INVALID_ARG, profile);
break;
}
PING_CHECK(size >= copy_size, "unmatched data size for profile %d", err, ESP_ERR_INVALID_SIZE, profile);
memcpy(data, from, copy_size);
return ESP_OK;
err:
return ret;
}

View File

@@ -59,7 +59,7 @@ typedef enum {
typedef enum {
PING_RES_TIMEOUT = 0,
PING_RES_OK = 1,
PING_RES_OK = 1,
PING_RES_FINISH = 2,
} ping_res_t;

View File

@@ -43,10 +43,10 @@ extern "C" {
#endif
int ping_init(void);
int ping_init(void) __attribute__ ((deprecated));
#ifdef ESP_PING
void ping_deinit(void);
void ping_deinit(void) __attribute__ ((deprecated));
#endif
#if !PING_USE_SOCKETS

View File

@@ -0,0 +1,169 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// 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, software
// 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.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "esp_err.h"
#include "lwip/ip_addr.h"
/**
* @brief Type of "ping" session handle
*
*/
typedef void *esp_ping_handle_t;
/**
* @brief Type of "ping" callback functions
*
*/
typedef struct {
/**
* @brief arguments for callback functions
*
*/
void *cb_args;
/**
* @brief Invoked by internal ping thread when received ICMP echo reply packet
*
*/
void (*on_ping_success)(esp_ping_handle_t hdl, void *args);
/**
* @brief Invoked by internal ping thread when receive ICMP echo reply packet timeout
*
*/
void (*on_ping_timeout)(esp_ping_handle_t hdl, void *args);
/**
* @brief Invoked by internal ping thread when a ping session is finished
*
*/
void (*on_ping_end)(esp_ping_handle_t hdl, void *args);
} esp_ping_callbacks_t;
/**
* @brief Type of "ping" configuration
*
*/
typedef struct {
uint32_t count; /*!< A "ping" session contains count procedures */
uint32_t interval_ms; /*!< Milliseconds between each ping procedure */
uint32_t timeout_ms; /*!< Timeout value (in milliseconds) of each ping procedure */
uint32_t data_size; /*!< Size of the data next to ICMP packet header */
uint8_t tos; /*!< Type of Service, a field specified in the IP header */
ip_addr_t target_addr; /*!< Target IP address, either IPv4 or IPv6 */
uint32_t task_stack_size; /*!< Stack size of internal ping task */
uint32_t task_prio; /*!< Priority of internal ping task */
} esp_ping_config_t;
/**
* @brief Default ping configuration
*
*/
#define ESP_PING_DEFAULT_CONFIG() \
{ \
.count = 5, \
.interval_ms = 1000, \
.timeout_ms = 1000, \
.data_size = 56, \
.tos = 0, \
.target_addr = ip_addr_any_type, \
.task_stack_size = 2048, \
.task_prio = 2, \
}
#define ESP_PING_COUNT_INFINITE (0) /*!< Set ping count to zero will ping target infinitely */
/**
* @brief Profile of ping session
*
*/
typedef enum {
ESP_PING_PROF_SEQNO, /*!< Sequence number of a ping procedure */
ESP_PING_PROF_TTL, /*!< Time to live of a ping procedure */
ESP_PING_PROF_REQUEST, /*!< Number of request packets sent out */
ESP_PING_PROF_REPLY, /*!< Number of reply packets received */
ESP_PING_PROF_IPADDR, /*!< IP address of replied target */
ESP_PING_PROF_SIZE, /*!< Size of received packet */
ESP_PING_PROF_TIMEGAP, /*!< Elapsed time between request and reply packet */
ESP_PING_PROF_DURATION /*!< Elapsed time of the whole ping session */
} esp_ping_profile_t;
/**
* @brief Create a ping session
*
* @param config ping configuration
* @param cbs a bunch of callback functions invoked by internal ping task
* @param hdl_out handle of ping session
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. configuration is null, etc)
* - ESP_ERR_NO_MEM: out of memory
* - ESP_FAIL: other internal error (e.g. socket error)
* - ESP_OK: create ping session successfully, user can take the ping handle to do follow-on jobs
*/
esp_err_t esp_ping_new_session(const esp_ping_config_t *config, const esp_ping_callbacks_t *cbs, esp_ping_handle_t *hdl_out);
/**
* @brief Delete a ping session
*
* @param hdl handle of ping session
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc)
* - ESP_OK: delete ping session successfully
*/
esp_err_t esp_ping_delete_session(esp_ping_handle_t hdl);
/**
* @brief Start the ping session
*
* @param hdl handle of ping session
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc)
* - ESP_OK: start ping session successfully
*/
esp_err_t esp_ping_start(esp_ping_handle_t hdl);
/**
* @brief Stop the ping session
*
* @param hdl handle of ping session
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc)
* - ESP_OK: stop ping session successfully
*/
esp_err_t esp_ping_stop(esp_ping_handle_t hdl);
/**
* @brief Get runtime profile of ping session
*
* @param hdl handle of ping session
* @param profile type of profile
* @param data profile data
* @param size profile data size
* @return
* - ESP_ERR_INVALID_ARG: invalid parameters (e.g. ping handle is null, etc)
* - ESP_ERR_INVALID_SIZE: the actual profile data size doesn't match the "size" parameter
* - ESP_OK: get profile successfully
*/
esp_err_t esp_ping_get_profile(esp_ping_handle_t hdl, esp_ping_profile_t profile, void *data, uint32_t size);
#ifdef __cplusplus
}
#endif

View File

@@ -117,6 +117,8 @@ INPUT = \
../../components/esp-tls/esp_tls.h \
## MQTT
../../components/mqtt/esp-mqtt/include/mqtt_client.h \
## ICMP-ECHO
../../components/lwip/include/apps/ping/ping_sock.h \
## mDNS
../../components/mdns/include/mdns.h \
../../components/esp_http_client/include/esp_http_client.h \

View File

@@ -0,0 +1,117 @@
ICMP Echo
=========
Overview
--------
ICMP (Internet Control Message Protocol) is used for diagnostic or control purposes or generated in response to errors in IP operations. The common network util ``ping`` is implemented based on the ICMP packets with the type field value of 0, also called ``Echo Reply``.
During a ping session, the source host firstly sends out an ICMP echo request packet and wait for an ICMP echo reply with specific times. In this way, it also measures the round-trip time for the messages. After receiving a valid ICMP echo reply, the source host will generate statistics about the IP link layer (e.g. packet loss, elapsed time, etc).
It is common that IoT device needs to check whether a remote server is alive or not. The device should show the warnings to users when it got offline. It can be achieved by creating a ping session and sending/parsing ICMP echo packets periodically.
To make this internal procedure much easier for users, ESP-IDF provides some out-of-box APIs.
Create a new ping session
^^^^^^^^^^^^^^^^^^^^^^^^^
To create a ping session, you need to fill in the ``esp_ping_config_t`` configuration structure firstly, specifying target IP address, interval times, and etc. Optionally, you can also register some callback functions with the `esp_ping_callbacks_t`` structure.
Example method to create a new ping session and register callbacks:
.. highlight:: c
::
static void test_on_ping_success(esp_ping_handle_t hdl, void *args)
{
// optionally, get callback arguments
// const char* str = (const char*) args;
// printf("%s\r\n", str); // "foo"
uint8_t ttl;
uint16_t seqno;
uint32_t elapsed_time, recv_len;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
printf("%d bytes from %s icmp_seq=%d ttl=%d time=%d ms\n",
recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time);
}
static void test_on_ping_timeout(esp_ping_handle_t hdl, void *args)
{
uint16_t seqno;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
printf("From %s icmp_seq=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno);
}
static void test_on_ping_end(esp_ping_handle_t hdl, void *args)
{
uint32_t transmitted;
uint32_t received;
uint32_t total_time_ms;
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
printf("%d packets transmitted, %d received, time %dms\n", transmitted, received, total_time_ms);
}
void initialize_ping()
{
/* convert URL to IP address */
getaddrinfo("www.espressif.com", NULL, &hint, &res) == 0);
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
freeaddrinfo(res);
esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();
ping_config.target_addr = target_addr; // target IP address
ping_config.count = ESP_PING_COUNT_INFINITE; // ping in infinite mode, esp_ping_stop can stop it
/* set callback functions */
esp_ping_callbacks_t cbs;
cbs.on_ping_success = test_on_ping_success;
cbs.on_ping_timeout = test_on_ping_timeout;
cbs.on_ping_end = test_on_ping_end;
cbs.cb_args = "foo"; // arguments that will feed to all callback functions, can be NULL
cbs.cb_args = eth_event_group;
esp_ping_handle_t ping;
esp_ping_new_session(&ping_config, &cbs, &ping);
}
Start and Stop ping session
^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can start and stop ping session with the handle returned by ``esp_ping_new_session``. Note that, the ping session won't start automatically after creation. If the ping session is stopped, and restart again, the sequence number in ICMP packets will recount from zero again.
Delete a ping session
^^^^^^^^^^^^^^^^^^^^^
If a ping session won't be used any more, you can delete it with ``esp_ping_delete_session``. Please make sure the ping session is in stop state (i.e. you have called ``esp_ping_stop`` before or the ping session has finished all the procedures) when you call this function.
Get runtime statistics
^^^^^^^^^^^^^^^^^^^^^^
As the example code above, you can call ``esp_ping_get_profile`` to get different runtime statistics of ping session in the callback function.
Application Example
-------------------
ICMP echo example: :example:`protocols/icmp_echo`
API Reference
-------------
.. include:: /_build/inc/ping_sock.inc

View File

@@ -11,6 +11,7 @@ Application Protocols
HTTP Client <esp_http_client>
HTTP Server <esp_http_server>
HTTPS Server <esp_https_server>
ICMP Echo <icmp_echo>
Local Control <esp_local_ctrl>
mDNS <mdns>
Modbus <modbus>

View File

@@ -0,0 +1 @@
.. include:: ../../../en/api-reference/protocols/icmp_echo.rst

View File

@@ -11,6 +11,7 @@
Websocket Client <esp_websocket_client>
HTTP 服务器 <esp_http_server>
HTTPS Server <esp_https_server>
ICMP Echo <icmp_echo>
ASIO <asio>
ESP-MQTT <mqtt>
Modbus slave <modbus>

View File

@@ -0,0 +1,10 @@
# 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)
# (Not part of the boilerplate)
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(icmp-echo)

View File

@@ -0,0 +1,11 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := icmp-echo
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,100 @@
# ICMP Echo-Reply (Ping) example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
Ping is a useful network utility used to test if a remote host is reachable on the IP network. It measures the round-trip time for messages sent from the source host to a destination target that are echoed back to the source.
Ping operates by sending Internet Control Message Protocol (ICMP) echo request packets to the target host and waiting for an ICMP echo reply.
This example implements a simple ping command line util based on the [console component](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/console.html).
**Notes:** Currently this example only supports IPv4.
## How to use example
### Hardware Required
This example should be able to run on any commonly available ESP32 development board.
### Configure the project
```
idf.py menuconfig
```
In the `Example Connection Configuration` menu:
* Choose the network interface (Wi-Fi or Ethernet) used by this example under `Connect using`.
* If Wi-Fi interface is selected, you also have to set:
* Wi-Fi SSID and Wi-Fi password that your board will connect to.
* If Ethernet interface is selected, you also have to set:
* Select Ethernet type under `Ethernet Type`, for example, `Internal EMAC` or `SPI Ethernet Module`.
* Select Ethernet PHY chip model under `Ethernet PHY Device`, for example, `IP101`.
* You might also have to set other Ethernet driver specific parameters under `Component Config > Ethernet`, for example, EMAC Clock mode, GPIO used by SMI, and etc.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
* Run `help` to get manual of all supported commands.
```bash
esp32> help
help
Print the list of registered commands
ping [-W <t>] [-i <t>] [-s <n>] [-c <n>] [-Q <n>] <host>
send ICMP ECHO_REQUEST to network hosts
-W, --timeout=<t> Time to wait for a response, in seconds
-i, --interval=<t> Wait interval seconds between sending each packet
-s, --size=<n> Specify the number of data bytes to be sent
-c, --count=<n> Stop after sending count packets
-Q, --tos=<n> Set Type of Service related bits in IP datagrams
<host> Host address
```
* Run `ping` command to test reachable of remote server.
```bash
esp32> ping www.espressif.com
64 bytes from 119.9.92.99 icmp_seq=1 ttl=51 time=36 ms
64 bytes from 119.9.92.99 icmp_seq=2 ttl=51 time=34 ms
64 bytes from 119.9.92.99 icmp_seq=3 ttl=51 time=37 ms
64 bytes from 119.9.92.99 icmp_seq=4 ttl=51 time=36 ms
64 bytes from 119.9.92.99 icmp_seq=5 ttl=51 time=33 ms
--- 119.9.92.99 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 176ms
```
* Run `ping` with a wrong domain name
```bash
esp32> ping www.hello-world.io
ping: unknown host www.hello-world.io
Command returned non-zero error code: 0x1 (ERROR)
```
* Run `ping` with an unreachable server
```bash
esp32> ping www.zoom.us
From 69.171.230.18 icmp_seq=1 timeout
From 69.171.230.18 icmp_seq=2 timeout
From 69.171.230.18 icmp_seq=3 timeout
From 69.171.230.18 icmp_seq=4 timeout
From 69.171.230.18 icmp_seq=5 timeout
--- 69.171.230.18 ping statistics ---
5 packets transmitted, 0 received, 100% packet loss, time 4996ms
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "echo_example_main.c" "cmd_ping.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,161 @@
/* Ping command
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 <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "cmd_ping.h"
#include "argtable3/argtable3.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "esp_console.h"
#include "ping/ping_sock.h"
static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args)
{
uint8_t ttl;
uint16_t seqno;
uint32_t elapsed_time, recv_len;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
printf("%d bytes from %s icmp_seq=%d ttl=%d time=%d ms\n",
recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time);
}
static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args)
{
uint16_t seqno;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
printf("From %s icmp_seq=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno);
}
static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args)
{
ip_addr_t target_addr;
uint32_t transmitted;
uint32_t received;
uint32_t total_time_ms;
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
uint32_t loss = (uint32_t)((1 - ((float)received) / transmitted) * 100);
if (IP_IS_V4(&target_addr)) {
printf("\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr)));
} else {
printf("\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr)));
}
printf("%d packets transmitted, %d received, %d%% packet loss, time %dms\n",
transmitted, received, loss, total_time_ms);
// delete the ping sessions, so that we clean up all resources and can create a new ping session
// we don't have to call delete function in the callback, instead we can call delete function from other tasks
esp_ping_delete_session(hdl);
}
static struct {
struct arg_dbl *timeout;
struct arg_dbl *interval;
struct arg_int *data_size;
struct arg_int *count;
struct arg_int *tos;
struct arg_str *host;
struct arg_end *end;
} ping_args;
static int do_ping_cmd(int argc, char **argv)
{
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
int nerrors = arg_parse(argc, argv, (void **)&ping_args);
if (nerrors != 0) {
arg_print_errors(stderr, ping_args.end, argv[0]);
return 1;
}
if (ping_args.timeout->count > 0) {
config.timeout_ms = (uint32_t)(ping_args.timeout->dval[0] * 1000);
}
if (ping_args.interval->count > 0) {
config.interval_ms = (uint32_t)(ping_args.interval->dval[0] * 1000);
}
if (ping_args.data_size->count > 0) {
config.data_size = (uint32_t)(ping_args.data_size->ival[0]);
}
if (ping_args.count->count > 0) {
config.count = (uint32_t)(ping_args.count->ival[0]);
}
if (ping_args.tos->count > 0) {
config.tos = (uint32_t)(ping_args.tos->ival[0]);
}
// parse IP address
ip_addr_t target_addr;
struct addrinfo hint;
struct addrinfo *res = NULL;
memset(&hint, 0, sizeof(hint));
memset(&target_addr, 0, sizeof(target_addr));
/* convert domain name to IP address */
if (getaddrinfo(ping_args.host->sval[0], NULL, &hint, &res) != 0) {
printf("ping: unknown host %s\n", ping_args.host->sval[0]);
return 1;
}
if (res->ai_family == AF_INET) {
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
} else {
struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr;
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
}
freeaddrinfo(res);
config.target_addr = target_addr;
/* set callback functions */
esp_ping_callbacks_t cbs = {
.on_ping_success = cmd_ping_on_ping_success,
.on_ping_timeout = cmd_ping_on_ping_timeout,
.on_ping_end = cmd_ping_on_ping_end,
.cb_args = NULL
};
esp_ping_handle_t ping;
esp_ping_new_session(&config, &cbs, &ping);
esp_ping_start(ping);
return 0;
}
void register_ping(void)
{
ping_args.timeout = arg_dbl0("W", "timeout", "<t>", "Time to wait for a response, in seconds");
ping_args.interval = arg_dbl0("i", "interval", "<t>", "Wait interval seconds between sending each packet");
ping_args.data_size = arg_int0("s", "size", "<n>", "Specify the number of data bytes to be sent");
ping_args.count = arg_int0("c", "count", "<n>", "Stop after sending count packets");
ping_args.tos = arg_int0("Q", "tos", "<n>", "Set Type of Service related bits in IP datagrams");
ping_args.host = arg_str1(NULL, NULL, "<host>", "Host address");
ping_args.end = arg_end(1);
const esp_console_cmd_t ping_cmd = {
.command = "ping",
.help = "send ICMP ECHO_REQUEST to network hosts",
.hint = NULL,
.func = &do_ping_cmd,
.argtable = &ping_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&ping_cmd));
}

View File

@@ -0,0 +1,24 @@
/* Ping command
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.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Register Ping command
*
*/
void register_ping(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,145 @@
/* ICMP echo 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 <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_console.h"
#include "driver/uart.h"
#include "esp_vfs_dev.h"
#include "esp_event.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "cmd_ping.h"
static void initialize_console(void)
{
/* Disable buffering on stdin */
setvbuf(stdin, NULL, _IONBF, 0);
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
/* Move the caret to the beginning of the next line on '\n' */
esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
/* Configure UART. Note that REF_TICK is used so that the baud rate remains
* correct while APB frequency is changing in light sleep mode.
*/
const uart_config_t uart_config = {
.baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.use_ref_tick = true
};
ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config));
/* Install UART driver for interrupt-driven reads and writes */
ESP_ERROR_CHECK(uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0));
/* Tell VFS to use UART driver */
esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
/* Initialize the console */
esp_console_config_t console_config = {
.max_cmdline_args = 16,
.max_cmdline_length = 256,
#if CONFIG_LOG_COLORS
.hint_color = atoi(LOG_COLOR_CYAN)
#endif
};
ESP_ERROR_CHECK(esp_console_init(&console_config));
/* Configure linenoise line completion library */
/* Enable multiline editing. If not set, long commands will scroll within
* single line.
*/
linenoiseSetMultiLine(1);
/* Tell linenoise where to get command completions and hints */
linenoiseSetCompletionCallback(&esp_console_get_completion);
linenoiseSetHintsCallback((linenoiseHintsCallback *) &esp_console_get_hint);
/* Set command history size */
linenoiseHistorySetMaxLen(100);
}
void app_main(void)
{
initialize_console();
ESP_ERROR_CHECK(nvs_flash_init());
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* wait for active network connection */
ESP_ERROR_CHECK(example_connect());
/* Register commands */
esp_console_register_help_command();
register_ping();
/* Prompt to be printed before each line.
* This can be customized, made dynamic, etc.
*/
const char *prompt = LOG_COLOR_I "esp32> " LOG_RESET_COLOR;
printf("\n"
"Type 'help' to get the list of commands.\n"
"Use UP/DOWN arrows to navigate through command history.\n"
"Press TAB when typing command name to auto-complete.\n");
/* Figure out if the terminal supports escape sequences */
int probe_status = linenoiseProbe();
if (probe_status) { /* zero indicates success */
printf("\n"
"Your terminal application does not support escape sequences.\n"
"Line editing and history features are disabled.\n"
"On Windows, try using Putty instead.\n");
linenoiseSetDumbMode(1);
#if CONFIG_LOG_COLORS
/* Since the terminal doesn't support escape sequences,
* don't use color codes in the prompt.
*/
prompt = "esp32> ";
#endif //CONFIG_LOG_COLORS
}
/* Main loop */
while (true) {
/* Get a line using linenoise.
* The line is returned when ENTER is pressed.
*/
char *line = linenoise(prompt);
if (line == NULL) { /* Ignore empty lines */
continue;
}
/* Add the command to the history */
linenoiseHistoryAdd(line);
/* Try to run the command */
int ret;
esp_err_t err = esp_console_run(line, &ret);
if (err == ESP_ERR_NOT_FOUND) {
printf("Unrecognized command\n");
} else if (err == ESP_ERR_INVALID_ARG) {
// command was empty
} else if (err == ESP_OK && ret != ESP_OK) {
printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret));
} else if (err != ESP_OK) {
printf("Internal error: %s\n", esp_err_to_name(err));
}
/* linenoise allocates line buffer on the heap, so need to free it */
linenoiseFree(line);
}
}

View File

@@ -148,4 +148,4 @@ Several commands implemented in `cmd_wifi.c` and `cmd_system.c` use the Argtable
### Command history
Each time a new command line is obtained from `linenoise`, it is written into history and the history is saved into a file in flash memory. On reset, history is initialized from that file.
Each time a new command line is obtained from `linenoise`, it is written into history and the history is saved into a file in flash memory. On reset, history is initialized from that file.

View File

@@ -1,3 +1,3 @@
idf_component_register(SRCS "cmd_wifi.c"
"console_example_main.c"
INCLUDE_DIRS ".")
INCLUDE_DIRS ".")