3
3
#include < boost/asio.hpp>
4
4
#include " api.pb.h"
5
5
#include " entity_conversion.hpp"
6
+ #include " executor.hpp"
6
7
#include " make_unexpected_result.hpp"
8
+ #include " net.hpp"
7
9
#include " plain_text_protocol.hpp"
8
10
9
11
namespace asio = boost::asio;
@@ -23,25 +25,51 @@ namespace cppesphomeapi
23
25
ApiConnection::ApiConnection (std::string hostname,
24
26
std::uint16_t port,
25
27
std::string password,
28
+ std::stop_source &stop_source,
26
29
const asio::any_io_executor &executor)
27
30
: hostname_{std::move (hostname)}
28
31
, port_{port}
29
32
, password_{std::move (password)}
30
- , strand_{asio::make_strand (executor)}
31
- , socket_{strand_}
32
- {}
33
+ , socket_{executor}
34
+ {
35
+ executor::addStopService (executor::getContext (executor), stop_source);
36
+ }
37
+
38
+ void ApiConnection::cancel ()
39
+ {
40
+ executor::stopAssetOf (executor::getContext (socket_.get_executor ())).request_stop ();
41
+ }
33
42
34
43
AsyncResult<void > ApiConnection::connect ()
35
44
{
36
45
auto executor = co_await this_coro::executor;
37
- tcp::resolver resolver{executor};
38
- const auto resolved = co_await resolver.async_resolve (hostname_, std::to_string (port_));
46
+ net::Timer timer{executor};
47
+ timer.expires_after (std::chrono::milliseconds{500 });
48
+
49
+ auto endpoints = co_await net::resolveHostEndpoints (hostname_, net::Port{port_}, timer);
50
+ if (not endpoints.has_value ())
51
+ {
52
+ co_return make_unexpected_result (
53
+ ApiErrorCode::UnexpectedMessage,
54
+ std::format (
55
+ " Could not resolve host {}:{}. Failed with error: {}" , hostname_, port_, endpoints.error ().message ()));
56
+ }
57
+
58
+ timer.expires_after (std::chrono::milliseconds{500 });
59
+ auto connect_result = co_await net::connectTo (*endpoints, timer);
60
+ if (not connect_result.has_value ())
61
+ {
62
+ co_return make_unexpected_result (ApiErrorCode::UnexpectedMessage,
63
+ std::format (" Could not connect to host {}:{}. Failed with error: {}" ,
64
+ hostname_,
65
+ port_,
66
+ endpoints.error ().message ()));
67
+ }
39
68
40
- co_await socket_.async_connect (resolved->endpoint ());
41
- socket_.set_option (asio::socket_base::keep_alive{true });
69
+ socket_ = std::move (*connect_result);
42
70
43
- asio::co_spawn (strand_, std::bind ( &ApiConnection::async_receive , this ), asio::detached );
44
- spawn_heartbeat ( );
71
+ executor::commission (socket_. get_executor (), &ApiConnection::receive_loop , this );
72
+ executor::commission (socket_. get_executor (), &ApiConnection::heartbeat_loop, this );
45
73
46
74
REQUIRE_SUCCESS (co_await send_message_hello ());
47
75
REQUIRE_SUCCESS (co_await send_message_connect ());
@@ -165,14 +193,25 @@ AsyncResult<void> ApiConnection::send_message(const google::protobuf::Message &m
165
193
if (packet.has_value ())
166
194
{
167
195
std::println (" Sending {}" , message.GetTypeName ());
168
- const auto written = co_await socket_.async_write_some (asio::buffer (packet.value ()));
169
- if (written != packet->size ())
196
+ auto executor = co_await this_coro::executor;
197
+ net::Timer timer{executor};
198
+ const auto watch_dog = executor::abort (socket_, timer);
199
+ timer.expires_after (std::chrono::milliseconds{500 });
200
+
201
+ const auto written = co_await net::sendTo (socket_, timer, packet.value ());
202
+ if (written.has_value () && written.value () != packet->size ())
170
203
{
171
204
co_return make_unexpected_result (
172
205
ApiErrorCode::SendError,
173
206
std::format (" Could not send message. Bytes written are different: expected={}, written={}" ,
174
207
packet->size (),
175
- written));
208
+ *written));
209
+ }
210
+ else if (not written.has_value ())
211
+ {
212
+ co_return make_unexpected_result (
213
+ ApiErrorCode::SendError,
214
+ std::format (" Could not send message in time. Error: {}" , written.error ().message ()));
176
215
}
177
216
co_return Result<void >{};
178
217
}
@@ -207,15 +246,23 @@ AsyncResult<LogEntry> ApiConnection::receive_log()
207
246
};
208
247
}
209
248
210
- boost::asio::awaitable<void > ApiConnection::async_receive ()
249
+ boost::asio::awaitable<void > ApiConnection::receive_loop ()
211
250
{
212
- namespace asio = boost::asio;
213
- std::array<std::uint8_t , 4096 > buffer{};
214
- bool do_receive{true };
251
+ std::array<std::byte, 4096 > buffer{};
252
+ auto executor = co_await this_coro::executor;
253
+ net::Timer timer{executor};
254
+ const auto watch_dog = executor::abort (socket_, timer);
215
255
256
+ bool do_receive{true };
216
257
while (do_receive)
217
258
{
218
- const auto received_bytes = co_await socket_.async_receive (asio::buffer (buffer));
259
+ timer.expires_after (std::chrono::seconds{100 }); // at least every 90secs. a message should be received
260
+ const auto received_bytes = co_await net::receiveFrom (socket_, timer, buffer);
261
+ if (not received_bytes.has_value ())
262
+ {
263
+ std::println (" Could not receive bytes. Error {}" , received_bytes.error ().message ());
264
+ break ;
265
+ }
219
266
auto result = PlainTextProtocol{}
220
267
.decode_multiple <proto::SubscribeLogsResponse,
221
268
proto::DeviceInfoResponse,
@@ -247,7 +294,7 @@ boost::asio::awaitable<void> ApiConnection::async_receive()
247
294
proto::ListEntitiesTimeResponse,
248
295
proto::ListEntitiesUpdateResponse,
249
296
proto::ListEntitiesValveResponse>(
250
- std::span{buffer.begin (), received_bytes}, [this ](auto &&message) {
297
+ std::span{buffer.cbegin (), received_bytes. value () }, [this ](auto &&message) {
251
298
std::vector<boost::asio::any_completion_handler<void (MessageWrapper)>> handlers;
252
299
{
253
300
std::unique_lock l{handler_mtx_};
@@ -276,11 +323,6 @@ boost::asio::awaitable<void> ApiConnection::async_receive()
276
323
std::println (" RECEIVE ENDED!" );
277
324
}
278
325
279
- void ApiConnection::spawn_heartbeat ()
280
- {
281
- asio::co_spawn (strand_, std::bind (&ApiConnection::heartbeat_loop, this ), asio::detached);
282
- }
283
-
284
326
boost::asio::awaitable<void > ApiConnection::heartbeat_loop ()
285
327
{
286
328
auto executor = co_await this_coro::executor;
@@ -296,6 +338,7 @@ boost::asio::awaitable<void> ApiConnection::heartbeat_loop()
296
338
// otherwise the connection is dead.
297
339
co_await receive_message<proto::PingResponse>(asio::use_awaitable);
298
340
}
341
+ executor::stopAssetOf (executor).request_stop ();
299
342
}
300
343
301
344
} // namespace cppesphomeapi
0 commit comments