mirror of
https://github.com/esphome/esphome.git
synced 2026-07-10 08:55:36 +00:00
[socket] Add BSD socket support for nRF52 (#16699)
Co-authored-by: tomaszduda23 <tomaszduda23@gmail.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
This commit is contained in:
@@ -149,6 +149,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
ln882x=IMPLEMENTATION_LWIP_SOCKETS,
|
||||
rtl87xx=IMPLEMENTATION_LWIP_SOCKETS,
|
||||
host=IMPLEMENTATION_BSD_SOCKETS,
|
||||
nrf52=IMPLEMENTATION_BSD_SOCKETS,
|
||||
): cv.one_of(
|
||||
IMPLEMENTATION_LWIP_TCP,
|
||||
IMPLEMENTATION_LWIP_SOCKETS,
|
||||
@@ -168,6 +169,11 @@ async def to_code(config):
|
||||
cg.add_define("USE_SOCKET_IMPL_LWIP_SOCKETS")
|
||||
elif impl == IMPLEMENTATION_BSD_SOCKETS:
|
||||
cg.add_define("USE_SOCKET_IMPL_BSD_SOCKETS")
|
||||
if CORE.using_zephyr:
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
|
||||
zephyr_add_prj_conf("NET_SOCKETS", True)
|
||||
zephyr_add_prj_conf("POSIX_API", True)
|
||||
# ESP32 and LibreTiny both have LwIP >= 2.1.3 with lwip_socket_dbg_get_socket()
|
||||
# and FreeRTOS task notifications — enable fast select to bypass lwip_select().
|
||||
# Only when not using lwip_tcp, which does not provide select() support.
|
||||
|
||||
@@ -22,11 +22,13 @@ BSDSocketImpl::BSDSocketImpl(int fd, bool monitor_loop) {
|
||||
if (flags >= 0)
|
||||
::fcntl(this->fd_, F_SETFD, flags | FD_CLOEXEC);
|
||||
#endif
|
||||
// Guard structure matches socket_ready_fd(): non-HOST platforms (nRF52/OpenThread)
|
||||
// do not register fds with the esphome select loop, so monitor_loop is a no-op there.
|
||||
if (!monitor_loop)
|
||||
return;
|
||||
#ifdef USE_LWIP_FAST_SELECT
|
||||
this->cached_sock_ = hook_fd_for_fast_select(this->fd_);
|
||||
#else
|
||||
#elif defined(USE_HOST)
|
||||
this->loop_monitored_ = wake_register_fd(this->fd_);
|
||||
#endif
|
||||
}
|
||||
@@ -45,7 +47,7 @@ int BSDSocketImpl::close() {
|
||||
// touch an unrelated socket's pcb. No per-socket callback unhook is needed —
|
||||
// all LwIP sockets share the same static event_callback.
|
||||
this->cached_sock_ = nullptr;
|
||||
#else
|
||||
#elif defined(USE_HOST)
|
||||
if (this->loop_monitored_) {
|
||||
wake_unregister_fd(this->fd_);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class BSDSocketImpl {
|
||||
#endif
|
||||
}
|
||||
ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) {
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_ZEPHYR)
|
||||
return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len);
|
||||
#else
|
||||
return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len);
|
||||
@@ -85,6 +85,19 @@ class BSDSocketImpl {
|
||||
ssize_t readv(const struct iovec *iov, int iovcnt) {
|
||||
#if defined(USE_ESP32)
|
||||
return ::lwip_readv(this->fd_, iov, iovcnt);
|
||||
#elif defined(USE_ZEPHYR)
|
||||
// Zephyr does not provide readv(); emulate with a read() loop. Stream sockets only:
|
||||
// on a datagram socket each read() would consume a separate datagram, not scatter one.
|
||||
ssize_t total = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t n = ::read(this->fd_, iov[i].iov_base, iov[i].iov_len);
|
||||
if (n < 0)
|
||||
return total > 0 ? total : n;
|
||||
total += n;
|
||||
if (static_cast<size_t>(n) < iov[i].iov_len)
|
||||
break;
|
||||
}
|
||||
return total;
|
||||
#else
|
||||
return ::readv(this->fd_, iov, iovcnt);
|
||||
#endif
|
||||
@@ -100,6 +113,19 @@ class BSDSocketImpl {
|
||||
ssize_t writev(const struct iovec *iov, int iovcnt) {
|
||||
#if defined(USE_ESP32)
|
||||
return ::lwip_writev(this->fd_, iov, iovcnt);
|
||||
#elif defined(USE_ZEPHYR)
|
||||
// Zephyr does not provide writev(); emulate with a write() loop. Stream sockets only:
|
||||
// on a datagram socket each write() would emit a separate datagram, not gather one.
|
||||
ssize_t total = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t n = ::write(this->fd_, iov[i].iov_base, iov[i].iov_len);
|
||||
if (n < 0)
|
||||
return total > 0 ? total : n;
|
||||
total += n;
|
||||
if (static_cast<size_t>(n) < iov[i].iov_len)
|
||||
break; // partial write: stop so caller resumes from the correct stream offset
|
||||
}
|
||||
return total;
|
||||
#else
|
||||
return ::writev(this->fd_, iov, iovcnt);
|
||||
#endif
|
||||
|
||||
@@ -158,7 +158,9 @@ using socklen_t = uint32_t;
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#ifndef USE_ZEPHYR
|
||||
#include <sys/uio.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef USE_HOST
|
||||
@@ -167,6 +169,10 @@ using socklen_t = uint32_t;
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/tcp.h>
|
||||
#endif // USE_HOST
|
||||
#ifdef USE_ZEPHYR
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#endif // USE_ZEPHYR
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
|
||||
|
||||
@@ -12,9 +12,19 @@
|
||||
namespace esphome::socket {
|
||||
|
||||
#ifdef USE_HOST
|
||||
// Shared ready() implementation for fd-based socket implementations (BSD and LWIP sockets).
|
||||
// Checks if the host wake select() loop has marked this fd as ready.
|
||||
// Host: ready when the wake select() loop has flagged this fd (or it isn't monitored).
|
||||
bool socket_ready_fd(int fd, bool loop_monitored) { return !loop_monitored || wake_fd_ready(fd); }
|
||||
#elif defined(USE_ZEPHYR)
|
||||
// Zephyr (nRF52): fd monitoring isn't wired into the esphome select loop
|
||||
// (wake_register_fd is USE_HOST-only), so loop_monitored is always false. Always
|
||||
// return true — the caller handles EAGAIN/EWOULDBLOCK on read.
|
||||
//
|
||||
// Cost (known trade-off, not an oversight): loop-monitored sockets (API, web_server)
|
||||
// are read every loop() iteration and bail on EAGAIN; there is no event-driven wake,
|
||||
// so the main loop busy-polls at loop frequency and cannot idle between packets.
|
||||
// TODO: wire Zephyr fds into an event-driven wake source (e.g. zsock_poll/k_poll) so
|
||||
// the loop can sleep between packets on battery/OpenThread targets.
|
||||
bool socket_ready_fd(int /*fd*/, bool /*loop_monitored*/) { return true; }
|
||||
#endif
|
||||
|
||||
// Platform-specific inet_ntop wrappers
|
||||
@@ -40,6 +50,19 @@ static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t
|
||||
return lwip_inet_ntop(AF_INET6, addr, buf, size);
|
||||
}
|
||||
#endif
|
||||
#elif defined(USE_ZEPHYR)
|
||||
// Zephyr BSD sockets — use Zephyr native address formatting via POSIX-subset wrappers.
|
||||
// <zephyr/net/socket.h> is already included transitively through <sys/socket.h>.
|
||||
static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
|
||||
return zsock_inet_ntop(AF_INET, addr, buf, size);
|
||||
}
|
||||
// IPv6 is always enabled on nRF52 (config validation enforces enable_ipv6=True),
|
||||
// but the guard is retained for consistency with other platform blocks.
|
||||
#if USE_NETWORK_IPV6
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
return zsock_inet_ntop(AF_INET6, addr, buf, size);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
// BSD sockets (host, ESP32-IDF)
|
||||
static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
|
||||
@@ -68,6 +91,15 @@ size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::s
|
||||
esphome_inet_ntop4(&addr->sin6_addr.s6_addr[12], buf.data(), buf.size()) != nullptr) {
|
||||
return strlen(buf.data());
|
||||
}
|
||||
#elif defined(USE_ZEPHYR)
|
||||
// Format IPv4-mapped IPv6 addresses as regular IPv4. Zephyr uses the standard POSIX
|
||||
// s6_addr layout (not the LWIP union) but provides no IN6_IS_ADDR_V4MAPPED macro, so
|
||||
// detect the ::ffff:0:0/96 prefix directly on the address words.
|
||||
if (addr->sin6_addr.s6_addr32[0] == 0 && addr->sin6_addr.s6_addr32[1] == 0 &&
|
||||
addr->sin6_addr.s6_addr32[2] == htonl(0xFFFF) &&
|
||||
esphome_inet_ntop4(&addr->sin6_addr.s6_addr32[3], buf.data(), buf.size()) != nullptr) {
|
||||
return strlen(buf.data());
|
||||
}
|
||||
#elif !defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
// Format IPv4-mapped IPv6 addresses as regular IPv4 (LWIP layout)
|
||||
if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 &&
|
||||
@@ -117,11 +149,19 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
|
||||
server->sin6_port = htons(port);
|
||||
|
||||
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
#if defined(USE_ZEPHYR)
|
||||
// Zephyr BSD sockets: use native address conversion
|
||||
if (zsock_inet_pton(AF_INET6, ip_address, &server->sin6_addr) != 1) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
// Use standard inet_pton for BSD sockets
|
||||
if (inet_pton(AF_INET6, ip_address, &server->sin6_addr) != 1) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
// Use LWIP-specific functions
|
||||
ip6_addr_t ip6;
|
||||
@@ -138,7 +178,15 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
|
||||
auto *server = reinterpret_cast<sockaddr_in *>(addr);
|
||||
memset(server, 0, sizeof(sockaddr_in));
|
||||
server->sin_family = AF_INET;
|
||||
#if defined(USE_ZEPHYR)
|
||||
// Zephyr BSD sockets: use native address conversion
|
||||
if (zsock_inet_pton(AF_INET, ip_address, &server->sin_addr) != 1) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
server->sin_addr.s_addr = inet_addr(ip_address);
|
||||
#endif
|
||||
server->sin_port = htons(port);
|
||||
return sizeof(sockaddr_in);
|
||||
}
|
||||
|
||||
@@ -60,11 +60,11 @@ inline struct lwip_sock *hook_fd_for_fast_select(int fd) {
|
||||
}
|
||||
return sock;
|
||||
}
|
||||
#elif defined(USE_HOST)
|
||||
#elif defined(USE_HOST) || defined(USE_ZEPHYR)
|
||||
/// Shared ready() helper for fd-based socket implementations.
|
||||
/// Checks if the Application's select() loop has marked this fd as ready.
|
||||
bool socket_ready_fd(int fd, bool loop_monitored);
|
||||
#endif
|
||||
#endif // USE_LWIP_FAST_SELECT
|
||||
|
||||
// Inline ready() — defined here because it depends on socket_ready/socket_ready_fd
|
||||
// declared above, while the impl headers are included before those declarations.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
socket:
|
||||
@@ -0,0 +1 @@
|
||||
socket:
|
||||
@@ -0,0 +1 @@
|
||||
socket:
|
||||
Reference in New Issue
Block a user