#include #include #include #include #include HomeAssistant::HomeAssistant(std::string addr, std::string ent) { address = addr; entity = ent; } HomeAssistant::~HomeAssistant() { disconnect(); } void HomeAssistant::event_handler(struct lws* wsi, enum lws_callback_reasons reason, char* in) { switch (reason) { case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: { log(LOG::ERR, "websocket conn error: {}", in ? in : "(null)"); return; } case LWS_CALLBACK_CLIENT_RECEIVE: { nlohmann::json json = nlohmann::json::parse(in); std::string ha_type = json["type"].get(); if (ha_type == "auth_required") { json.clear(); json["type"] = "auth"; json["access_token"] = HASS_TOKEN; tx = json.dump(); break; } if (ha_type == "auth_ok") { log(LOG::INF, "subscribing to entity \"{}\"", entity); json.clear(); json["id"] = id++; json["type"] = "subscribe_entities"; json["entity_ids"][0] = entity.c_str(); tx = json.dump(); break; } if (ha_type == "result") { if (json["success"].get() != true) log(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"][entity]["s"].get()); if(json.contains("c")) state = std::stof(json["c"][entity]["+"]["s"].get()); break; } log(LOG::WRN, "unhandled event: {}", (char*)in); break; } case LWS_CALLBACK_CLIENT_ESTABLISHED: { log(LOG::INF, "Websocket connected"); break; } case LWS_CALLBACK_CLIENT_CLOSED: { log(LOG::INF, "Websocket closed"); return; } case LWS_CALLBACK_CLIENT_WRITEABLE: { /* skip whole callback if there's no tx to send */ if (tx.size() > 0) { size_t msgLen = LWS_PRE + tx.size(); unsigned char* buf = (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(struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in, size_t) { HomeAssistant* self = static_cast(user); self->event_handler(wsi, reason, (char*)in); return 0; } int HomeAssistant::connect() { struct lws_client_connect_info i; struct lws_context_creation_info info; lws_set_log_level(LLL_ERR | LLL_WARN, lwsl_emit_syslog); memset(&i, 0, sizeof(i)); memset(&info, 0, sizeof info); static const struct lws_protocols protocols[] = { {"lws-minimal-client", callback, sizeof(void*), 0, 0, NULL, 0}, LWS_PROTOCOL_LIST_TERM}; static const struct lws_extension extensions[] = { {"permessage-deflate", lws_extension_callback_pm_deflate, "permessage-deflate" "; client_no_context_takeover" "; client_max_window_bits"}, {NULL, NULL, NULL}}; 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) { log(LOG::ERR, "error creating context"); return -1; } i.context = context; i.port = 443; i.address = 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 = NULL; i.userdata = this; i.local_protocol_name = "lws-minimal-client"; websock = lws_client_connect_via_info(&i); if (!websock) { log(LOG::ERR, "error creating lws client"); return -1; } return 0; } void HomeAssistant::service(int timeout_ms) { lws_callback_on_writable(websock); lws_service(context, timeout_ms); } void HomeAssistant::disconnect() { lws_context_destroy(context); }