[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:
Ardumine
2026-06-26 14:30:07 +01:00
committed by GitHub
parent f9f28a6a00
commit 75cdabee3d
9 changed files with 98 additions and 7 deletions
+6
View File
@@ -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_);
}
+27 -1
View File
@@ -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
+6
View File
@@ -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
+50 -2
View File
@@ -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);
}
+2 -2
View File
@@ -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: