ESP32 Native version of Blinky, featureful controller code for WS2811/WS2812/NeoPixels
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

159 rindas
5.1KB

  1. static const char *TAG = "device";
  2. #include <esp_log.h>
  3. #include "device.hpp"
  4. #include "presets.hpp"
  5. #include "utils.hpp"
  6. Device::Device(std::string _id) :
  7. id(_id),
  8. topic_prefix("light/" + id),
  9. ready(false), should_publish(false),
  10. mutex(xSemaphoreCreateMutex()),
  11. sem(xSemaphoreCreateBinary()),
  12. power_on(false), strip_length(0),
  13. client(start_mqtt_client(on_mqtt_connect, on_mqtt_message, this))
  14. {}
  15. void Device::set_json_config(const cJSON *json) {
  16. xSemaphoreTake(mutex, portMAX_DELAY);
  17. const cJSON *state = cJSON_GetObjectItem(json, "state");
  18. if (state && cJSON_IsString(state)) {
  19. power_on = strcmp(state->valuestring, "ON") == 0;
  20. }
  21. const cJSON *fx = cJSON_GetObjectItem(json, "effect");
  22. if (fx && cJSON_IsString(fx)) {
  23. effect = fx->valuestring;
  24. }
  25. const cJSON *length = cJSON_GetObjectItem(json, "length");
  26. if (length && cJSON_IsNumber(length)) {
  27. strip_length = length->valuedouble;
  28. }
  29. publish_state_locked();
  30. xSemaphoreGive(mutex);
  31. }
  32. void Device::on_mqtt_connect(esp_mqtt_client_handle_t client) {
  33. xSemaphoreTake(mutex, portMAX_DELAY);
  34. ESP_LOGI(TAG, "Connected to MQTT");
  35. std::string topic = topic_prefix + "/#";
  36. if (esp_mqtt_client_subscribe(client, topic.c_str(), 0) < 0) {
  37. ESP_LOGE(TAG, "Failed to subscribe to %s", topic.c_str());
  38. }
  39. // TODO: Re-announce when presets change
  40. cJSON *json = cJSON_CreateObject();
  41. cJSON_AddStringToObject(json, "unique_id", id.c_str());
  42. cJSON *device = cJSON_AddObjectToObject(json, "device");
  43. cJSON_AddStringToObject(device, "name", id.c_str());
  44. cJSON *identifiers = cJSON_AddArrayToObject(device, "identifiers");
  45. cJSON_AddItemToArray(identifiers, cJSON_CreateString(id.c_str()));
  46. cJSON_AddStringToObject(json, "state_topic", (topic_prefix + "/state").c_str());
  47. cJSON_AddStringToObject(json, "command_topic", (topic_prefix + "/cmd").c_str());
  48. cJSON_AddStringToObject(json, "schema", "json");
  49. cJSON_AddBoolToObject(json, "effect", true);
  50. cJSON *effects = cJSON_AddArrayToObject(json, "effect_list");
  51. for (auto iter = Presets::map_begin(); iter != Presets::map_end(); ++iter)
  52. cJSON_AddItemToArray(effects, cJSON_CreateString(iter->first.c_str()));
  53. char *config = cJSON_PrintUnformatted(json);
  54. if (0 > esp_mqtt_client_publish(
  55. client,
  56. ("homeassistant/light/" + id + "/config").c_str(),
  57. config, 0,
  58. 0, 0
  59. )) {
  60. ESP_LOGE(TAG, "Failed to publish discovery message");
  61. }
  62. cJSON_free(config);
  63. cJSON_Delete(json);
  64. ready = true;
  65. if (should_publish) publish_state_locked();
  66. xSemaphoreGive(mutex);
  67. }
  68. std::string Device::subtopic(const std::string &topic) {
  69. auto slash_pos = topic.find('/', topic_prefix.length());
  70. if (slash_pos == std::string::npos) return "";
  71. auto another_slash_pos = topic.find('/', slash_pos + 1);
  72. if (slash_pos == std::string::npos) {
  73. // No further levels
  74. return topic.substr(slash_pos + 1);
  75. }
  76. // First subtopic
  77. return topic.substr(slash_pos + 1, another_slash_pos - (slash_pos + 1));
  78. }
  79. void Device::on_mqtt_message(esp_mqtt_client_handle_t, esp_mqtt_event_handle_t event) {
  80. std::string topic(event->topic, event->topic_len);
  81. std::string command = subtopic(topic);
  82. ESP_LOGI(TAG, "Received command via MQTT: %s", command.c_str());
  83. if (command == "state") {
  84. // This is from us, just ignore it
  85. return;
  86. } else if (command == "cmd") {
  87. cJSON *json = cJSON_ParseWithLength(event->data, event->data_len);
  88. if (json) {
  89. set_json_config(json);
  90. cJSON_Delete(json);
  91. } else {
  92. ESP_LOGE(TAG, "Invalid JSON data");
  93. }
  94. // Fall through & kick semaphore
  95. } else if (command == "reboot") {
  96. ESP_LOGI(TAG, "Rebooting immediately");
  97. esp_restart();
  98. return;
  99. } else if (command == "data") {
  100. size_t slash = topic.rfind('/');
  101. if (slash != std::string::npos) {
  102. write_file(
  103. ("/spiffs/" + topic.substr(slash + 1) + ".json").c_str(),
  104. event->data, event->data_len
  105. );
  106. }
  107. // Fall through & kick semaphore
  108. } else {
  109. ESP_LOGE(TAG, "Unhandled command: %s (%d bytes)", command.c_str(), event->data_len);
  110. return;
  111. }
  112. xSemaphoreGive(sem);
  113. }
  114. cJSON* Device::make_json_config_locked() const {
  115. cJSON *json = cJSON_CreateObject();
  116. cJSON_AddStringToObject(json, "state", power_on ? "ON" : "OFF");
  117. cJSON_AddStringToObject(json, "effect", effect.c_str());
  118. cJSON_AddNumberToObject(json, "length", strip_length);
  119. return json;
  120. }
  121. void Device::publish_state_locked() {
  122. if (!(should_publish = !ready)) {
  123. cJSON *json = make_json_config_locked();
  124. char *config = cJSON_PrintUnformatted(json);
  125. ESP_ERROR_CHECK(esp_mqtt_client_publish(
  126. client, (topic_prefix + "/state").c_str(), config, 0, 0, 0
  127. ));
  128. cJSON_free(config);
  129. cJSON_Delete(json);
  130. }
  131. }