homeassistant websocket api

This commit is contained in:
Drath
2025-09-11 14:20:58 +02:00
parent f1d61b45a6
commit 3153b1dc1b
5 changed files with 8682 additions and 1 deletions

View File

@@ -2,7 +2,7 @@ MAKEFLAGS := --jobs=16
# MAKEFLAGS += --output-sync=target
# compiling with C++26
CXXFLAGS := -std=c++2c -Ofast -fdiagnostics-color=always -Iinclude
CXXFLAGS := -std=c++2c -Ofast -fdiagnostics-color=always -Iinclude -lwebsockets
CXXFLAGS += -MMD -MP
SOURCES := main.cpp $(wildcard src/*.cpp)

8331
include/ArduinoJson.hpp Normal file

File diff suppressed because it is too large Load Diff

20
include/hass.hpp Normal file
View File

@@ -0,0 +1,20 @@
#ifndef HASS_HPP
#define HASS_HPP
#include <libwebsockets.h>
#include <ArduinoJson.hpp>
#include <iostream>
#include <print>
#include <string>
#define HASS_ADDRESS "home.drath.win"
#define HASS_TOKEN "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI0MzUxMTZiY2VjNTg0MTI3OGI4YmU0NTY4ZGQ5MzIyYiIsImlhdCI6MTc1NzQzMDI0MCwiZXhwIjoyMDcyNzkwMjQwfQ.6wKTlxEA0FLWfv-qW35vewUSvTxdVrZWqFwHT5Khu6E"
#define HASS_ENTITY "sensor.gniazdko_komputer_moc"
int hass_connect();
void hass_poll();
float hass_get();
void hass_disconnect();
#endif

153
src/hass.cpp Normal file
View File

@@ -0,0 +1,153 @@
#include <hass.hpp>
std::string tx;
struct lws_context *context = NULL;
lws *websock = NULL;
float state;
uint16_t id = 2;
static int callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
static const struct lws_protocols protocols[] = {
{"lws-minimal-client", callback, 0, 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}};
static int callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
ArduinoJson::JsonDocument json;
switch (reason) {
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: {
std::println("[HomeAssistant] websocket conn error: {}", in ? (char *)in : "(null)");
return -1;
}
case LWS_CALLBACK_CLIENT_RECEIVE: {
ArduinoJson::deserializeJson(json, (char *)in);
std::string ha_type = json["type"].as<std::string>();
// std::println("[HomeAssistant] got event type: {} ", ha_type);
if (ha_type == "auth_required") {
json.clear();
json["type"] = "auth";
json["access_token"] = HASS_TOKEN;
ArduinoJson::serializeJson(json, tx);
break;
}
if (ha_type == "auth_ok") {
std::println("[HomeAssistant] subscribing to entity \"{}\"", HASS_ENTITY);
json.clear();
json["id"] = id++;
json["type"] = "subscribe_entities";
json["entity_ids"][0] = HASS_ENTITY;
ArduinoJson::serializeJson(json, tx);
break;
}
if (ha_type == "result") {
if (json["success"].as<std::string>() == "false")
std::println("[HomeAssistant] api said success was \"false\" on message id {}", id - 1);
break;
}
if (ha_type == "event") {
state = json["event"]["c"][HASS_ENTITY]["+"]["s"].as<float>();
break;
}
std::println("[HomeAssistant] unhandled event: {}", (char *)in);
break;
}
case LWS_CALLBACK_CLIENT_ESTABLISHED: {
std::println("[HomeAssistant] Websocket connected");
break;
}
case LWS_CALLBACK_CLIENT_CLOSED: {
std::println("[HomeAssistant] Websocket closed");
return -1;
}
case LWS_CALLBACK_CLIENT_WRITEABLE: {
/* skip whole callback if there's no tx to send */
if (tx.size() > 0) {
// std::println("[HomeAssistant] sending {}", tx);
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());
if (!lws_write(wsi, &buf[LWS_PRE], tx.size(), LWS_WRITE_TEXT))
;
tx = "";
free(buf);
}
}
break;
}
default:
break;
}
return 0;
}
int hass_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);
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) {
std::println("[HomeAssistant] error creating context");
return -1;
}
i.context = context;
i.port = 443;
i.address = HASS_ADDRESS;
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.local_protocol_name = "lws-minimal-client";
websock = lws_client_connect_via_info(&i);
if (!websock) {
std::println("[HomeAssistant] error creating lws client");
return -1;
}
return 0;
}
void hass_poll() {
lws_service(context, 0);
lws_callback_on_writable(websock);
}
float hass_get() {
return state;
}
void hass_disconnect() {
lws_context_destroy(context);
}

177
tests/wstest.cpp Normal file
View File

@@ -0,0 +1,177 @@
#include <libwebsockets.h>
#include <ArduinoJson.hpp>
#include <csignal>
#include <print>
#include <string>
#define HASS_ADDRESS "home.drath.win"
#define HASS_TOKEN "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI0MzUxMTZiY2VjNTg0MTI3OGI4YmU0NTY4ZGQ5MzIyYiIsImlhdCI6MTc1NzQzMDI0MCwiZXhwIjoyMDcyNzkwMjQwfQ.6wKTlxEA0FLWfv-qW35vewUSvTxdVrZWqFwHT5Khu6E"
static bool bExit;
std::string tx;
struct lws_context *context = NULL;
lws *websock = NULL;
uint16_t id = 2;
static int callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
static const struct lws_protocols protocols[] = {
{"lws-minimal-client", callback, 0, 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}};
static int callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
ArduinoJson::JsonDocument json;
std::string entity = "sensor.listwa_moc";
switch (reason) {
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: {
std::println("[HomeAssistant] websocket conn error: {}", in ? (char *)in : "(null)");
return -1;
}
case LWS_CALLBACK_CLIENT_RECEIVE: {
ArduinoJson::deserializeJson(json, (char *)in);
std::string ha_type = json["type"].as<std::string>();
// std::println("[HomeAssistant] got event type: {} ", ha_type);
if (ha_type == "auth_required") {
json.clear();
json["type"] = "auth";
json["access_token"] = HASS_TOKEN;
ArduinoJson::serializeJson(json, tx);
break;
}
if (ha_type == "auth_ok") {
std::println("[HomeAssistant] subscribing to entity \"{}\"", entity);
json.clear();
json["id"] = id++;
json["type"] = "subscribe_entities";
json["entity_ids"][0] = entity;
ArduinoJson::serializeJson(json, tx);
break;
}
if (ha_type == "result") {
if (json["success"].as<std::string>() == "false")
std::println("[HomeAssistant] api said success was \"false\" on message id {}", id - 1);
break;
}
if (ha_type == "event") {
float state = json["event"]["c"][entity]["+"]["s"].as<float>();
std::println("{}", state);
break;
}
std::println("[HomeAssistant] unhandled event: {}", (char *)in);
break;
}
case LWS_CALLBACK_CLIENT_ESTABLISHED: {
std::println("[HomeAssistant] Websocket connected");
break;
}
case LWS_CALLBACK_CLIENT_CLOSED: {
std::println("[HomeAssistant] Websocket closed");
return -1;
}
case LWS_CALLBACK_CLIENT_WRITEABLE: {
/* skip whole callback if there's no tx to send */
if (tx.size() > 0) {
// std::println("[HomeAssistant] sending {}", tx);
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());
if(!lws_write(wsi, &buf[LWS_PRE], tx.size(), LWS_WRITE_TEXT));
tx = "";
free(buf);
}
}
break;
}
default:
break;
}
return 0;
}
static void handle_sigint(int sig) {
bExit = true;
}
int hass_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);
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) {
std::println("[HomeAssistant] error creating context");
return -1;
}
i.context = context;
i.port = 443;
i.address = HASS_ADDRESS;
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.local_protocol_name = "lws-minimal-client";
websock = lws_client_connect_via_info(&i);
if (!websock) {
std::println("[HomeAssistant] error creating lws client");
return -1;
}
return 0;
}
void hass_poll() {
lws_service(context, 0);
lws_callback_on_writable(websock);
}
void hass_disconnect() {
lws_context_destroy(context);
}
int main(int argc, const char **argv) {
signal(SIGINT, handle_sigint);
hass_connect();
while (!bExit)
hass_poll();
hass_disconnect();
return 0;
}