HomeAssistantDiscoveryClient.cpp 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #include <HomeAssistantDiscoveryClient.h>
  2. #include <MiLightCommands.h>
  3. HomeAssistantDiscoveryClient::HomeAssistantDiscoveryClient(Settings& settings, MqttClient* mqttClient)
  4. : settings(settings)
  5. , mqttClient(mqttClient)
  6. { }
  7. void HomeAssistantDiscoveryClient::sendDiscoverableDevices(const std::map<String, BulbId>& aliases) {
  8. #ifdef MQTT_DEBUG
  9. Serial.println(F("HomeAssistantDiscoveryClient: Sending discoverable devices..."));
  10. #endif
  11. for (auto itr = aliases.begin(); itr != aliases.end(); ++itr) {
  12. addConfig(itr->first.c_str(), itr->second);
  13. }
  14. }
  15. void HomeAssistantDiscoveryClient::removeOldDevices(const std::map<uint32_t, BulbId>& aliases) {
  16. #ifdef MQTT_DEBUG
  17. Serial.println(F("HomeAssistantDiscoveryClient: Removing discoverable devices..."));
  18. #endif
  19. for (auto itr = aliases.begin(); itr != aliases.end(); ++itr) {
  20. removeConfig(itr->second);
  21. }
  22. }
  23. void HomeAssistantDiscoveryClient::removeConfig(const BulbId& bulbId) {
  24. // Remove by publishing an empty message
  25. String topic = buildTopic(bulbId);
  26. mqttClient->send(topic.c_str(), "", true);
  27. }
  28. void HomeAssistantDiscoveryClient::addConfig(const char* alias, const BulbId& bulbId) {
  29. String topic = buildTopic(bulbId);
  30. DynamicJsonDocument config(1024);
  31. config[F("schema")] = F("json");
  32. config[F("name")] = alias;
  33. config[F("command_topic")] = mqttClient->bindTopicString(settings.mqttTopicPattern, bulbId);
  34. config[F("state_topic")] = mqttClient->bindTopicString(settings.mqttStateTopicPattern, bulbId);
  35. JsonObject deviceMetadata = config.createNestedObject(F("device"));
  36. deviceMetadata[F("manufacturer")] = F("esp8266_milight_hub");
  37. deviceMetadata[F("sw_version")] = QUOTE(MILIGHT_HUB_VERSION);
  38. JsonArray identifiers = deviceMetadata.createNestedArray(F("identifiers"));
  39. identifiers.add(ESP.getChipId());
  40. bulbId.serialize(identifiers);
  41. // HomeAssistant only supports simple client availability
  42. if (settings.mqttClientStatusTopic.length() > 0 && settings.simpleMqttClientStatus) {
  43. config[F("availability_topic")] = settings.mqttClientStatusTopic;
  44. config[F("payload_available")] = F("connected");
  45. config[F("payload_not_available")] = F("disconnected");
  46. }
  47. // Configure supported commands based on the bulb type
  48. // All supported bulbs support brightness and night mode
  49. config[GroupStateFieldNames::BRIGHTNESS] = true;
  50. config[GroupStateFieldNames::EFFECT] = true;
  51. JsonArray effects = config.createNestedArray(F("effect_list"));
  52. effects.add(MiLightCommandNames::NIGHT_MODE);
  53. // These bulbs support switching between rgb/white, and have a "white_mode" command
  54. switch (bulbId.deviceType) {
  55. case REMOTE_TYPE_FUT089:
  56. case REMOTE_TYPE_RGB_CCT:
  57. case REMOTE_TYPE_RGBW:
  58. effects.add("white_mode");
  59. break;
  60. default:
  61. break; //nothing
  62. }
  63. // All bulbs except CCT have 9 modes. FUT029 and RGB/FUT096 has 9 modes, but they
  64. // are not selectable directly. There are only "next mode" commands.
  65. switch (bulbId.deviceType) {
  66. case REMOTE_TYPE_CCT:
  67. case REMOTE_TYPE_RGB:
  68. case REMOTE_TYPE_FUT020:
  69. break;
  70. default:
  71. addNumberedEffects(effects, 0, 8);
  72. break;
  73. }
  74. // These bulbs support RGB color
  75. switch (bulbId.deviceType) {
  76. case REMOTE_TYPE_FUT089:
  77. case REMOTE_TYPE_RGB:
  78. case REMOTE_TYPE_RGB_CCT:
  79. case REMOTE_TYPE_RGBW:
  80. config[F("rgb")] = true;
  81. break;
  82. default:
  83. break; //nothing
  84. }
  85. // These bulbs support adjustable white values
  86. switch (bulbId.deviceType) {
  87. case REMOTE_TYPE_CCT:
  88. case REMOTE_TYPE_FUT089:
  89. case REMOTE_TYPE_FUT091:
  90. case REMOTE_TYPE_RGB_CCT:
  91. config[GroupStateFieldNames::COLOR_TEMP] = true;
  92. break;
  93. default:
  94. break; //nothing
  95. }
  96. String message;
  97. serializeJson(config, message);
  98. #ifdef MQTT_DEBUG
  99. Serial.printf_P(PSTR("HomeAssistantDiscoveryClient: adding discoverable device: %s...\n"), alias);
  100. Serial.printf_P(PSTR(" topic: %s\nconfig: %s\n"), topic.c_str(), message.c_str());
  101. #endif
  102. mqttClient->send(topic.c_str(), message.c_str(), true);
  103. }
  104. // Topic syntax:
  105. // <discovery_prefix>/<component>/[<node_id>/]<object_id>/config
  106. //
  107. // source: https://www.home-assistant.io/docs/mqtt/discovery/
  108. String HomeAssistantDiscoveryClient::buildTopic(const BulbId& bulbId) {
  109. String topic = settings.homeAssistantDiscoveryPrefix;
  110. // Don't require the user to entier a "/" (or break things if they do)
  111. if (! topic.endsWith("/")) {
  112. topic += "/";
  113. }
  114. topic += "light/";
  115. // Use a static ID that doesn't depend on configuration.
  116. topic += "milight_hub_" + String(ESP.getChipId());
  117. // make the object ID based on the actual parameters rather than the alias.
  118. topic += "/";
  119. topic += MiLightRemoteTypeHelpers::remoteTypeToString(bulbId.deviceType);
  120. topic += "_";
  121. topic += bulbId.getHexDeviceId();
  122. topic += "_";
  123. topic += bulbId.groupId;
  124. topic += "/config";
  125. return topic;
  126. }
  127. String HomeAssistantDiscoveryClient::bindTopicVariables(const String& topic, const char* alias, const BulbId& bulbId) {
  128. String boundTopic = topic;
  129. String hexDeviceId = bulbId.getHexDeviceId();
  130. boundTopic.replace(":device_alias", alias);
  131. boundTopic.replace(":device_id", hexDeviceId);
  132. boundTopic.replace(":hex_device_id", hexDeviceId);
  133. boundTopic.replace(":dec_device_id", String(bulbId.deviceId));
  134. boundTopic.replace(":device_type", MiLightRemoteTypeHelpers::remoteTypeToString(bulbId.deviceType));
  135. boundTopic.replace(":group_id", String(bulbId.groupId));
  136. return boundTopic;
  137. }
  138. void HomeAssistantDiscoveryClient::addNumberedEffects(JsonArray& effectList, uint8_t start, uint8_t end) {
  139. for (uint8_t i = start; i <= end; ++i) {
  140. effectList.add(String(i));
  141. }
  142. }