ESPHome 2026.4.0-dev
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 const int saved_errno = errno;
56 if (saved_errno != EINPROGRESS) {
57 ESP_LOGE(TAG, "Connect failed: %d", saved_errno);
58 close();
59 if (error_cb_)
60 error_cb_(error_arg_, this, saved_errno);
61 return false;
62 }
63
64 connecting_ = true;
65 return true;
66}
67
69 socket_.reset();
70 bool was_connected = connected_;
71 connected_ = false;
72 connecting_ = false;
73 if (was_connected && disconnect_cb_)
74 disconnect_cb_(disconnect_arg_, this);
75}
76
77size_t AsyncClient::write(const char *data, size_t len) {
78 if (!socket_ || !connected_)
79 return 0;
80
81 ssize_t sent = socket_->write(data, len);
82 if (sent < 0) {
83 const int err = errno;
84 if (err != EAGAIN && err != EWOULDBLOCK) {
85 ESP_LOGE(TAG, "Write error: %d", err);
86 close();
87 if (error_cb_)
88 error_cb_(error_arg_, this, err);
89 }
90 return 0;
91 }
92 return sent;
93}
94
96 if (!socket_)
97 return;
98
99 if (connecting_) {
100 // For connecting, we need to check writability, not readability
101 // The Application's select() only monitors read FDs, so we do our own check here
102 // For ESP platforms lwip_select() might be faster, but this code isn't used
103 // on those platforms anyway. If it was, we'd fix the Application select()
104 // to report writability instead of doing it this way.
105 int fd = socket_->get_fd();
106 if (fd < 0) {
107 ESP_LOGW(TAG, "Invalid socket fd");
108 close();
109 return;
110 }
111
112 fd_set writefds;
113 FD_ZERO(&writefds);
114 FD_SET(fd, &writefds);
115
116 struct timeval tv = {0, 0};
117 int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv);
118
119 if (ret > 0 && FD_ISSET(fd, &writefds)) {
120 int error = 0;
121 socklen_t len = sizeof(error);
122 if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) {
123 connecting_ = false;
124 connected_ = true;
125 if (connect_cb_)
126 connect_cb_(connect_arg_, this);
127 } else {
128 ESP_LOGW(TAG, "Connection failed: %d", error);
129 close();
130 if (error_cb_)
131 error_cb_(error_arg_, this, error);
132 }
133 } else if (ret < 0) {
134 const int err = errno;
135 ESP_LOGE(TAG, "Select error: %d", err);
136 close();
137 if (error_cb_)
138 error_cb_(error_arg_, this, err);
139 }
140 } else if (connected_) {
141 // For connected sockets, use the Application's select() results
142 if (!socket_->ready())
143 return;
144
145 uint8_t buf[READ_BUFFER_SIZE];
146 ssize_t len = socket_->read(buf, READ_BUFFER_SIZE);
147
148 if (len == 0) {
149 ESP_LOGI(TAG, "Connection closed by peer");
150 close();
151 } else if (len > 0) {
152 if (data_cb_)
153 data_cb_(data_arg_, this, buf, len);
154 } else {
155 const int err = errno;
156 if (err != EAGAIN && err != EWOULDBLOCK) {
157 ESP_LOGW(TAG, "Read error: %d", err);
158 close();
159 if (error_cb_)
160 error_cb_(error_arg_, this, err);
161 }
162 }
163 }
164}
165
166} // namespace esphome::async_tcp
167
168#endif
bool connect(const char *host, uint16_t port)
size_t write(const char *data, size_t len)
uint32_t socklen_t
Definition headers.h:99
__int64 ssize_t
Definition httplib.h:178
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *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:104
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:1045