Add chip temperature sensor and RGB LED control, verified live in HA
Quick end-to-end data-flow proof using only what's on the ESP32-C6- DevKitC-1 itself (no sensors on the bare devkit -- confirmed via research: BOOT button, WS2812 LED, no external sensors): - drivers::chip_temperature_* wraps ESP-IDF's built-in temperature_sensor driver (SoC die temperature). - drivers::rgb_led_* wraps the onboard WS2812 (GPIO8) via the espressif/led_strip managed component (another external registry dependency, like mdns/mqtt). - profiles::ChipTemperatureSensor is the first real implementation of the Sensor interface designed earlier, converting the driver's float reading to the fixed-point convention once at the boundary. - transport/mqtt.cpp publishes chip temperature as an HA "temperature" sensor every 30s, and exposes the LED as an HA "light" entity (rgb_command_topic) -- the first use of MQTT subscribe in this project, not just publish. Added a ChipTemperature SensorChannel (distinct from AmbientTemperature -- different meaning/range) to sensor.h. Verified live: both the chip temperature reading and LED color control work from Home Assistant against the real device. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace kvida {
|
||||
|
||||
// Starts mDNS-based MQTT broker discovery (_mqtt._tcp) and, once found,
|
||||
@@ -12,4 +14,16 @@ void start_mqtt();
|
||||
|
||||
bool mqtt_connected();
|
||||
|
||||
// Publishes (retained) Home Assistant MQTT Discovery for a diagnostic
|
||||
// "Chip temperature" sensor on first call, then the reading itself.
|
||||
// Quick end-to-end proof that a real Sensor implementation's data can
|
||||
// reach MQTT/HA -- not the eventual generic publish(topic, value) API.
|
||||
void publish_chip_temperature(float celsius);
|
||||
|
||||
// Registered by main to receive "set LED color" commands from Home
|
||||
// Assistant (an RGB light entity) without transport depending on
|
||||
// `drivers` directly.
|
||||
using LedColorHandler = void (*)(uint8_t red, uint8_t green, uint8_t blue);
|
||||
void set_led_color_handler(LedColorHandler handler);
|
||||
|
||||
} // namespace kvida
|
||||
|
||||
@@ -33,10 +33,19 @@ std::atomic<bool> g_connected{false};
|
||||
esp_mqtt_client_handle_t g_client = nullptr;
|
||||
esp_timer_handle_t g_retry_timer = nullptr;
|
||||
httpd_handle_t g_settings_httpd = nullptr;
|
||||
LedColorHandler g_led_handler = nullptr;
|
||||
|
||||
char g_availability_topic[48];
|
||||
char g_discovery_topic[80];
|
||||
char g_discovery_payload[512];
|
||||
|
||||
char g_temp_state_topic[48];
|
||||
char g_led_command_topic[48];
|
||||
char g_led_rgb_command_topic[48];
|
||||
char g_led_state_topic[48];
|
||||
char g_led_rgb_state_topic[48];
|
||||
bool g_led_discovery_published = false;
|
||||
bool g_led_on = false;
|
||||
// Remembered so a settings update can reconnect without re-running mDNS
|
||||
// discovery.
|
||||
char g_broker_uri[96]; // "mqtt://" + up to 63-char host + ":" + port + NUL
|
||||
@@ -149,18 +158,106 @@ void publish_discovery_and_birth()
|
||||
esp_mqtt_client_publish(g_client, g_availability_topic, "online", 0, 1, true);
|
||||
}
|
||||
|
||||
void mqtt_event_handler(void * /*arg*/, esp_event_base_t /*base*/, int32_t event_id, void * /*event_data*/)
|
||||
// Publishes HA MQTT Discovery for an RGB light entity (once) and
|
||||
// (re)subscribes to its command topics -- topic subscriptions don't
|
||||
// survive a client reconnect, so this runs on every MQTT_EVENT_CONNECTED,
|
||||
// not just the first.
|
||||
void publish_led_discovery_and_subscribe()
|
||||
{
|
||||
char id[16];
|
||||
device_id(id, sizeof(id));
|
||||
|
||||
snprintf(g_led_command_topic, sizeof(g_led_command_topic), "kvida/%s/led/set", id);
|
||||
snprintf(g_led_rgb_command_topic, sizeof(g_led_rgb_command_topic), "kvida/%s/led/rgb/set", id);
|
||||
snprintf(g_led_state_topic, sizeof(g_led_state_topic), "kvida/%s/led/state", id);
|
||||
snprintf(g_led_rgb_state_topic, sizeof(g_led_rgb_state_topic), "kvida/%s/led/rgb/state", id);
|
||||
|
||||
if (!g_led_discovery_published) {
|
||||
char discovery_topic[80];
|
||||
snprintf(discovery_topic, sizeof(discovery_topic), "homeassistant/light/%s/led/config", id);
|
||||
|
||||
char payload[640];
|
||||
snprintf(payload, sizeof(payload),
|
||||
"{"
|
||||
"\"name\":\"LED\","
|
||||
"\"unique_id\":\"%s_led\","
|
||||
"\"command_topic\":\"%s\","
|
||||
"\"state_topic\":\"%s\","
|
||||
"\"rgb_command_topic\":\"%s\","
|
||||
"\"rgb_state_topic\":\"%s\","
|
||||
"\"payload_on\":\"ON\","
|
||||
"\"payload_off\":\"OFF\","
|
||||
"\"availability_topic\":\"%s\","
|
||||
"\"payload_available\":\"online\","
|
||||
"\"payload_not_available\":\"offline\","
|
||||
"\"device\":{"
|
||||
"\"identifiers\":[\"%s\"],"
|
||||
"\"name\":\"%s\","
|
||||
"\"manufacturer\":\"Xylon\","
|
||||
"\"model\":\"Kvida\""
|
||||
"}"
|
||||
"}",
|
||||
id, g_led_command_topic, g_led_state_topic, g_led_rgb_command_topic, g_led_rgb_state_topic,
|
||||
g_availability_topic, id, id);
|
||||
|
||||
esp_mqtt_client_publish(g_client, discovery_topic, payload, 0, 1, true);
|
||||
g_led_discovery_published = true;
|
||||
}
|
||||
|
||||
esp_mqtt_client_subscribe(g_client, g_led_command_topic, 1);
|
||||
esp_mqtt_client_subscribe(g_client, g_led_rgb_command_topic, 1);
|
||||
}
|
||||
|
||||
void handle_led_data(const char *topic, size_t topic_len, const char *data, size_t data_len)
|
||||
{
|
||||
char payload[32] = {0};
|
||||
size_t n = data_len < sizeof(payload) - 1 ? data_len : sizeof(payload) - 1;
|
||||
memcpy(payload, data, n);
|
||||
payload[n] = '\0';
|
||||
|
||||
bool is_power = topic_len == strlen(g_led_command_topic) && memcmp(topic, g_led_command_topic, topic_len) == 0;
|
||||
bool is_rgb =
|
||||
topic_len == strlen(g_led_rgb_command_topic) && memcmp(topic, g_led_rgb_command_topic, topic_len) == 0;
|
||||
|
||||
if (is_power) {
|
||||
g_led_on = strcmp(payload, "ON") == 0;
|
||||
if (!g_led_on && g_led_handler) {
|
||||
g_led_handler(0, 0, 0);
|
||||
}
|
||||
esp_mqtt_client_publish(g_client, g_led_state_topic, g_led_on ? "ON" : "OFF", 0, 1, true);
|
||||
} else if (is_rgb) {
|
||||
// Home Assistant's classic (non-JSON) rgb_command_topic schema
|
||||
// sends "R,G,B".
|
||||
int r = 0, g = 0, b = 0;
|
||||
if (sscanf(payload, "%d,%d,%d", &r, &g, &b) == 3) {
|
||||
g_led_on = true;
|
||||
if (g_led_handler) {
|
||||
g_led_handler(static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b));
|
||||
}
|
||||
esp_mqtt_client_publish(g_client, g_led_state_topic, "ON", 0, 1, true);
|
||||
esp_mqtt_client_publish(g_client, g_led_rgb_state_topic, payload, 0, 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mqtt_event_handler(void * /*arg*/, esp_event_base_t /*base*/, int32_t event_id, void *event_data)
|
||||
{
|
||||
switch (static_cast<esp_mqtt_event_id_t>(event_id)) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT connected");
|
||||
g_connected.store(true);
|
||||
publish_discovery_and_birth();
|
||||
publish_led_discovery_and_subscribe();
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGW(TAG, "MQTT disconnected");
|
||||
g_connected.store(false);
|
||||
break;
|
||||
case MQTT_EVENT_DATA: {
|
||||
auto *event = static_cast<esp_mqtt_event_handle_t>(event_data);
|
||||
handle_led_data(event->topic, event->topic_len, event->data, event->data_len);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -411,4 +508,55 @@ bool mqtt_connected()
|
||||
return g_connected.load();
|
||||
}
|
||||
|
||||
void publish_chip_temperature(float celsius)
|
||||
{
|
||||
if (!g_client || !g_connected.load()) {
|
||||
return;
|
||||
}
|
||||
|
||||
char id[16];
|
||||
device_id(id, sizeof(id));
|
||||
snprintf(g_temp_state_topic, sizeof(g_temp_state_topic), "kvida/%s/chip_temperature", id);
|
||||
|
||||
static bool discovery_published = false;
|
||||
if (!discovery_published) {
|
||||
char discovery_topic[80];
|
||||
snprintf(discovery_topic, sizeof(discovery_topic),
|
||||
"homeassistant/sensor/%s/chip_temperature/config", id);
|
||||
|
||||
char payload[512];
|
||||
snprintf(payload, sizeof(payload),
|
||||
"{"
|
||||
"\"name\":\"Chip temperature\","
|
||||
"\"device_class\":\"temperature\","
|
||||
"\"unit_of_measurement\":\"\xc2\xb0"
|
||||
"C\","
|
||||
"\"unique_id\":\"%s_chip_temperature\","
|
||||
"\"state_topic\":\"%s\","
|
||||
"\"availability_topic\":\"%s\","
|
||||
"\"payload_available\":\"online\","
|
||||
"\"payload_not_available\":\"offline\","
|
||||
"\"device\":{"
|
||||
"\"identifiers\":[\"%s\"],"
|
||||
"\"name\":\"%s\","
|
||||
"\"manufacturer\":\"Xylon\","
|
||||
"\"model\":\"Kvida\""
|
||||
"}"
|
||||
"}",
|
||||
id, g_temp_state_topic, g_availability_topic, id, id);
|
||||
|
||||
esp_mqtt_client_publish(g_client, discovery_topic, payload, 0, 1, true);
|
||||
discovery_published = true;
|
||||
}
|
||||
|
||||
char value[16];
|
||||
snprintf(value, sizeof(value), "%.1f", static_cast<double>(celsius));
|
||||
esp_mqtt_client_publish(g_client, g_temp_state_topic, value, 0, 0, false);
|
||||
}
|
||||
|
||||
void set_led_color_handler(LedColorHandler handler)
|
||||
{
|
||||
g_led_handler = handler;
|
||||
}
|
||||
|
||||
} // namespace kvida
|
||||
|
||||
Reference in New Issue
Block a user