ESPHome 2026.1.4
Loading...
Searching...
No Matches
async_tcp_socket.cpp
Go to the documentation of this file.
1#include "async_tcp_socket.h"
2
3#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \
4 (defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS))
5
7#include "esphome/core/log.h"
8#include <cerrno>
9#include <sys/select.h>
10
12
13static const char *const TAG = "async_tcp";
14
15// Read buffer size matches TCP MSS (1500 MTU - 40 bytes IP/TCP headers).
16// This implementation only runs on ESP-IDF and host which have ample stack.
17static constexpr size_t READ_BUFFER_SIZE = 1460;
18
19bool AsyncClient::connect(const char *host, uint16_t port) {
20 if (connected_ || connecting_) {
21 ESP_LOGW(TAG, "Already connected/connecting");
22 return false;
23 }
24
25 // Resolve address
26 struct sockaddr_storage addr;
27 socklen_t addrlen = esphome::socket::set_sockaddr((struct sockaddr *) &addr, sizeof(addr), host, port);
28 if (addrlen == 0) {
29 ESP_LOGE(TAG, "Invalid address: %s", host);
30 if (error_cb_)
31 error_cb_(error_arg_, this, -1);
32 return false;
33 }
34
35 // Create socket with loop monitoring
36 int family = ((struct sockaddr *) &addr)->sa_family;
37 socket_ = esphome::socket::socket_loop_monitored(family, SOCK_STREAM, IPPROTO_TCP);
38 if (!socket_) {
39 ESP_LOGE(TAG, "Failed to create socket");
40 if (error_cb_)
41 error_cb_(error_arg_, this, -1);
42 return false;
43 }
44
45 socket_->setblocking(false);
46
47 int err = socket_->connect((struct sockaddr *) &addr, addrlen);
48 if (err == 0) {
49 // Connection succeeded immediately (rare, but possible for localhost)
50 connected_ = true;
51 if (connect_cb_)
52 connect_cb_(connect_arg_, this);
53 return true;
54 }
55 if (errno != EINPROGRESS) {
56 ESP_LOGE(TAG, "Connect failed: %d", errno);
57 close();
58 if (error_cb_)
59 error_cb_(error_arg_, this, errno);
60 return false;
61 }
62
63 connecting_ = true;
64 return true;
65}
66
68 socket_.reset();
69 bool was_connected = connected_;
70 connected_ = false;
71 connecting_ = false;
72 if (was_connected && disconnect_cb_)
73 disconnect_cb_(disconnect_arg_, this);
74}
75
76size_t AsyncClient::write(const char *data, size_t len) {
77 if (!socket_ || !connected_)
78 return 0;
79
80 ssize_t sent = socket_->write(data, len);
81 if (sent < 0) {
82 if (errno != EAGAIN && errno != EWOULDBLOCK) {
83 ESP_LOGE(TAG, "Write error: %d", errno);
84 close();
85 if (error_cb_)
86 error_cb_(error_arg_, this, errno);
87 }
88 return 0;
89 }
90 return sent;
91}
92
94 if (!socket_)
95 return;
96
97 if (connecting_) {
98 // For connecting, we need to check writability, not readability
99 // The Application's select() only monitors read FDs, so we do our own check here
100 // For ESP platforms lwip_select() might be faster, but this code isn't used
101 // on those platforms anyway. If it was, we'd fix the Application select()
102 // to report writability instead of doing it this way.
103 int fd = socket_->get_fd();
104 if (fd < 0) {
105 ESP_LOGW(TAG, "Invalid socket fd");
106 close();
107 return;
108 }
109
110 fd_set writefds;
111 FD_ZERO(&writefds);
112 FD_SET(fd, &writefds);
113
114 struct timeval tv = {0, 0};
115 int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv);
116
117 if (ret > 0 && FD_ISSET(fd, &writefds)) {
118 int error = 0;
119 socklen_t len = sizeof(error);
120 if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) {
121 connecting_ = false;
122 connected_ = true;
123 if (connect_cb_)
124 connect_cb_(connect_arg_, this);
125 } else {
126 ESP_LOGW(TAG, "Connection failed: %d", error);
127 close();
128 if (error_cb_)
129 error_cb_(error_arg_, this, error);
130 }
131 } else if (ret < 0) {
132 ESP_LOGE(TAG, "Select error: %d", errno);
133 close();
134 if (error_cb_)
135 error_cb_(error_arg_, this, errno);
136 }
137 } else if (connected_) {
138 // For connected sockets, use the Application's select() results
139 if (!socket_->ready())
140 return;
141
142 uint8_t buf[READ_BUFFER_SIZE];
143 ssize_t len = socket_->read(buf, READ_BUFFER_SIZE);
144
145 if (len == 0) {
146 ESP_LOGI(TAG, "Connection closed by peer");
147 close();
148 } else if (len > 0) {
149 if (data_cb_)
150 data_cb_(data_arg_, this, buf, len);
151 } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
152 ESP_LOGW(TAG, "Read error: %d", errno);
153 close();
154 if (error_cb_)
155 error_cb_(error_arg_, this, errno);
156 }
157 }
158}
159
160} // namespace esphome::async_tcp
161
162#endif
bool connect(const char *host, uint16_t port)
size_t write(const char *data, size_t len)
virtual ssize_t write(const void *buf, size_t len)=0
virtual int setblocking(bool blocking)=0
virtual ssize_t read(void *buf, size_t len)=0
virtual int getsockopt(int level, int optname, void *optval, socklen_t *optlen)=0
virtual int get_fd() const
Get the underlying file descriptor (returns -1 if not supported)
Definition socket.h:67
virtual int connect(const struct sockaddr *addr, socklen_t addrlen)=0
virtual bool ready() const
Check if socket has data ready to read For loop-monitored sockets, checks with the Application's sele...
Definition socket.h:72
uint32_t socklen_t
Definition headers.h:97
__int64 ssize_t
Definition httplib.h:178
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port)
Set a sockaddr to the specified address and port for the IP version used by socket_ip().
Definition socket.cpp:110
std::unique_ptr< Socket > socket_loop_monitored(int domain, int type, int protocol)
Create a socket and monitor it for data in the main loop.
std::string size_t len
Definition helpers.h:595