From 1ef13c524c484e9fb62e6c0b11482c30c5383728 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Tue, 29 May 2018 11:25:24 +0200 Subject: [PATCH] asio: initial idf port of asio library without ssl --- .gitmodules | 4 + components/asio/asio | 1 + components/asio/component.mk | 6 + .../asio/port/include/esp_asio_config.h | 45 ++++ components/asio/port/include/esp_exception.h | 39 ++++ components/lwip/api/sockets.c | 79 +++++++ components/lwip/include/lwip/lwip/netdb.h | 15 ++ components/lwip/include/lwip/lwip/sockets.h | 31 ++- components/lwip/include/lwip/posix/netdb.h | 7 + components/newlib/platform_include/errno.h | 39 ++++ components/newlib/platform_include/net/if.h | 45 ++++ components/newlib/platform_include/pthread.h | 33 +++ components/newlib/platform_include/sys/poll.h | 32 +++ components/newlib/platform_include/sys/uio.h | 21 ++ components/newlib/platform_include/sys/un.h | 24 ++ .../newlib/platform_include/sys/unistd.h | 1 + components/newlib/pthread.c | 10 + components/vfs/include/sys/ioctl.h | 7 + docs/en/COPYRIGHT.rst | 4 +- docs/en/api-reference/protocols/asio.rst | 32 +++ docs/en/api-reference/protocols/index.rst | 1 + docs/zh_CN/api-reference/protocols/asio.rst | 1 + examples/protocols/asio/chat_client/Makefile | 7 + examples/protocols/asio/chat_client/README.md | 19 ++ .../asio/chat_client/asio_chat_client_test.py | 98 ++++++++ .../asio/chat_client/components/component.mk | 10 + .../asio/chat_client/components/wifi_asio.cpp | 136 +++++++++++ .../asio/chat_client/main/Kconfig.projbuild | 27 +++ .../asio/chat_client/main/chat_client.cpp | 165 ++++++++++++++ .../asio/chat_client/main/chat_message.hpp | 91 ++++++++ .../asio/chat_client/main/component.mk | 8 + .../asio/chat_client/sdkconfig.defaults | 1 + examples/protocols/asio/chat_server/Makefile | 7 + examples/protocols/asio/chat_server/README.md | 22 ++ .../asio/chat_server/asio_chat_server_test.py | 56 +++++ .../asio/chat_server/components/component.mk | 10 + .../asio/chat_server/components/wifi_asio.cpp | 136 +++++++++++ .../asio/chat_server/main/Kconfig.projbuild | 21 ++ .../asio/chat_server/main/chat_message.hpp | 91 ++++++++ .../asio/chat_server/main/chat_server.cpp | 214 ++++++++++++++++++ .../asio/chat_server/main/component.mk | 8 + .../asio/chat_server/sdkconfig.defaults | 1 + .../protocols/asio/tcp_echo_server/Makefile | 7 + .../protocols/asio/tcp_echo_server/README.md | 18 ++ .../tcp_echo_server/asio_tcp_server_test.py | 59 +++++ .../tcp_echo_server/components/component.mk | 10 + .../tcp_echo_server/components/wifi_asio.cpp | 136 +++++++++++ .../tcp_echo_server/main/Kconfig.projbuild | 21 ++ .../asio/tcp_echo_server/main/component.mk | 9 + .../asio/tcp_echo_server/main/echo_server.cpp | 91 ++++++++ .../asio/tcp_echo_server/sdkconfig.defaults | 1 + .../protocols/asio/udp_echo_server/Makefile | 7 + .../protocols/asio/udp_echo_server/README.md | 18 ++ .../udp_echo_server/asio_udp_server_test.py | 58 +++++ .../udp_echo_server/components/component.mk | 10 + .../udp_echo_server/components/wifi_asio.cpp | 136 +++++++++++ .../udp_echo_server/main/Kconfig.projbuild | 21 ++ .../asio/udp_echo_server/main/component.mk | 8 + .../udp_echo_server/main/udp_echo_server.cpp | 69 ++++++ .../asio/udp_echo_server/sdkconfig.defaults | 1 + .../protocols/asio/wifi_init/wifi_asio.cpp | 136 +++++++++++ tools/ci/mirror-list.txt | 1 + 62 files changed, 2414 insertions(+), 8 deletions(-) create mode 160000 components/asio/asio create mode 100644 components/asio/component.mk create mode 100644 components/asio/port/include/esp_asio_config.h create mode 100644 components/asio/port/include/esp_exception.h create mode 100644 components/newlib/platform_include/errno.h create mode 100644 components/newlib/platform_include/net/if.h create mode 100644 components/newlib/platform_include/pthread.h create mode 100644 components/newlib/platform_include/sys/poll.h create mode 100644 components/newlib/platform_include/sys/uio.h create mode 100644 components/newlib/platform_include/sys/un.h create mode 100644 components/newlib/pthread.c create mode 100644 docs/en/api-reference/protocols/asio.rst create mode 100644 docs/zh_CN/api-reference/protocols/asio.rst create mode 100644 examples/protocols/asio/chat_client/Makefile create mode 100644 examples/protocols/asio/chat_client/README.md create mode 100644 examples/protocols/asio/chat_client/asio_chat_client_test.py create mode 100644 examples/protocols/asio/chat_client/components/component.mk create mode 100644 examples/protocols/asio/chat_client/components/wifi_asio.cpp create mode 100644 examples/protocols/asio/chat_client/main/Kconfig.projbuild create mode 100644 examples/protocols/asio/chat_client/main/chat_client.cpp create mode 100644 examples/protocols/asio/chat_client/main/chat_message.hpp create mode 100644 examples/protocols/asio/chat_client/main/component.mk create mode 100644 examples/protocols/asio/chat_client/sdkconfig.defaults create mode 100644 examples/protocols/asio/chat_server/Makefile create mode 100644 examples/protocols/asio/chat_server/README.md create mode 100644 examples/protocols/asio/chat_server/asio_chat_server_test.py create mode 100644 examples/protocols/asio/chat_server/components/component.mk create mode 100644 examples/protocols/asio/chat_server/components/wifi_asio.cpp create mode 100644 examples/protocols/asio/chat_server/main/Kconfig.projbuild create mode 100644 examples/protocols/asio/chat_server/main/chat_message.hpp create mode 100644 examples/protocols/asio/chat_server/main/chat_server.cpp create mode 100644 examples/protocols/asio/chat_server/main/component.mk create mode 100644 examples/protocols/asio/chat_server/sdkconfig.defaults create mode 100644 examples/protocols/asio/tcp_echo_server/Makefile create mode 100644 examples/protocols/asio/tcp_echo_server/README.md create mode 100644 examples/protocols/asio/tcp_echo_server/asio_tcp_server_test.py create mode 100644 examples/protocols/asio/tcp_echo_server/components/component.mk create mode 100644 examples/protocols/asio/tcp_echo_server/components/wifi_asio.cpp create mode 100644 examples/protocols/asio/tcp_echo_server/main/Kconfig.projbuild create mode 100644 examples/protocols/asio/tcp_echo_server/main/component.mk create mode 100644 examples/protocols/asio/tcp_echo_server/main/echo_server.cpp create mode 100644 examples/protocols/asio/tcp_echo_server/sdkconfig.defaults create mode 100644 examples/protocols/asio/udp_echo_server/Makefile create mode 100644 examples/protocols/asio/udp_echo_server/README.md create mode 100644 examples/protocols/asio/udp_echo_server/asio_udp_server_test.py create mode 100644 examples/protocols/asio/udp_echo_server/components/component.mk create mode 100644 examples/protocols/asio/udp_echo_server/components/wifi_asio.cpp create mode 100644 examples/protocols/asio/udp_echo_server/main/Kconfig.projbuild create mode 100644 examples/protocols/asio/udp_echo_server/main/component.mk create mode 100644 examples/protocols/asio/udp_echo_server/main/udp_echo_server.cpp create mode 100644 examples/protocols/asio/udp_echo_server/sdkconfig.defaults create mode 100644 examples/protocols/asio/wifi_init/wifi_asio.cpp diff --git a/.gitmodules b/.gitmodules index 849ef99043..5c800c562b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -41,3 +41,7 @@ [submodule "components/mbedtls/mbedtls"] path = components/mbedtls/mbedtls url = https://github.com/espressif/mbedtls.git + +[submodule "components/asio/asio"] + path = components/asio/asio + url = https://github.com/espressif/asio.git diff --git a/components/asio/asio b/components/asio/asio new file mode 160000 index 0000000000..55efc179b7 --- /dev/null +++ b/components/asio/asio @@ -0,0 +1 @@ +Subproject commit 55efc179b76139c8f9b44bf22a4aba4803f7a7bd diff --git a/components/asio/component.mk b/components/asio/component.mk new file mode 100644 index 0000000000..e024df3f31 --- /dev/null +++ b/components/asio/component.mk @@ -0,0 +1,6 @@ +COMPONENT_ADD_INCLUDEDIRS := asio/asio/include port/include +COMPONENT_PRIV_INCLUDEDIRS := private_include +COMPONENT_SRCDIRS := asio/asio/src +COMPONENT_OBJEXCLUDE := asio/asio/src/asio_ssl.o + +COMPONENT_SUBMODULES += asio diff --git a/components/asio/port/include/esp_asio_config.h b/components/asio/port/include/esp_asio_config.h new file mode 100644 index 0000000000..accccad0da --- /dev/null +++ b/components/asio/port/include/esp_asio_config.h @@ -0,0 +1,45 @@ +// Copyright 2018 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. +#ifndef _ESP_ASIO_CONFIG_H_ +#define _ESP_ASIO_CONFIG_H_ + +// +// Enabling exceptions only when they are enabled in menuconfig +// +# include +# ifndef CONFIG_CXX_EXCEPTIONS +# define ASIO_NO_EXCEPTIONS +# endif // CONFIG_CXX_EXCEPTIONS + +// +// LWIP compatifility inet and address macros/functions +// +# define LWIP_COMPAT_SOCKET_INET 1 +# define LWIP_COMPAT_SOCKET_ADDR 1 + +// +// Specific ASIO feature flags +// +# define ASIO_DISABLE_SERIAL_PORT +# define ASIO_SEPARATE_COMPILATION +# define ASIO_STANDALONE +# define ASIO_NO_TYPEID +# define ASIO_DISABLE_SIGNAL +# define ASIO_HAS_PTHREADS +# define ASIO_DISABLE_EPOLL +# define ASIO_DISABLE_EVENTFD +# define ASIO_DISABLE_SIGNAL +# define ASIO_DISABLE_SIGACTION + +#endif // _ESP_ASIO_CONFIG_H_ diff --git a/components/asio/port/include/esp_exception.h b/components/asio/port/include/esp_exception.h new file mode 100644 index 0000000000..3c5c043755 --- /dev/null +++ b/components/asio/port/include/esp_exception.h @@ -0,0 +1,39 @@ + +// Copyright 2018 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. +#ifndef _ESP_EXCEPTION_H_ +#define _ESP_EXCEPTION_H_ + +// +// This exception stub is enabled only if exceptions are disabled in menuconfig +// +#if !defined(CONFIG_CXX_EXCEPTIONS) && defined (ASIO_NO_EXCEPTIONS) + +#include "esp_log.h" + +// +// asio exception stub +// +namespace asio { +namespace detail { +template +void throw_exception(const Exception& e) +{ + ESP_LOGE("esp32_asio_exception", "Caught exception: %s!", e.what()); + abort(); +} +}} +#endif // CONFIG_CXX_EXCEPTIONS==1 && defined (ASIO_NO_EXCEPTIONS) + +#endif // _ESP_EXCEPTION_H_ diff --git a/components/lwip/api/sockets.c b/components/lwip/api/sockets.c index 181740813e..9d2d30391c 100644 --- a/components/lwip/api/sockets.c +++ b/components/lwip/api/sockets.c @@ -186,6 +186,12 @@ static void sockaddr_to_ipaddr_port(const struct sockaddr* sockaddr, ip_addr_t* #define NUM_SOCKETS MEMP_NUM_NETCONN +#if !defined IOV_MAX +#define IOV_MAX 0xFFFF +#elif IOV_MAX > 0xFFFF +#error "IOV_MAX larger than supported by LwIP" +#endif /* IOV_MAX */ + /** This is overridable for the rare case where more than 255 threads * select on the same socket... */ @@ -1197,6 +1203,71 @@ lwip_send(int s, const void *data, size_t size, int flags) return (err == ERR_OK ? (int)written : -1); } +ssize_t +lwip_recvmsg(int s, struct msghdr *message, int flags) +{ + struct lwip_sock *sock; + int i; + ssize_t buflen; + + LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_recvmsg(%d, message=%p, flags=0x%x)\n", s, (void *)message, flags)); + LWIP_ERROR("lwip_recvmsg: invalid message pointer", message != NULL, return ERR_ARG;); + LWIP_ERROR("lwip_recvmsg: unsupported flags", (flags & ~(MSG_PEEK|MSG_DONTWAIT)) == 0, + set_errno(EOPNOTSUPP); return -1;); + + if ((message->msg_iovlen <= 0) || (message->msg_iovlen > IOV_MAX)) { + set_errno(EMSGSIZE); + return -1; + } + + sock = get_socket(s); + if (!sock) { + return -1; + } + + /* check for valid vectors */ + buflen = 0; + for (i = 0; i < message->msg_iovlen; i++) { + if ((message->msg_iov[i].iov_base == NULL) || ((ssize_t)message->msg_iov[i].iov_len <= 0) || + ((size_t)(ssize_t)message->msg_iov[i].iov_len != message->msg_iov[i].iov_len) || + ((ssize_t)(buflen + (ssize_t)message->msg_iov[i].iov_len) <= 0)) { + sock_set_errno(sock, ERR_VAL); + return -1; + } + buflen = (ssize_t)(buflen + (ssize_t)message->msg_iov[i].iov_len); + } + + int recv_flags = flags; + message->msg_flags = 0; + /* recv the data */ + buflen = 0; + for (i = 0; i < message->msg_iovlen; i++) { + /* try to receive into this vector's buffer */ + ssize_t recvd_local = lwip_recvfrom(s, message->msg_iov[i].iov_base, message->msg_iov[i].iov_len, recv_flags, NULL, NULL); + if (recvd_local > 0) { + /* sum up received bytes */ + buflen += recvd_local; + } + if ((recvd_local < 0) || (recvd_local < (int)message->msg_iov[i].iov_len) || + (flags & MSG_PEEK)) { + /* returned prematurely (or peeking, which might actually be limitated to the first iov) */ + if (buflen <= 0) { + /* nothing received at all, propagate the error */ + buflen = recvd_local; + } + break; + } + /* pass MSG_DONTWAIT to lwip_recv_tcp() to prevent waiting for more data */ + recv_flags |= MSG_DONTWAIT; + } + if (buflen > 0) { + /* reset socket error since we have received something */ + sock_set_errno(sock, 0); + } + + return buflen; +} + int lwip_sendmsg(int s, const struct msghdr *msg, int flags) { @@ -3237,6 +3308,14 @@ lwip_connect_r(int s, const struct sockaddr *name, socklen_t namelen) LWIP_API_UNLOCK(); } +int +lwip_recvmsg_r(int s, struct msghdr *msg, int flags) +{ + LWIP_API_LOCK(); + __ret = lwip_recvmsg(s, msg, flags); + LWIP_API_UNLOCK(); +} + int lwip_listen_r(int s, int backlog) { diff --git a/components/lwip/include/lwip/lwip/netdb.h b/components/lwip/include/lwip/lwip/netdb.h index 144a6e0bd4..45a0362e33 100644 --- a/components/lwip/include/lwip/lwip/netdb.h +++ b/components/lwip/include/lwip/lwip/netdb.h @@ -125,12 +125,27 @@ int lwip_getaddrinfo(const char *nodename, struct addrinfo **res); #if LWIP_COMPAT_SOCKETS +#if LWIP_COMPAT_SOCKET_ADDR == 1 +/* Some libraries have problems with inet_... being macros, so please use this define + to declare normal functions */ +static inline int gethostbyname_r(const char *name, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop) +{ return lwip_gethostbyname_r(name, ret, buf, buflen, result, h_errnop); } +static inline struct hostent *gethostbyname(const char *name) +{ return lwip_gethostbyname(name); } +static inline void freeaddrinfo(struct addrinfo *ai) +{ lwip_freeaddrinfo(ai); } +static inline int getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) +{ return lwip_getaddrinfo(nodename, servname, hints, res); } +#else +/* By default fall back to original inet_... macros */ + #define gethostbyname(name) lwip_gethostbyname(name) #define gethostbyname_r(name, ret, buf, buflen, result, h_errnop) \ lwip_gethostbyname_r(name, ret, buf, buflen, result, h_errnop) #define freeaddrinfo(addrinfo) lwip_freeaddrinfo(addrinfo) #define getaddrinfo(nodname, servname, hints, res) \ lwip_getaddrinfo(nodname, servname, hints, res) +#endif /* LWIP_COMPAT_SOCKET_ADDR == 1 */ #endif /* LWIP_COMPAT_SOCKETS */ #ifdef __cplusplus diff --git a/components/lwip/include/lwip/lwip/sockets.h b/components/lwip/include/lwip/lwip/sockets.h index e9ebff6560..ecc219b439 100644 --- a/components/lwip/include/lwip/lwip/sockets.h +++ b/components/lwip/include/lwip/lwip/sockets.h @@ -530,7 +530,6 @@ int lwip_fcntl(int s, int cmd, int val); #if LWIP_COMPAT_SOCKETS != 2 #if ESP_THREAD_SAFE - int lwip_accept_r(int s, struct sockaddr *addr, socklen_t *addrlen); int lwip_bind_r(int s, const struct sockaddr *name, socklen_t namelen); int lwip_shutdown_r(int s, int how); @@ -541,6 +540,7 @@ int lwip_setsockopt_r (int s, int level, int optname, const void *optval, sockle int lwip_close_r(int s); int lwip_connect_r(int s, const struct sockaddr *name, socklen_t namelen); int lwip_listen_r(int s, int backlog); +int lwip_recvmsg_r(int s, struct msghdr *message, int flags); int lwip_recv_r(int s, void *mem, size_t len, int flags); int lwip_read_r(int s, void *mem, size_t len); int lwip_recvfrom_r(int s, void *mem, size_t len, int flags, @@ -577,6 +577,8 @@ static inline int connect(int s,const struct sockaddr *name,socklen_t namelen) { return lwip_connect_r(s,name,namelen); } static inline int listen(int s,int backlog) { return lwip_listen_r(s,backlog); } +static inline int recvmsg(int sockfd, struct msghdr *msg, int flags) +{ return lwip_recvmsg_r(sockfd, msg, flags); } static inline int recv(int s,void *mem,size_t len,int flags) { return lwip_recv_r(s,mem,len,flags); } static inline int recvfrom(int s,void *mem,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen) @@ -633,6 +635,8 @@ static inline int connect(int s,const struct sockaddr *name,socklen_t namelen) { return lwip_connect(s,name,namelen); } static inline int listen(int s,int backlog) { return lwip_listen(s,backlog); } +static inline int recvmsg(int sockfd, struct msghdr *msg, int flags) +{ return lwip_recvmsg(sockfd, msg, flags); } static inline int recv(int s,void *mem,size_t len,int flags) { return lwip_recv(s,mem,len,flags); } static inline int recvfrom(int s,void *mem,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen) @@ -671,24 +675,37 @@ static inline int ioctl(int s,int cmd,int argp) #endif /* LWIP_COMPAT_SOCKETS != 2 */ #if LWIP_IPV4 && LWIP_IPV6 -#define inet_ntop(af,src,dst,size) \ +#define lwip_inet_ntop(af,src,dst,size) \ (((af) == AF_INET6) ? ip6addr_ntoa_r((const ip6_addr_t*)(src),(dst),(size)) \ : (((af) == AF_INET) ? ip4addr_ntoa_r((const ip4_addr_t*)(src),(dst),(size)) : NULL)) -#define inet_pton(af,src,dst) \ +#define lwip_inet_pton(af,src,dst) \ (((af) == AF_INET6) ? ip6addr_aton((src),(ip6_addr_t*)(dst)) \ : (((af) == AF_INET) ? ip4addr_aton((src),(ip4_addr_t*)(dst)) : 0)) #elif LWIP_IPV4 /* LWIP_IPV4 && LWIP_IPV6 */ -#define inet_ntop(af,src,dst,size) \ +#define lwip_inet_ntop(af,src,dst,size) \ (((af) == AF_INET) ? ip4addr_ntoa_r((const ip4_addr_t*)(src),(dst),(size)) : NULL) -#define inet_pton(af,src,dst) \ +#define lwip_inet_pton(af,src,dst) \ (((af) == AF_INET) ? ip4addr_aton((src),(ip4_addr_t*)(dst)) : 0) #else /* LWIP_IPV4 && LWIP_IPV6 */ -#define inet_ntop(af,src,dst,size) \ +#define lwip_inet_ntop(af,src,dst,size) \ (((af) == AF_INET6) ? ip6addr_ntoa_r((const ip6_addr_t*)(src),(dst),(size)) : NULL) -#define inet_pton(af,src,dst) \ +#define lwip_inet_pton(af,src,dst) \ (((af) == AF_INET6) ? ip6addr_aton((src),(ip6_addr_t*)(dst)) : 0) #endif /* LWIP_IPV4 && LWIP_IPV6 */ +#if LWIP_COMPAT_SOCKET_INET == 1 +/* Some libraries have problems with inet_... being macros, so please use this define + to declare normal functions */ +static inline const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) +{ lwip_inet_ntop(af, src, dst, size); return dst; } +static inline int inet_pton(int af, const char *src, void *dst) +{ lwip_inet_pton(af, src, dst); return 1; } +#else +/* By default fall back to original inet_... macros */ +# define inet_ntop(a,b,c,d) lwip_inet_ntop(a,b,c,d) +# define inet_pton(a,b,c) lwip_inet_pton(a,b,c) +#endif /* LWIP_COMPAT_SOCKET_INET */ + #endif /* LWIP_COMPAT_SOCKETS */ #ifdef __cplusplus diff --git a/components/lwip/include/lwip/posix/netdb.h b/components/lwip/include/lwip/posix/netdb.h index 12d4c7f566..363154f688 100644 --- a/components/lwip/include/lwip/posix/netdb.h +++ b/components/lwip/include/lwip/posix/netdb.h @@ -31,3 +31,10 @@ */ #include "lwip/netdb.h" + +#ifdef ESP_PLATFORM +int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, + char *host, socklen_t hostlen, + char *serv, socklen_t servlen, int flags); + +#endif diff --git a/components/newlib/platform_include/errno.h b/components/newlib/platform_include/errno.h new file mode 100644 index 0000000000..85fb2e15b8 --- /dev/null +++ b/components/newlib/platform_include/errno.h @@ -0,0 +1,39 @@ + +// Copyright 2018 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. +#ifndef _ESP_PLATFORM_ERRNO_H_ +#define _ESP_PLATFORM_ERRNO_H_ + +#include_next "errno.h" + +// +// Possibly define some missing errors +// +#ifndef ESHUTDOWN +#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ +#endif + +#ifndef EAI_SOCKTYPE +#define EAI_SOCKTYPE 10 /* ai_socktype not supported */ +#endif + +#ifndef EAI_AGAIN +#define EAI_AGAIN 2 /* temporary failure in name resolution */ +#endif + +#ifndef EAI_BADFLAGS +#define EAI_BADFLAGS 3 /* invalid value for ai_flags */ +#endif + +#endif // _ESP_PLATFORM_ERRNO_H_ diff --git a/components/newlib/platform_include/net/if.h b/components/newlib/platform_include/net/if.h new file mode 100644 index 0000000000..8760bb156e --- /dev/null +++ b/components/newlib/platform_include/net/if.h @@ -0,0 +1,45 @@ +// Copyright 2018 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. +#ifndef _ESP_PLATFORM_NET_IF_H_ +#define _ESP_PLATFORM_NET_IF_H_ + +#define MSG_DONTROUTE 0x4 /* send without using routing tables */ +#define SOCK_SEQPACKET 5 /* sequenced packet stream */ +#define MSG_EOR 0x8 /* data completes record */ +#define SOCK_SEQPACKET 5 /* sequenced packet stream */ +#define SOMAXCONN 128 + +#define IF_NAMESIZE 16 + +#define IPV6_UNICAST_HOPS 4 /* int; IP6 hops */ + +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 +#define NI_NUMERICSERV 0x00000008 +#define NI_DGRAM 0x00000010 + + +struct ipv6_mreq { + struct in6_addr ipv6mr_multiaddr; + unsigned int ipv6mr_interface; +}; + +typedef u32_t socklen_t; + + +unsigned int if_nametoindex(const char *ifname); + +char *if_indextoname(unsigned int ifindex, char *ifname); + +#endif // _ESP_PLATFORM_NET_IF_H_ diff --git a/components/newlib/platform_include/pthread.h b/components/newlib/platform_include/pthread.h new file mode 100644 index 0000000000..4515fb0098 --- /dev/null +++ b/components/newlib/platform_include/pthread.h @@ -0,0 +1,33 @@ +// Copyright 2018 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. +#ifndef __ESP_PLATFORM_PTHREAD_H__ +#define __ESP_PLATFORM_PTHREAD_H__ + +#include +#include +#include_next + +#ifdef __cplusplus +extern "C" { +#endif + +int pthread_condattr_getclock(const pthread_condattr_t * attr, clockid_t * clock_id); + +int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id); + +#ifdef __cplusplus +} +#endif + +#endif // __ESP_PLATFORM_PTHREAD_H__ diff --git a/components/newlib/platform_include/sys/poll.h b/components/newlib/platform_include/sys/poll.h new file mode 100644 index 0000000000..6e0067347c --- /dev/null +++ b/components/newlib/platform_include/sys/poll.h @@ -0,0 +1,32 @@ +// Copyright 2018 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. +#ifndef _ESP_PLATFORM_SYS_POLL_H_ +#define _ESP_PLATFORM_SYS_POLL_H_ + +#define POLLIN 0x0001 /* any readable data available */ +#define POLLOUT 0x0004 /* file descriptor is writeable */ +#define POLLPRI 0x0002 /* OOB/Urgent readable data */ +#define POLLERR 0x0008 /* some poll error occurred */ +#define POLLHUP 0x0010 /* file descriptor was "hung up" */ + +struct pollfd { + int fd; /* The descriptor. */ + short events; /* The event(s) is/are specified here. */ + short revents; /* Events found are returned here. */ +}; + +typedef unsigned int nfds_t; +int poll(struct pollfd *fds, nfds_t nfds, int timeout); + +#endif // _ESP_PLATFORM_SYS_POLL_H_ diff --git a/components/newlib/platform_include/sys/uio.h b/components/newlib/platform_include/sys/uio.h new file mode 100644 index 0000000000..ede27b2351 --- /dev/null +++ b/components/newlib/platform_include/sys/uio.h @@ -0,0 +1,21 @@ +// Copyright 2018 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. +#ifndef _ESP_PLATFORM_SYS_UIO_H_ +#define _ESP_PLATFORM_SYS_UIO_H_ + +int writev(int s, const struct iovec *iov, int iovcnt); + +ssize_t readv(int fd, const struct iovec *iov, int iovcnt); + +#endif // _ESP_PLATFORM_SYS_UIO_H_ diff --git a/components/newlib/platform_include/sys/un.h b/components/newlib/platform_include/sys/un.h new file mode 100644 index 0000000000..a99b183259 --- /dev/null +++ b/components/newlib/platform_include/sys/un.h @@ -0,0 +1,24 @@ +// Copyright 2018 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. +#ifndef _ESP_PLATFORM_SYS_UN_H_ +#define _ESP_PLATFORM_SYS_UN_H_ + +#define AF_UNIX 1 /* local to host (pipes) */ + +struct sockaddr_un { + short sun_family; /*AF_UNIX*/ + char sun_path[108]; /*path name */ +}; + +#endif // _ESP_PLATFORM_SYS_UN_H_ diff --git a/components/newlib/platform_include/sys/unistd.h b/components/newlib/platform_include/sys/unistd.h index e76c84b90b..e09b68be25 100644 --- a/components/newlib/platform_include/sys/unistd.h +++ b/components/newlib/platform_include/sys/unistd.h @@ -23,6 +23,7 @@ extern "C" { #include_next int _EXFUN(truncate, (const char *, off_t __length)); +int _EXFUN(gethostname, (char *__name, size_t __len)); #ifdef __cplusplus } diff --git a/components/newlib/pthread.c b/components/newlib/pthread.c new file mode 100644 index 0000000000..71e50d9e5a --- /dev/null +++ b/components/newlib/pthread.c @@ -0,0 +1,10 @@ +#include +#include "esp_log.h" + +const static char *TAG = "esp32_asio_pthread"; + +int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id) +{ + ESP_LOGW(TAG, "%s: not yet supported!", __FUNCTION__); + return 0; +} diff --git a/components/vfs/include/sys/ioctl.h b/components/vfs/include/sys/ioctl.h index 95bad9818b..90cbb47d66 100644 --- a/components/vfs/include/sys/ioctl.h +++ b/components/vfs/include/sys/ioctl.h @@ -14,5 +14,12 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + int ioctl(int fd, int request, ...); +#ifdef __cplusplus +} +#endif diff --git a/docs/en/COPYRIGHT.rst b/docs/en/COPYRIGHT.rst index 8068d135d6..5c3a710472 100644 --- a/docs/en/COPYRIGHT.rst +++ b/docs/en/COPYRIGHT.rst @@ -53,6 +53,8 @@ These third party libraries can be included into the application (firmware) prod * :component_file:`SD/MMC driver ` is derived from `OpenBSD SD/MMC driver`_, Copyright (c) 2006 Uwe Stuehler, and is licensed under BSD license. +* :component:`Asio `, Copyright (c) 2003-2018 Christopher M. Kohlhoff is licensed under the Boost Software License. + Build Tools ----------- @@ -152,4 +154,4 @@ Copyright (C) 2011, ChaN, all right reserved. .. _OpenBSD SD/MMC driver: https://github.com/openbsd/src/blob/f303646/sys/dev/sdmmc/sdmmc.c .. _Mbed TLS: https://github.com/ARMmbed/mbedtls .. _spiffs: https://github.com/pellepl/spiffs - +.. _asio: https://github.com/chriskohlhoff/asio diff --git a/docs/en/api-reference/protocols/asio.rst b/docs/en/api-reference/protocols/asio.rst new file mode 100644 index 0000000000..635ef053ff --- /dev/null +++ b/docs/en/api-reference/protocols/asio.rst @@ -0,0 +1,32 @@ +ASIO port +========= + +Overview +-------- +Asio is a cross-platform C++ library, see https://think-async.com. It provides a consistent asynchronous model using a modern C++ approach. + + +ASIO documentation +^^^^^^^^^^^^^^^^^^ +Please refer to the original asio documentation at https://think-async.com/Asio/Documentation. +Asio also comes with a number of examples which could be find under Documentation/Examples on that web site. + +Supported features +^^^^^^^^^^^^^^^^^^ +ESP platform port currently supports only network asynchronous socket operations; does not support serial port and ssl. +Internal asio settings for ESP include +- EXCEPTIONS: Supported, choice in menuconfig +- SIGNAL, SIGACTION: Not supported +- EPOLL, EVENTFD: Not supported +- TYPEID: Disabled by default, but supported in toolchain and asio (provided stdlib recompiled with -frtti) + +Application Example +------------------- +ESP examples are based on standard asio examples `examples/protocols/asio`: +- udp_echo_server +- tcp_echo_server +- chat_client +- chat_server +Please refer to the specific example README.md for details + + diff --git a/docs/en/api-reference/protocols/index.rst b/docs/en/api-reference/protocols/index.rst index 7bcd671152..55dcf4325a 100644 --- a/docs/en/api-reference/protocols/index.rst +++ b/docs/en/api-reference/protocols/index.rst @@ -8,5 +8,6 @@ Protocols API ESP-TLS HTTP Client HTTP Server + ASIO Example code for this API section is provided in :example:`protocols` directory of ESP-IDF examples. diff --git a/docs/zh_CN/api-reference/protocols/asio.rst b/docs/zh_CN/api-reference/protocols/asio.rst new file mode 100644 index 0000000000..d1afa231e4 --- /dev/null +++ b/docs/zh_CN/api-reference/protocols/asio.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/protocols/asio.rst \ No newline at end of file diff --git a/examples/protocols/asio/chat_client/Makefile b/examples/protocols/asio/chat_client/Makefile new file mode 100644 index 0000000000..8f651a3eb3 --- /dev/null +++ b/examples/protocols/asio/chat_client/Makefile @@ -0,0 +1,7 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := asio_chatclient + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/asio/chat_client/README.md b/examples/protocols/asio/chat_client/README.md new file mode 100644 index 0000000000..0ab16f578f --- /dev/null +++ b/examples/protocols/asio/chat_client/README.md @@ -0,0 +1,19 @@ +# ASIO chat server example + +Simple asio chat client using WiFi STA + +## Example workflow + +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- Once connected and acquired IP address ASIO chat client connects to a corresponding server whose port number and ip are defined through `make menuconfig` +- Chat client receives all messages from other chat clients, also it sends message received from stdin using `make monitor` + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and server ip address and port number +- Start chat server either on host machine or as another ESP device running chat_server example +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connect to your access point +- Receive and send messages to/from other clients on stdin/stdout via serial terminal. + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/asio/chat_client/asio_chat_client_test.py b/examples/protocols/asio/chat_client/asio_chat_client_test.py new file mode 100644 index 0000000000..a1cce97c5a --- /dev/null +++ b/examples/protocols/asio/chat_client/asio_chat_client_test.py @@ -0,0 +1,98 @@ +import re +import os +import sys +from socket import * +from threading import Thread +import time + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + +global g_client_response; +global g_msg_to_client; + +g_client_response = "" +g_msg_to_client = " 3XYZ" + +def get_my_ip(): + s1 = socket(AF_INET, SOCK_DGRAM) + s1.connect(("8.8.8.8", 80)) + my_ip = s1.getsockname()[0] + s1.close() + return my_ip + +def chat_server_sketch(my_ip): + global g_client_response + print("Starting the server on {}".format(my_ip)) + port=2222 + s=socket(AF_INET, SOCK_STREAM) + s.bind((my_ip, port)) + s.listen(1) + q,addr=s.accept() + print("connection accepted") + q.send(g_msg_to_client) + data = q.recv(1024) + # check if received initial empty message + if (len(data)>4): + g_client_response = data + else: + g_client_response = q.recv(1024) + print("received from client {}".format(g_client_response)) + s.close() + print("server closed") + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_asio_chat_client(env, extra_data): + """ + steps: | + 1. Test to start simple tcp server + 2. `dut1` joins AP + 3. Test injects server IP to `dut1`via stdin + 4. Test evaluates `dut1` receives a message server placed + 5. Test injects a message to `dut1` to be sent as chat_client message + 6. Test evaluates received test message in host server + """ + global g_client_response + global g_msg_to_client + test_msg="ABC" + dut1 = env.get_dut("chat_client", "examples/protocols/asio/chat_client") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "asio_chatclient.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("asio_chatclient_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("asio_chatclient_size", bin_size//1024) + # 1. start a tcp server on the host + host_ip = get_my_ip() + thread1 = Thread(target = chat_server_sketch, args = (host_ip,)) + thread1.start() + # 2. start the dut test and wait till client gets IP address + dut1.start_app() + data = dut1.expect(re.compile(r" sta ip: ([^,]+),")) + # 3. send host's IP to the client i.e. the `dut1` + dut1.write(host_ip) + # 4. client `dut1` should receive a message + dut1.expect(g_msg_to_client[4:]) # Strip out the front 4 bytes of message len (see chat_message protocol) + # 5. write test message from `dut1` chat_client to the server + dut1.write(test_msg) + while g_client_response == "": + time.sleep(1) + print(g_client_response) + # 6. evaluate host_server received this message + if (g_client_response[4:] == test_msg): + print("PASS: Received correct message") + pass + else: + print("Failure!") + raise ValueError('Wrong data received from asi tcp server: {} (expected:{})'.format(g_client_response, test_msg)) + thread1.join() + +if __name__ == '__main__': + test_examples_protocol_asio_chat_client() diff --git a/examples/protocols/asio/chat_client/components/component.mk b/examples/protocols/asio/chat_client/components/component.mk new file mode 100644 index 0000000000..b23b0cb719 --- /dev/null +++ b/examples/protocols/asio/chat_client/components/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_SRCDIRS = . diff --git a/examples/protocols/asio/chat_client/components/wifi_asio.cpp b/examples/protocols/asio/chat_client/components/wifi_asio.cpp new file mode 100644 index 0000000000..b90ce26ae0 --- /dev/null +++ b/examples/protocols/asio/chat_client/components/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "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 "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.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_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +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_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + 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)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + 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() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + 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); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +} diff --git a/examples/protocols/asio/chat_client/main/Kconfig.projbuild b/examples/protocols/asio/chat_client/main/Kconfig.projbuild new file mode 100644 index 0000000000..32e5f3fcb4 --- /dev/null +++ b/examples/protocols/asio/chat_client/main/Kconfig.projbuild @@ -0,0 +1,27 @@ +menu "Example Configuration" + +config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config EXAMPLE_PORT + string "asio example port number" + default "2222" + help + Port number used by ASIO example + +config EXAMPLE_SERVER_IP + string "asio example server ip for this client to connect to (leave defalut=FROM_STDIN to enter the server address via serial terminal)" + default "FROM_STDIN" + help + Please set the host name or ip address of corespondant server running + +endmenu diff --git a/examples/protocols/asio/chat_client/main/chat_client.cpp b/examples/protocols/asio/chat_client/main/chat_client.cpp new file mode 100644 index 0000000000..e18d8d4e3c --- /dev/null +++ b/examples/protocols/asio/chat_client/main/chat_client.cpp @@ -0,0 +1,165 @@ +// +// chat_client.cpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include "asio.hpp" +#include "chat_message.hpp" + +using asio::ip::tcp; + +typedef std::deque chat_message_queue; + +class chat_client +{ +public: + chat_client(asio::io_context& io_context, + const tcp::resolver::results_type& endpoints) + : io_context_(io_context), + socket_(io_context) + { + do_connect(endpoints); + } + + void write(const chat_message& msg) + { + asio::post(io_context_, + [this, msg]() + { + bool write_in_progress = !write_msgs_.empty(); + write_msgs_.push_back(msg); + if (!write_in_progress) + { + do_write(); + } + }); + } + + void close() + { + asio::post(io_context_, [this]() { socket_.close(); }); + } + +private: + void do_connect(const tcp::resolver::results_type& endpoints) + { + asio::async_connect(socket_, endpoints, + [this](std::error_code ec, tcp::endpoint) + { + if (!ec) + { + do_read_header(); + } + }); + } + + void do_read_header() + { + asio::async_read(socket_, + asio::buffer(read_msg_.data(), chat_message::header_length), + [this](std::error_code ec, std::size_t /*length*/) + { + if (!ec && read_msg_.decode_header()) + { + do_read_body(); + } + else + { + socket_.close(); + } + }); + } + + void do_read_body() + { + asio::async_read(socket_, + asio::buffer(read_msg_.body(), read_msg_.body_length()), + [this](std::error_code ec, std::size_t /*length*/) + { + if (!ec) + { + std::cout.write(read_msg_.body(), read_msg_.body_length()); + std::cout << "\n"; + do_read_header(); + } + else + { + socket_.close(); + } + }); + } + + void do_write() + { + asio::async_write(socket_, + asio::buffer(write_msgs_.front().data(), + write_msgs_.front().length()), + [this](std::error_code ec, std::size_t /*length*/) + { + if (!ec) + { + write_msgs_.pop_front(); + if (!write_msgs_.empty()) + { + do_write(); + } + } + else + { + socket_.close(); + } + }); + } + +private: + asio::io_context& io_context_; + tcp::socket socket_; + chat_message read_msg_; + chat_message_queue write_msgs_; +}; + +void read_line(char * line, int max_chars); + + +void asio_main() +{ + std::string name(CONFIG_EXAMPLE_SERVER_IP); + std::string port(CONFIG_EXAMPLE_PORT); + char line[chat_message::max_body_length + 1] = { 0 }; + + if (name == "FROM_STDIN") { + std::cout << "Please enter ip address of chat server" << std::endl; + if (std::cin.getline(line, chat_message::max_body_length + 1)) { + name = line; + std::cout << "Chat server IP:" << name << std::endl; + } + } + + asio::io_context io_context; + tcp::resolver resolver(io_context); + auto endpoints = resolver.resolve(name, port); + + chat_client c(io_context, endpoints); + + std::thread t([&io_context](){ io_context.run(); }); + + while (std::cin.getline(line, chat_message::max_body_length + 1) && std::string(line) != "exit\n") { + chat_message msg; + msg.body_length(std::strlen(line)); + std::memcpy(msg.body(), line, msg.body_length()); + msg.encode_header(); + c.write(msg); + } + + c.close(); + t.join(); +} diff --git a/examples/protocols/asio/chat_client/main/chat_message.hpp b/examples/protocols/asio/chat_client/main/chat_message.hpp new file mode 100644 index 0000000000..629105b05b --- /dev/null +++ b/examples/protocols/asio/chat_client/main/chat_message.hpp @@ -0,0 +1,91 @@ +// +// chat_message.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef CHAT_MESSAGE_HPP +#define CHAT_MESSAGE_HPP + +#include +#include +#include + +class chat_message +{ +public: + enum { header_length = 4 }; + enum { max_body_length = 512 }; + + chat_message() + : body_length_(0) + { + } + + const char* data() const + { + return data_; + } + + char* data() + { + return data_; + } + + std::size_t length() const + { + return header_length + body_length_; + } + + const char* body() const + { + return data_ + header_length; + } + + char* body() + { + return data_ + header_length; + } + + std::size_t body_length() const + { + return body_length_; + } + + void body_length(std::size_t new_length) + { + body_length_ = new_length; + if (body_length_ > max_body_length) + body_length_ = max_body_length; + } + + bool decode_header() + { + char header[header_length + 1] = ""; + std::strncat(header, data_, header_length); + body_length_ = std::atoi(header); + if (body_length_ > max_body_length) + { + body_length_ = 0; + return false; + } + return true; + } + + void encode_header() + { + char header[header_length + 1] = ""; + std::sprintf(header, "%4d", static_cast(body_length_)); + std::memcpy(data_, header, header_length); + } + +private: + char data_[header_length + max_body_length]; + std::size_t body_length_; +}; + +#endif // CHAT_MESSAGE_HPP diff --git a/examples/protocols/asio/chat_client/main/component.mk b/examples/protocols/asio/chat_client/main/component.mk new file mode 100644 index 0000000000..61f8990c31 --- /dev/null +++ b/examples/protocols/asio/chat_client/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/protocols/asio/chat_client/sdkconfig.defaults b/examples/protocols/asio/chat_client/sdkconfig.defaults new file mode 100644 index 0000000000..d99552e192 --- /dev/null +++ b/examples/protocols/asio/chat_client/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_MAIN_TASK_STACK_SIZE=8192 diff --git a/examples/protocols/asio/chat_server/Makefile b/examples/protocols/asio/chat_server/Makefile new file mode 100644 index 0000000000..c69f109c9e --- /dev/null +++ b/examples/protocols/asio/chat_server/Makefile @@ -0,0 +1,7 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := asio_chatserver + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/asio/chat_server/README.md b/examples/protocols/asio/chat_server/README.md new file mode 100644 index 0000000000..591efd9776 --- /dev/null +++ b/examples/protocols/asio/chat_server/README.md @@ -0,0 +1,22 @@ +# ASIO chat server example + +Simple asio chat server using WiFi STA + +## Example workflow + +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- Once connected and acquired IP address, ASIO chat server is started on port number defined through `make menuconfig` +- Chat server echoes a message (received from any client) to all connected clients + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and port number +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connect to your access point (note the IP address) +- Connect to the server using multiple clients, for example using any option below + - build and run asi chat client on your host machine + - run chat_client asio example on ESP platform + - since chat message consist of ascii size and message, it is possible to + netcat `nc IP PORT` and type for example ` 4ABC` to transmit 'ABC\n' + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/asio/chat_server/asio_chat_server_test.py b/examples/protocols/asio/chat_server/asio_chat_server_test.py new file mode 100644 index 0000000000..55746f1f97 --- /dev/null +++ b/examples/protocols/asio/chat_server/asio_chat_server_test.py @@ -0,0 +1,56 @@ +import re +import os +import sys +from socket import * + + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + + + + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_asio_chat_server(env, extra_data): + """ + steps: | + 1. join AP + 2. Start server + 3. Test connects to server and sends a test message + 4. Test evaluates received test message from server + """ + test_msg=" 4ABC\n" + dut1 = env.get_dut("chat_server", "examples/protocols/asio/chat_server") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "asio_chatserver.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("asio_chatserver_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("asio_chatserver_size", bin_size//1024) + # 1. start test + dut1.start_app() + # 2. get the server IP address + data = dut1.expect(re.compile(r" sta ip: ([^,]+),")) + # 3. create tcp client and connect to server + cli = socket(AF_INET,SOCK_STREAM) + cli.connect((data[0],80)) + cli.send(test_msg) + data = cli.recv(1024) + # 4. check the message received back from the server + if (data == test_msg): + print("PASS: Received correct message {}".format(data)) + pass + else: + print("Failure!") + raise ValueError('Wrong data received from asi tcp server: {} (expoected:{})'.format(data, test_msg)) + + +if __name__ == '__main__': + test_examples_protocol_asio_chat_server() diff --git a/examples/protocols/asio/chat_server/components/component.mk b/examples/protocols/asio/chat_server/components/component.mk new file mode 100644 index 0000000000..b23b0cb719 --- /dev/null +++ b/examples/protocols/asio/chat_server/components/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_SRCDIRS = . diff --git a/examples/protocols/asio/chat_server/components/wifi_asio.cpp b/examples/protocols/asio/chat_server/components/wifi_asio.cpp new file mode 100644 index 0000000000..b90ce26ae0 --- /dev/null +++ b/examples/protocols/asio/chat_server/components/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "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 "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.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_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +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_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + 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)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + 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() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + 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); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +} diff --git a/examples/protocols/asio/chat_server/main/Kconfig.projbuild b/examples/protocols/asio/chat_server/main/Kconfig.projbuild new file mode 100644 index 0000000000..c0d6fd9cd5 --- /dev/null +++ b/examples/protocols/asio/chat_server/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Example Configuration" + +config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config EXAMPLE_PORT + string "asio example port number" + default "80" + help + Port number used by ASIO example + +endmenu diff --git a/examples/protocols/asio/chat_server/main/chat_message.hpp b/examples/protocols/asio/chat_server/main/chat_message.hpp new file mode 100644 index 0000000000..629105b05b --- /dev/null +++ b/examples/protocols/asio/chat_server/main/chat_message.hpp @@ -0,0 +1,91 @@ +// +// chat_message.hpp +// ~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef CHAT_MESSAGE_HPP +#define CHAT_MESSAGE_HPP + +#include +#include +#include + +class chat_message +{ +public: + enum { header_length = 4 }; + enum { max_body_length = 512 }; + + chat_message() + : body_length_(0) + { + } + + const char* data() const + { + return data_; + } + + char* data() + { + return data_; + } + + std::size_t length() const + { + return header_length + body_length_; + } + + const char* body() const + { + return data_ + header_length; + } + + char* body() + { + return data_ + header_length; + } + + std::size_t body_length() const + { + return body_length_; + } + + void body_length(std::size_t new_length) + { + body_length_ = new_length; + if (body_length_ > max_body_length) + body_length_ = max_body_length; + } + + bool decode_header() + { + char header[header_length + 1] = ""; + std::strncat(header, data_, header_length); + body_length_ = std::atoi(header); + if (body_length_ > max_body_length) + { + body_length_ = 0; + return false; + } + return true; + } + + void encode_header() + { + char header[header_length + 1] = ""; + std::sprintf(header, "%4d", static_cast(body_length_)); + std::memcpy(data_, header, header_length); + } + +private: + char data_[header_length + max_body_length]; + std::size_t body_length_; +}; + +#endif // CHAT_MESSAGE_HPP diff --git a/examples/protocols/asio/chat_server/main/chat_server.cpp b/examples/protocols/asio/chat_server/main/chat_server.cpp new file mode 100644 index 0000000000..05740f4798 --- /dev/null +++ b/examples/protocols/asio/chat_server/main/chat_server.cpp @@ -0,0 +1,214 @@ +// +// chat_server.cpp +// ~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include +#include +#include +#include +#include "asio.hpp" +#include "chat_message.hpp" + +using asio::ip::tcp; + +//---------------------------------------------------------------------- + +typedef std::deque chat_message_queue; + +//---------------------------------------------------------------------- + +class chat_participant +{ +public: + virtual ~chat_participant() {} + virtual void deliver(const chat_message& msg) = 0; +}; + +typedef std::shared_ptr chat_participant_ptr; + +//---------------------------------------------------------------------- + +class chat_room +{ +public: + void join(chat_participant_ptr participant) + { + participants_.insert(participant); + for (auto msg: recent_msgs_) + participant->deliver(msg); + } + + void leave(chat_participant_ptr participant) + { + participants_.erase(participant); + } + + void deliver(const chat_message& msg) + { + recent_msgs_.push_back(msg); + while (recent_msgs_.size() > max_recent_msgs) + recent_msgs_.pop_front(); + + for (auto participant: participants_) + participant->deliver(msg); + } + +private: + std::set participants_; + enum { max_recent_msgs = 100 }; + chat_message_queue recent_msgs_; +}; + +//---------------------------------------------------------------------- + +class chat_session + : public chat_participant, + public std::enable_shared_from_this +{ +public: + chat_session(tcp::socket socket, chat_room& room) + : socket_(std::move(socket)), + room_(room) + { + } + + void start() + { + room_.join(shared_from_this()); + do_read_header(); + } + + void deliver(const chat_message& msg) + { + bool write_in_progress = !write_msgs_.empty(); + write_msgs_.push_back(msg); + if (!write_in_progress) + { + do_write(); + } + } + +private: + void do_read_header() + { + auto self(shared_from_this()); + asio::async_read(socket_, + asio::buffer(read_msg_.data(), chat_message::header_length), + [this, self](std::error_code ec, std::size_t /*length*/) + { + if (!ec && read_msg_.decode_header()) + { + do_read_body(); + } + else + { + room_.leave(shared_from_this()); + } + }); + } + + void do_read_body() + { + auto self(shared_from_this()); + asio::async_read(socket_, + asio::buffer(read_msg_.body(), read_msg_.body_length()), + [this, self](std::error_code ec, std::size_t /*length*/) + { + if (!ec) + { + room_.deliver(read_msg_); + do_read_header(); + } + else + { + room_.leave(shared_from_this()); + } + }); + } + + void do_write() + { + auto self(shared_from_this()); + asio::async_write(socket_, + asio::buffer(write_msgs_.front().data(), + write_msgs_.front().length()), + [this, self](std::error_code ec, std::size_t /*length*/) + { + if (!ec) + { + write_msgs_.pop_front(); + if (!write_msgs_.empty()) + { + do_write(); + } + } + else + { + room_.leave(shared_from_this()); + } + }); + } + + tcp::socket socket_; + chat_room& room_; + chat_message read_msg_; + chat_message_queue write_msgs_; +}; + +//---------------------------------------------------------------------- + +class chat_server +{ +public: + chat_server(asio::io_context& io_context, + const tcp::endpoint& endpoint) + : acceptor_(io_context, endpoint) + { + do_accept(); + } + +private: + void do_accept() + { + acceptor_.async_accept( + [this](std::error_code ec, tcp::socket socket) + { + if (!ec) + { + std::make_shared(std::move(socket), room_)->start(); + } + + do_accept(); + }); + } + + tcp::acceptor acceptor_; + chat_room room_; +}; + +//---------------------------------------------------------------------- + +int asio_main() +{ + asio::io_context io_context; + + std::list servers; + + { + tcp::endpoint endpoint(tcp::v4(), std::atoi(CONFIG_EXAMPLE_PORT)); + servers.emplace_back(io_context, endpoint); + } + + io_context.run(); + + return 0; +} diff --git a/examples/protocols/asio/chat_server/main/component.mk b/examples/protocols/asio/chat_server/main/component.mk new file mode 100644 index 0000000000..61f8990c31 --- /dev/null +++ b/examples/protocols/asio/chat_server/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/protocols/asio/chat_server/sdkconfig.defaults b/examples/protocols/asio/chat_server/sdkconfig.defaults new file mode 100644 index 0000000000..d99552e192 --- /dev/null +++ b/examples/protocols/asio/chat_server/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_MAIN_TASK_STACK_SIZE=8192 diff --git a/examples/protocols/asio/tcp_echo_server/Makefile b/examples/protocols/asio/tcp_echo_server/Makefile new file mode 100644 index 0000000000..2a2839a54e --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/Makefile @@ -0,0 +1,7 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := asio_tcp_echoserver + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/asio/tcp_echo_server/README.md b/examples/protocols/asio/tcp_echo_server/README.md new file mode 100644 index 0000000000..a32fd93ea9 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/README.md @@ -0,0 +1,18 @@ +# ASIO tcp echo server example + +Simple asio echo server using WiFi STA + +## Example workflow + +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- Once connected and acquired IP address, ASIO tcp server is started on port number defined through `make menuconfig` +- Server receives and echoes back messages transmitted from client + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and port number +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connect to your access point (note the IP address) +- You can now send a tcp message and check it is repeated, for example using netcat `nc IP PORT` + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/asio/tcp_echo_server/asio_tcp_server_test.py b/examples/protocols/asio/tcp_echo_server/asio_tcp_server_test.py new file mode 100644 index 0000000000..1ab432e626 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/asio_tcp_server_test.py @@ -0,0 +1,59 @@ +import re +import os +import sys +from socket import * + + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + + + + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_asio_tcp_server(env, extra_data): + """ + steps: | + 1. join AP + 2. Start server + 3. Test connects to server and sends a test message + 4. Test evaluates received test message from server + 5. Test evaluates received test message on server stdout + """ + test_msg="echo message from client to server" + dut1 = env.get_dut("tcp_echo_server", "examples/protocols/asio/tcp_echo_server") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "asio_tcp_echoserver.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("asio_tcp_echoserver_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("asio_tcp_echoserver_size", bin_size//1024) + # 1. start test + dut1.start_app() + # 2. get the server IP address + data = dut1.expect(re.compile(r" sta ip: ([^,]+),")) + # 3. create tcp client and connect to server + cli = socket(AF_INET,SOCK_STREAM) + cli.connect((data[0],80)) + cli.send(test_msg) + data = cli.recv(1024) + # 4. check the message received back from the server + if (data == test_msg): + print("PASS: Received correct message") + pass + else: + print("Failure!") + raise ValueError('Wrong data received from asi tcp server: {} (expoected:{})'.format(data, test_msg)) + # 5. check the client message appears also on server terminal + dut1.expect(test_msg) + + +if __name__ == '__main__': + test_examples_protocol_asio_tcp_server() diff --git a/examples/protocols/asio/tcp_echo_server/components/component.mk b/examples/protocols/asio/tcp_echo_server/components/component.mk new file mode 100644 index 0000000000..b23b0cb719 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/components/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_SRCDIRS = . diff --git a/examples/protocols/asio/tcp_echo_server/components/wifi_asio.cpp b/examples/protocols/asio/tcp_echo_server/components/wifi_asio.cpp new file mode 100644 index 0000000000..b90ce26ae0 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/components/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "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 "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.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_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +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_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + 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)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + 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() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + 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); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +} diff --git a/examples/protocols/asio/tcp_echo_server/main/Kconfig.projbuild b/examples/protocols/asio/tcp_echo_server/main/Kconfig.projbuild new file mode 100644 index 0000000000..c0d6fd9cd5 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Example Configuration" + +config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config EXAMPLE_PORT + string "asio example port number" + default "80" + help + Port number used by ASIO example + +endmenu diff --git a/examples/protocols/asio/tcp_echo_server/main/component.mk b/examples/protocols/asio/tcp_echo_server/main/component.mk new file mode 100644 index 0000000000..8c8d52abcb --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/main/component.mk @@ -0,0 +1,9 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# + diff --git a/examples/protocols/asio/tcp_echo_server/main/echo_server.cpp b/examples/protocols/asio/tcp_echo_server/main/echo_server.cpp new file mode 100644 index 0000000000..22f171f530 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/main/echo_server.cpp @@ -0,0 +1,91 @@ +#include "asio.hpp" +#include +#include + +using asio::ip::tcp; + +class session + : public std::enable_shared_from_this +{ +public: + session(tcp::socket socket) + : socket_(std::move(socket)) + { + } + + void start() + { + do_read(); + } + +private: + void do_read() + { + auto self(shared_from_this()); + socket_.async_read_some(asio::buffer(data_, max_length), + [this, self](std::error_code ec, std::size_t length) + { + if (!ec) + { + std::cout << data_ << std::endl; + do_write(length); + } + }); + } + + void do_write(std::size_t length) + { + auto self(shared_from_this()); + asio::async_write(socket_, asio::buffer(data_, length), + [this, self](std::error_code ec, std::size_t length) + { + if (!ec) + { + do_read(); + } + }); + } + + tcp::socket socket_; + enum { max_length = 1024 }; + char data_[max_length]; +}; + +class server +{ +public: + server(asio::io_context& io_context, short port) + : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) + { + do_accept(); + } + +private: + void do_accept() + { + acceptor_.async_accept( + [this](std::error_code ec, tcp::socket socket) + { + if (!ec) + { + std::make_shared(std::move(socket))->start(); + } + + do_accept(); + }); + } + + tcp::acceptor acceptor_; +}; + + +void asio_main() +{ + + asio::io_context io_context; + + server s(io_context, std::atoi(CONFIG_EXAMPLE_PORT)); + + io_context.run(); + +} \ No newline at end of file diff --git a/examples/protocols/asio/tcp_echo_server/sdkconfig.defaults b/examples/protocols/asio/tcp_echo_server/sdkconfig.defaults new file mode 100644 index 0000000000..d99552e192 --- /dev/null +++ b/examples/protocols/asio/tcp_echo_server/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_MAIN_TASK_STACK_SIZE=8192 diff --git a/examples/protocols/asio/udp_echo_server/Makefile b/examples/protocols/asio/udp_echo_server/Makefile new file mode 100644 index 0000000000..c659aeae0a --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/Makefile @@ -0,0 +1,7 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# +PROJECT_NAME := asio_udp_echoserver + +include $(IDF_PATH)/make/project.mk diff --git a/examples/protocols/asio/udp_echo_server/README.md b/examples/protocols/asio/udp_echo_server/README.md new file mode 100644 index 0000000000..6cf2c2ba38 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/README.md @@ -0,0 +1,18 @@ +# ASIO udp echo server example + +Simple asio echo server using WiFi STA + +## Example workflow + +- WiFi STA is started and trying to connect to the access point defined through `make menuconfig` +- Once connected and acquired IP address, ASIO udp server is started on port number defined through `make menuconfig` +- Server receives and echoes back messages transmitted from client + +## Running the example + +- Run `make menuconfig` to configure the access point's SSID and Password and port number +- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal +- Wait for WiFi to connect to your access point (note the IP address) +- You can now send a udp message and check it is repeated, for example using netcat `nc -u IP PORT` + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/asio/udp_echo_server/asio_udp_server_test.py b/examples/protocols/asio/udp_echo_server/asio_udp_server_test.py new file mode 100644 index 0000000000..490a8007a8 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/asio_udp_server_test.py @@ -0,0 +1,58 @@ +import re +import os +import sys +from socket import * + + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + + + + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_asio_udp_server(env, extra_data): + """ + steps: | + 1. join AP + 2. Start server + 3. Test connects to server and sends a test message + 4. Test evaluates received test message from server + 5. Test evaluates received test message on server stdout + """ + test_msg="echo message from client to server" + dut1 = env.get_dut("udp_echo_server", "examples/protocols/asio/udp_echo_server") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "asio_udp_echoserver.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("asio_udp_echoserver_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("asio_udp_echoserver_size", bin_size//1024) + # 1. start test + dut1.start_app() + # 2. get the server IP address + data = dut1.expect(re.compile(r" sta ip: ([^,]+),")) + # 3. create tcp client and connect to server + cli = socket(AF_INET, SOCK_DGRAM) + cli.connect((data[0], 80)) + cli.send(test_msg) + data = cli.recv(1024) + # 4. check the message received back from the server + if (data == test_msg): + print("PASS: Received correct message") + pass + else: + print("Failure!") + raise ValueError('Wrong data received from asi udp server: {} (expoected:{})'.format(data, test_msg)) + # 5. check the client message appears also on server terminal + dut1.expect(test_msg) + +if __name__ == '__main__': + test_examples_protocol_asio_udp_server() diff --git a/examples/protocols/asio/udp_echo_server/components/component.mk b/examples/protocols/asio/udp_echo_server/components/component.mk new file mode 100644 index 0000000000..b23b0cb719 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/components/component.mk @@ -0,0 +1,10 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +COMPONENT_SRCDIRS = . diff --git a/examples/protocols/asio/udp_echo_server/components/wifi_asio.cpp b/examples/protocols/asio/udp_echo_server/components/wifi_asio.cpp new file mode 100644 index 0000000000..b90ce26ae0 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/components/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "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 "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.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_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +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_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + 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)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + 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() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + 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); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +} diff --git a/examples/protocols/asio/udp_echo_server/main/Kconfig.projbuild b/examples/protocols/asio/udp_echo_server/main/Kconfig.projbuild new file mode 100644 index 0000000000..c0d6fd9cd5 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Example Configuration" + +config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +config EXAMPLE_PORT + string "asio example port number" + default "80" + help + Port number used by ASIO example + +endmenu diff --git a/examples/protocols/asio/udp_echo_server/main/component.mk b/examples/protocols/asio/udp_echo_server/main/component.mk new file mode 100644 index 0000000000..61f8990c31 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/examples/protocols/asio/udp_echo_server/main/udp_echo_server.cpp b/examples/protocols/asio/udp_echo_server/main/udp_echo_server.cpp new file mode 100644 index 0000000000..5b6607d736 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/main/udp_echo_server.cpp @@ -0,0 +1,69 @@ +// +// async_udp_echo_server.cpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include + +#include "asio.hpp" + +using asio::ip::udp; + +class server +{ +public: + server(asio::io_context& io_context, short port) + : socket_(io_context, udp::endpoint(udp::v4(), port)) + { + do_receive(); + } + + void do_receive() + { + socket_.async_receive_from( + asio::buffer(data_, max_length), sender_endpoint_, + [this](std::error_code ec, std::size_t bytes_recvd) + { + if (!ec && bytes_recvd > 0) + { + std::cout << data_ << std::endl; + do_send(bytes_recvd); + } + else + { + do_receive(); + } + }); + } + + void do_send(std::size_t length) + { + socket_.async_send_to( + asio::buffer(data_, length), sender_endpoint_, + [this](std::error_code /*ec*/, std::size_t bytes /*bytes_sent*/) + { + do_receive(); + }); + } + +private: + udp::socket socket_; + udp::endpoint sender_endpoint_; + enum { max_length = 1024 }; + char data_[max_length]; +}; + +void asio_main() +{ + asio::io_context io_context; + + server s(io_context, std::atoi(CONFIG_EXAMPLE_PORT)); + + io_context.run(); +} diff --git a/examples/protocols/asio/udp_echo_server/sdkconfig.defaults b/examples/protocols/asio/udp_echo_server/sdkconfig.defaults new file mode 100644 index 0000000000..d99552e192 --- /dev/null +++ b/examples/protocols/asio/udp_echo_server/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_MAIN_TASK_STACK_SIZE=8192 diff --git a/examples/protocols/asio/wifi_init/wifi_asio.cpp b/examples/protocols/asio/wifi_init/wifi_asio.cpp new file mode 100644 index 0000000000..b90ce26ae0 --- /dev/null +++ b/examples/protocols/asio/wifi_init/wifi_asio.cpp @@ -0,0 +1,136 @@ +/* Common WiFi Init as STA for ASIO examples + + 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 "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 "driver/uart.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" + +#include "lwip/err.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_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD + +/* FreeRTOS event group to signal when we are connected*/ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int WIFI_CONNECTED_BIT = BIT0; + +static const char *TAG = "asio example wifi init"; + +/** + * Definition of ASIO main method, which is called after network initialized + */ +void asio_main(); + +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_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void wifi_init_sta() +{ + wifi_event_group = xEventGroupCreate(); + + tcpip_adapter_init(); + 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)); + wifi_config_t wifi_config; + // zero out the config struct to ensure defaults are setup + memset(&wifi_config, 0, sizeof(wifi_sta_config_t)); + // only copy ssid&password from example config + strcpy((char*)(wifi_config.sta.ssid), EXAMPLE_ESP_WIFI_SSID); + strcpy((char*)(wifi_config.sta.password), EXAMPLE_ESP_WIFI_PASS); + + 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() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +extern "C" void app_main() +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_AP"); + wifi_init_sta(); + + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install( (uart_port_t)CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + 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); + + // wait till we receive IP, so asio realated code can be started + xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, 1, 1, portMAX_DELAY); + + // network is ready, let's proceed with ASIO example + asio_main(); +} diff --git a/tools/ci/mirror-list.txt b/tools/ci/mirror-list.txt index 5ff4fb74a3..f75acb2340 100644 --- a/tools/ci/mirror-list.txt +++ b/tools/ci/mirror-list.txt @@ -9,5 +9,6 @@ components/mbedtls/mbedtls @GENERAL_MIRROR_SERVER@/idf/ components/micro-ecc/micro-ecc @GENERAL_MIRROR_SERVER@/idf/micro-ecc.git ALLOW_TO_SYNC_FROM_PUBLIC components/nghttp/nghttp2 @GENERAL_MIRROR_SERVER@/idf/nghttp2.git ALLOW_TO_SYNC_FROM_PUBLIC components/spiffs/spiffs @GENERAL_MIRROR_SERVER@/idf/spiffs.git ALLOW_TO_SYNC_FROM_PUBLIC +components/asio/asio @GENERAL_MIRROR_SERVER@/idf/asio.git third-party/mruby @GENERAL_MIRROR_SERVER@/idf/mruby.git ALLOW_TO_SYNC_FROM_PUBLIC third-party/neverbleed @GENERAL_MIRROR_SERVER@/idf/neverbleed.git ALLOW_TO_SYNC_FROM_PUBLIC