Skip to content

Commit c7d78d3

Browse files
committed
more api WIP
1 parent 199232b commit c7d78d3

File tree

12 files changed

+409
-27
lines changed

12 files changed

+409
-27
lines changed

cppesphomeapi/include/cppesphomeapi/api_client.hpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
#include <memory>
55
#include <string>
66
#include <cppesphomeapi/cppesphomeapi_export.hpp>
7+
#include "api_version.hpp"
78
#include "async_result.hpp"
9+
#include "commands.hpp"
10+
#include "device_info.hpp"
11+
#include "entity.hpp"
812

913
namespace cppesphomeapi
1014
{
@@ -13,10 +17,19 @@ class ApiConnection;
1317
class CPPESPHOMEAPI_EXPORT ApiClient
1418
{
1519
public:
16-
explicit ApiClient(const boost::asio::any_io_executor &executor, std::string hostname, std::uint16_t port = 6053, std::string password = "");
20+
explicit ApiClient(const boost::asio::any_io_executor &executor,
21+
std::string hostname,
22+
std::uint16_t port = 6053,
23+
std::string password = "");
1724
~ApiClient();
1825

19-
AsyncResult<void> connect();
26+
[[nodiscard]] std::optional<ApiVersion> api_version() const;
27+
[[nodiscard]] const std::string &device_name() const;
28+
AsyncResult<void> async_connect();
29+
AsyncResult<void> async_disconnect();
30+
AsyncResult<DeviceInfo> async_device_info();
31+
AsyncResult<std::vector<EntityInfo>> async_list_entities_services();
32+
AsyncResult<void> async_light_command(LightCommand light_command);
2033

2134
ApiClient(const ApiClient &) = delete;
2235
ApiClient(ApiClient &&) = delete;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#ifndef CPPESPHOMEAPI_API_VERSION_HPP
2+
#define CPPESPHOMEAPI_API_VERSION_HPP
3+
#include <cstdint>
4+
5+
namespace cppesphomeapi
6+
{
7+
struct ApiVersion
8+
{
9+
std::uint32_t major{};
10+
std::uint32_t minor{};
11+
};
12+
} // namespace cppesphomeapi
13+
14+
#endif
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#ifndef CPPESPHOMEAPI_COMMANDS_HPP
2+
#define CPPESPHOMEAPI_COMMANDS_HPP
3+
4+
#include "commands/light_command.hpp"
5+
6+
#endif
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef CPPESPHOMEAPI_LIGHT_COMMAND_HPP
2+
#define CPPESPHOMEAPI_LIGHT_COMMAND_HPP
3+
#include <cstdint>
4+
#include <optional>
5+
#include <string>
6+
7+
namespace cppesphomeapi
8+
{
9+
struct LightCommand
10+
{
11+
std::uint32_t key{};
12+
std::optional<std::string> effect;
13+
};
14+
} // namespace cppesphomeapi
15+
16+
#endif
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#ifndef CPPESPHOMEAPI_DEVICE_INFO_HPP
2+
#define CPPESPHOMEAPI_DEVICE_INFO_HPP
3+
#include <string>
4+
#include <cstdint>
5+
6+
namespace cppesphomeapi
7+
{
8+
struct DeviceInfo
9+
{
10+
bool uses_password{};
11+
bool has_deep_sleep{};
12+
std::string name;
13+
std::string friendly_name;
14+
std::string mac_address;
15+
std::string compilation_time;
16+
std::string model;
17+
std::string manufacturer;
18+
std::string esphome_version;
19+
std::uint16_t webserver_port;
20+
std::string suggested_area;
21+
};
22+
} // namespace cppesphomeapi
23+
#endif
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#ifndef CPPESPHOMEAPI_ENTITY_HPP
2+
#define CPPESPHOMEAPI_ENTITY_HPP
3+
#include <cstdint>
4+
#include <string>
5+
6+
namespace cppesphomeapi
7+
{
8+
struct EntityInfo
9+
{};
10+
11+
struct UserService
12+
{};
13+
} // namespace cppesphomeapi
14+
#endif

cppesphomeapi/include/cppesphomeapi/result.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ enum ApiErrorCode
1111
SerializeError,
1212
ParseError,
1313
UnexpectedMessage,
14-
SendError
14+
SendError,
15+
AuthentificationError
1516
};
1617

1718
struct ApiError

cppesphomeapi/src/api_client.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,39 @@ ApiClient::ApiClient(const boost::asio::any_io_executor &executor,
1111

1212
ApiClient::~ApiClient() = default;
1313

14-
AsyncResult<void> ApiClient::connect()
14+
AsyncResult<void> ApiClient::async_connect()
1515
{
1616
co_return co_await connection_->connect();
1717
}
18+
19+
AsyncResult<void> ApiClient::async_disconnect()
20+
{
21+
co_return co_await connection_->disconnect();
22+
}
23+
24+
AsyncResult<DeviceInfo> ApiClient::async_device_info()
25+
{
26+
co_return co_await connection_->request_device_info();
27+
}
28+
29+
AsyncResult<std::vector<EntityInfo>> ApiClient::async_list_entities_services()
30+
{
31+
co_return co_await connection_->request_entities_and_services();
32+
}
33+
34+
AsyncResult<void> ApiClient::async_light_command(LightCommand light_command)
35+
{
36+
co_return co_await connection_->light_command(std::move(light_command));
37+
}
38+
39+
std::optional<ApiVersion> ApiClient::api_version() const
40+
{
41+
return connection_->api_version();
42+
}
43+
44+
const std::string &ApiClient::device_name() const
45+
{
46+
return connection_->device_name();
47+
}
48+
1849
} // namespace cppesphomeapi

cppesphomeapi/src/api_connection.cpp

Lines changed: 123 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
#include "api_connection.hpp"
2-
#include <print>
32
#include "api.pb.h"
43
#include "make_unexpected_result.hpp"
54

5+
#include <print>
6+
67
namespace asio = boost::asio;
78
namespace this_coro = asio::this_coro;
89

10+
#define REQUIRE_SUCCESS(async_operation) \
11+
{ \
12+
auto &&result = async_operation; \
13+
if (not result.has_value()) \
14+
{ \
15+
co_return std::unexpected(result.error()); \
16+
} \
17+
}
18+
919
namespace cppesphomeapi
1020
{
1121
ApiConnection::ApiConnection(std::string hostname,
@@ -28,28 +38,116 @@ AsyncResult<void> ApiConnection::connect()
2838
co_await socket_.async_connect(resolved->endpoint());
2939
socket_.set_option(asio::socket_base::keep_alive{true});
3040

31-
auto send_result = co_await send_message_hello();
41+
REQUIRE_SUCCESS(co_await send_message_hello());
42+
REQUIRE_SUCCESS(co_await send_message_connect());
3243

33-
co_return send_result;
44+
co_return Result<void>{};
45+
}
46+
47+
AsyncResult<void> ApiConnection::disconnect()
48+
{
49+
proto::DisconnectRequest request;
50+
REQUIRE_SUCCESS(co_await send_message(request));
51+
REQUIRE_SUCCESS(co_await receive_message<proto::DisconnectResponse>());
52+
co_return Result<void>{};
3453
}
3554

3655
AsyncResult<void> ApiConnection::send_message_hello()
3756
{
38-
cppesphomeapi::proto::HelloRequest hello_request;
39-
40-
hello_request.ParseFromArray(nullptr, 0);
41-
hello_request.set_client_info(std::string{"cppapi"});
42-
co_await send_message(hello_request);
43-
const auto msg_promise = co_await receive_message<cppesphomeapi::proto::HelloResponse>();
44-
45-
std::println("Got esphome device \"{}\": Version {}.{}",
46-
msg_promise->name(),
47-
msg_promise->api_version_major(),
48-
msg_promise->api_version_minor());
49-
// todo: do something with the message.
57+
proto::HelloRequest request;
58+
request.set_client_info(std::string{"cppapi"});
59+
REQUIRE_SUCCESS(co_await send_message(request));
60+
const auto response = co_await receive_message<proto::HelloResponse>();
61+
REQUIRE_SUCCESS(response);
62+
device_name_ = response->name();
63+
api_version_ = ApiVersion{.major = response->api_version_major(), .minor = response->api_version_minor()};
5064
co_return Result<void>{};
5165
}
5266

67+
AsyncResult<void> ApiConnection::send_message_connect()
68+
{
69+
proto::ConnectRequest request;
70+
request.set_password(password_);
71+
REQUIRE_SUCCESS(co_await send_message(request));
72+
const auto response = co_await receive_message<proto::ConnectResponse>();
73+
if (response->invalid_password())
74+
{
75+
co_return make_unexpected_result(ApiErrorCode::AuthentificationError, "Invalid password");
76+
}
77+
co_return Result<void>{};
78+
}
79+
80+
AsyncResult<DeviceInfo> ApiConnection::request_device_info()
81+
{
82+
proto::DeviceInfoRequest device_request{};
83+
REQUIRE_SUCCESS(co_await send_message(device_request));
84+
const auto response = co_await receive_message<proto::DeviceInfoResponse>();
85+
REQUIRE_SUCCESS(response);
86+
87+
co_return DeviceInfo{
88+
.uses_password = response->uses_password(),
89+
.has_deep_sleep = response->has_deep_sleep(),
90+
.name = response->name(),
91+
.friendly_name = response->friendly_name(),
92+
.mac_address = response->mac_address(),
93+
.compilation_time = response->compilation_time(), // todo: maybe parse directly into std::chrono?
94+
.model = response->model(),
95+
.manufacturer = response->manufacturer(),
96+
.esphome_version = response->esphome_version(),
97+
.webserver_port = static_cast<uint16_t>(response->webserver_port()),
98+
.suggested_area = response->suggested_area(),
99+
};
100+
}
101+
102+
AsyncResult<std::vector<EntityInfo>> ApiConnection::request_entities_and_services()
103+
{
104+
proto::ListEntitiesRequest request;
105+
REQUIRE_SUCCESS(co_await send_message(request));
106+
const auto messages = co_await receive_messages<proto::ListEntitiesDoneResponse,
107+
proto::ListEntitiesAlarmControlPanelResponse,
108+
proto::ListEntitiesBinarySensorResponse,
109+
proto::ListEntitiesButtonResponse,
110+
proto::ListEntitiesCameraResponse,
111+
proto::ListEntitiesClimateResponse,
112+
proto::ListEntitiesCoverResponse,
113+
proto::ListEntitiesDateResponse,
114+
proto::ListEntitiesDateTimeResponse,
115+
proto::ListEntitiesEventResponse,
116+
proto::ListEntitiesFanResponse,
117+
proto::ListEntitiesLightResponse,
118+
proto::ListEntitiesLockResponse,
119+
proto::ListEntitiesMediaPlayerResponse,
120+
proto::ListEntitiesNumberResponse,
121+
proto::ListEntitiesSelectResponse,
122+
proto::ListEntitiesSensorResponse,
123+
proto::ListEntitiesServicesResponse,
124+
proto::ListEntitiesSwitchResponse,
125+
proto::ListEntitiesTextResponse,
126+
proto::ListEntitiesTextSensorResponse,
127+
proto::ListEntitiesTimeResponse,
128+
proto::ListEntitiesUpdateResponse,
129+
proto::ListEntitiesValveResponse>();
130+
REQUIRE_SUCCESS(messages);
131+
132+
for (auto &&msg : messages.value())
133+
{
134+
std::println("GOT LIST .{}", std::visit([](auto &&msg) { return msg.key(); }, msg));
135+
}
136+
co_return std::vector<EntityInfo>{};
137+
}
138+
139+
AsyncResult<void> ApiConnection::light_command(LightCommand light_command)
140+
{
141+
proto::LightCommandRequest request{};
142+
request.set_key(light_command.key);
143+
144+
if (light_command.effect.has_value())
145+
{
146+
request.set_effect(std::move(light_command.effect.value()));
147+
}
148+
co_return co_await send_message(request);
149+
}
150+
53151
AsyncResult<void> ApiConnection::send_message(const google::protobuf::Message &message)
54152
{
55153
const auto packet = plain_text_serialize(message);
@@ -68,4 +166,14 @@ AsyncResult<void> ApiConnection::send_message(const google::protobuf::Message &m
68166
}
69167
co_return std::unexpected(packet.error());
70168
}
169+
170+
const std::optional<ApiVersion> &ApiConnection::api_version() const
171+
{
172+
return api_version_;
173+
}
174+
175+
const std::string &ApiConnection::device_name() const
176+
{
177+
return device_name_;
178+
}
71179
} // namespace cppesphomeapi

0 commit comments

Comments
 (0)