|
- static const char *TAG = "device";
- #include <esp_log.h>
-
- #include "device.hpp"
- #include "presets.hpp"
- #include "utils.hpp"
-
-
- Device::Device(std::string _id) :
- id(_id),
- topic_prefix("light/" + id),
- ready(false),
- mutex(xSemaphoreCreateMutex()),
- sem(xSemaphoreCreateBinary()),
- power_on(false), strip_length(0), brightness(255),
- client(start_mqtt_client(on_mqtt_connect, on_mqtt_message, this))
- {}
-
- void Device::set_json_config(const cJSON *json) {
- xSemaphoreTake(mutex, portMAX_DELAY);
-
- const cJSON *state = cJSON_GetObjectItem(json, "state");
- if (state && cJSON_IsString(state)) {
- power_on = strcmp(state->valuestring, "ON") == 0;
- }
- const cJSON *fx = cJSON_GetObjectItem(json, "effect");
- if (fx && cJSON_IsString(fx)) {
- effect = fx->valuestring;
- }
- const cJSON *length = cJSON_GetObjectItem(json, "length");
- if (length && cJSON_IsNumber(length)) {
- strip_length = length->valueint;
- }
- const cJSON *jbrightness = cJSON_GetObjectItem(json, "brightness");
- if (jbrightness && cJSON_IsNumber(jbrightness)) {
- brightness = jbrightness->valueint;
- }
-
- publish_state_locked();
-
- xSemaphoreGive(mutex);
- }
-
- // TODO: Re-announce when presets change
- void Device::advertise_locked() {
- if (ready) {
- ESP_LOGI(TAG, "Publishing HA discovery message");
-
- cJSON *json = cJSON_CreateObject();
- cJSON_AddStringToObject(json, "unique_id", id.c_str());
- cJSON *device = cJSON_AddObjectToObject(json, "device");
- cJSON_AddStringToObject(device, "name", id.c_str());
- cJSON *identifiers = cJSON_AddArrayToObject(device, "identifiers");
- cJSON_AddItemToArray(identifiers, cJSON_CreateString(id.c_str()));
- cJSON_AddStringToObject(json, "state_topic", (topic_prefix + "/state").c_str());
- cJSON_AddStringToObject(json, "command_topic", (topic_prefix + "/cmd").c_str());
- cJSON_AddStringToObject(json, "schema", "json");
- cJSON_AddBoolToObject(json, "effect", true);
- cJSON *effects = cJSON_AddArrayToObject(json, "effect_list");
- for (auto iter = Presets::map_begin(); iter != Presets::map_end(); ++iter)
- cJSON_AddItemToArray(effects, cJSON_CreateString(iter->first.c_str()));
- cJSON_AddBoolToObject(json, "brightness", true);
- cJSON_AddStringToObject(json, "brightness_command_topic", (topic_prefix + "/cmd").c_str());
- cJSON_AddStringToObject(json, "brightness_state_topic", (topic_prefix + "/state").c_str());
- cJSON_AddStringToObject(json, "brightness_template", "{{ value_json.brightness }}");
- cJSON_AddStringToObject(json, "brightness_value_template", "{\"brightness\": {{ value }} }");
-
- char *config = cJSON_PrintUnformatted(json);
-
- if (0 > esp_mqtt_client_publish(
- client,
- ("homeassistant/light/" + id + "/config").c_str(),
- config, 0,
- 0, 0
- )) {
- ESP_LOGE(TAG, "Failed to publish discovery message");
- }
-
- cJSON_free(config);
- cJSON_Delete(json);
- }
- }
-
- void Device::advertise() {
- xSemaphoreTake(mutex, portMAX_DELAY);
- advertise_locked();
- xSemaphoreGive(mutex);
- }
-
- void Device::on_mqtt_connect() {
- xSemaphoreTake(mutex, portMAX_DELAY);
-
- ESP_LOGI(TAG, "Connected to MQTT");
-
- ready = true;
-
- // Subscribe to relevant topics
- std::string topic = topic_prefix + "/#";
- if (esp_mqtt_client_subscribe(client, topic.c_str(), 0) < 0) {
- ESP_LOGE(TAG, "Failed to subscribe to %s", topic.c_str());
- }
-
- if (esp_mqtt_client_subscribe(client, "homeassistant/status", 0) < 0) {
- ESP_LOGE(TAG, "Failed to subscribe to %s", "homeassistant/status");
- }
-
- // Advertise on initial connection
- advertise_locked();
-
- // Let everyone know our state
- publish_state_locked();
-
- xSemaphoreGive(mutex);
- }
-
-
- static bool parse_topic(
- const std::string &topic,
- const std::string &prefix,
- std::string &subtopic
- ) {
- // Fail if this doesn't start with the prefix
- if (topic.rfind(prefix, 0) != 0) return false;
-
- // Blank subtopic if the topic is the prefix
- if (topic.length() == prefix.length()) {
- subtopic = "";
- return true;
- }
-
- // Fail if there's no separation as expected
- if (topic[prefix.length()] != '/') return false;
-
- // Find the following subtopic, slash or no
- const auto prefix_len = prefix.length() + 1;
- const auto slash_pos = topic.find('/', prefix_len);
- size_t len = (
- slash_pos == std::string::npos ?
- std::string::npos :
- slash_pos - prefix_len
- );
-
- subtopic = topic.substr(prefix_len, len);
- return true;
- }
-
-
- void Device::on_mqtt_message(esp_mqtt_event_handle_t event) {
- std::string topic(event->topic, event->topic_len);
- std::string command;
-
- ESP_LOGI(TAG, "Received command via MQTT: %s", topic.c_str());
-
- if (parse_topic(topic, "homeassistant", command)) {
- if (command == "status") {
- std::string status(event->data, event->data_len);
- if (status == "online") {
- advertise();
- }
- return;
- }
-
- } else if (parse_topic(topic, topic_prefix, command)) {
- if (command == "state") {
- // This is from us, just ignore it
- return;
-
- } else if (command == "cmd") {
- cJSON *json = cJSON_ParseWithLength(event->data, event->data_len);
- if (json) {
- set_json_config(json);
- cJSON_Delete(json);
- xSemaphoreGive(sem);
- } else {
- ESP_LOGE(TAG, "Invalid JSON data");
- }
- return;
-
- } else if (command == "reboot") {
- ESP_LOGI(TAG, "Rebooting immediately");
- esp_restart();
- return;
-
- } else if (command == "data") {
- size_t slash = topic.rfind('/');
- if (slash != std::string::npos) {
- write_file(
- ("/spiffs/" + topic.substr(slash + 1) + ".json").c_str(),
- event->data, event->data_len
- );
- }
- xSemaphoreGive(sem);
- return;
- }
- }
-
- ESP_LOGE(TAG, "Unhandled command: %s (%d bytes)", command.c_str(), event->data_len);
- }
-
- cJSON* Device::make_json_config_locked() const {
- cJSON *json = cJSON_CreateObject();
- cJSON_AddStringToObject(json, "state", power_on ? "ON" : "OFF");
- cJSON_AddStringToObject(json, "effect", effect.c_str());
- cJSON_AddNumberToObject(json, "length", strip_length);
- cJSON_AddNumberToObject(json, "brightness", brightness);
- return json;
- }
-
- void Device::publish_state_locked() {
- if (ready) {
- cJSON *json = make_json_config_locked();
- char *config = cJSON_PrintUnformatted(json);
- ESP_ERROR_CHECK(esp_mqtt_client_publish(
- client, (topic_prefix + "/state").c_str(), config, 0, 0, 0
- ));
- cJSON_free(config);
- cJSON_Delete(json);
- }
- }
|