164 lines
4.8 KiB
C++
164 lines
4.8 KiB
C++
#ifdef ENABLE_HOME_ASSISTANT
|
|
#include <libwebsockets.h>
|
|
|
|
#include <cstring>
|
|
#include <hass.hpp>
|
|
#include <log.hpp>
|
|
#include <nlohmann/json.hpp>
|
|
#include <string>
|
|
|
|
HomeAssistant::HomeAssistant(const Config& config) {
|
|
m_config = config;
|
|
}
|
|
|
|
HomeAssistant::~HomeAssistant() {
|
|
disconnect();
|
|
}
|
|
|
|
void HomeAssistant::event_handler(lws* wsi, const lws_callback_reasons reason, char* in) {
|
|
switch (reason) {
|
|
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: {
|
|
applog(LOG::ERR, "websocket conn error: {}", in ? in : "(null)");
|
|
return;
|
|
}
|
|
|
|
case LWS_CALLBACK_CLIENT_RECEIVE: {
|
|
nlohmann::json json = nlohmann::json::parse(in);
|
|
const std::string ha_type = json["type"].get<std::string>();
|
|
|
|
if (ha_type == "auth_required") {
|
|
json.clear();
|
|
json["type"] = "auth";
|
|
json["access_token"] = m_config.token;
|
|
tx = json.dump();
|
|
break;
|
|
}
|
|
|
|
if (ha_type == "auth_ok") {
|
|
applog(LOG::INF, "subscribing to entity \"{}\"", m_config.entity);
|
|
json.clear();
|
|
json["id"] = id++;
|
|
json["type"] = "subscribe_entities";
|
|
json["entity_ids"][0] = m_config.entity.c_str();
|
|
tx = json.dump();
|
|
break;
|
|
}
|
|
|
|
if (ha_type == "result") {
|
|
if (json["success"].get<bool>() != true)
|
|
applog(LOG::ERR, "api said success was \"false\" on message id {}", id - 1);
|
|
break;
|
|
}
|
|
|
|
if (ha_type == "event") {
|
|
json = json["event"];
|
|
if (json.contains("a"))
|
|
state = std::stof(json["a"][m_config.entity]["s"].get<std::string>());
|
|
if (json.contains("c"))
|
|
state = std::stof(json["c"][m_config.entity]["+"]["s"].get<std::string>());
|
|
break;
|
|
}
|
|
|
|
applog(LOG::WRN, "unhandled event: {}", static_cast<char*>(in));
|
|
break;
|
|
}
|
|
|
|
case LWS_CALLBACK_CLIENT_ESTABLISHED: {
|
|
applog(LOG::INF, "Websocket connected");
|
|
break;
|
|
}
|
|
|
|
case LWS_CALLBACK_CLIENT_CLOSED: {
|
|
applog(LOG::INF, "Websocket closed");
|
|
return;
|
|
}
|
|
|
|
case LWS_CALLBACK_CLIENT_WRITEABLE: {
|
|
/* skip whole callback if there's no tx to send */
|
|
if (!tx.empty()) {
|
|
const size_t msgLen = LWS_PRE + tx.size();
|
|
auto* buf = static_cast<unsigned char*>(malloc(msgLen));
|
|
if (buf) {
|
|
memcpy(&buf[LWS_PRE], tx.c_str(), tx.size());
|
|
lws_write(wsi, &buf[LWS_PRE], tx.size(), LWS_WRITE_TEXT);
|
|
tx = "";
|
|
free(buf);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int HomeAssistant::callback(lws* wsi, const lws_callback_reasons reason, void* user, void* in, size_t) {
|
|
auto* self = static_cast<HomeAssistant*>(user);
|
|
self->event_handler(wsi, reason, static_cast<char*>(in));
|
|
return 0;
|
|
}
|
|
|
|
int HomeAssistant::connect() {
|
|
lws_client_connect_info i{};
|
|
lws_context_creation_info info{};
|
|
|
|
lws_set_log_level(LLL_ERR | LLL_WARN, lwsl_emit_syslog);
|
|
|
|
std::memset(&i, 0, sizeof(i));
|
|
std::memset(&info, 0, sizeof info);
|
|
|
|
static constexpr lws_protocols protocols[] = {
|
|
{"lws-minimal-client", callback, sizeof(void*), 0, 0, nullptr, 0},
|
|
LWS_PROTOCOL_LIST_TERM};
|
|
|
|
static constexpr lws_extension extensions[] = {
|
|
{"permessage-deflate",
|
|
lws_extension_callback_pm_deflate,
|
|
"permessage-deflate"
|
|
"; client_no_context_takeover"
|
|
"; client_max_window_bits"},
|
|
{nullptr, nullptr, nullptr}};
|
|
|
|
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
|
|
info.port = CONTEXT_PORT_NO_LISTEN;
|
|
info.protocols = protocols;
|
|
info.fd_limit_per_thread = 1 + 1 + 1;
|
|
info.extensions = extensions;
|
|
|
|
context = lws_create_context(&info);
|
|
if (!context) {
|
|
applog(LOG::ERR, "error creating context");
|
|
return -1;
|
|
}
|
|
|
|
i.context = context;
|
|
i.port = 443;
|
|
i.address = m_config.address.c_str();
|
|
i.path = "/api/websocket";
|
|
i.host = i.address;
|
|
i.origin = i.address;
|
|
i.ssl_connection = LCCSCF_USE_SSL | LCCSCF_PRIORITIZE_READS;
|
|
i.protocol = nullptr;
|
|
i.userdata = this;
|
|
i.local_protocol_name = "lws-minimal-client";
|
|
|
|
websock = lws_client_connect_via_info(&i);
|
|
if (!websock) {
|
|
applog(LOG::ERR, "error creating lws client");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void HomeAssistant::service(const int timeout_ms) const {
|
|
lws_callback_on_writable(websock);
|
|
lws_service(context, timeout_ms);
|
|
}
|
|
|
|
void HomeAssistant::disconnect() const {
|
|
lws_context_destroy(context);
|
|
}
|
|
|
|
#endif
|