#include "MQTTTelemetry.h" #ifdef BOARD_ESP8266 #include #elif defined(BOARD_ESP32) #include #endif #include #include "../../Static.h" #include "../../Config.h" #include "../../Platform.h" WiFiClient wifiClient; MQTTTelemetry::MQTTTelemetry() : BufferedInputSource("MQTT"), m_mqtt(PubSubClient(wifiClient)), m_logPrinter(this) {} void MQTTTelemetry::handleEventOnline(const InputEvent& evt) { if (!m_mqtt.connected()) { Log.notice("Connecting to MQTT..."); const IPAddress server(10, 0, 0, 2); const char* deviceID = Platform::deviceID(); Log.verbose("Device ID %s", deviceID); m_mqtt.setServer(server, 1883); m_mqtt.setBufferSize(512); m_mqtt.setCallback(&MQTTTelemetry::s_callback); if (m_mqtt.connect(deviceID)) { Log.notice("Connected to MQTT"); m_needHeartbeat = true; const String deviceName = String("Renderbug ESP8266") + (char*)deviceID; const String rootTopic = String("homeassistant/light/renderbug/") + (char*)deviceID; const String configTopic = rootTopic + "/config"; Log.verbose("root topic %s", rootTopic.c_str()); Log.verbose("config topic %s", configTopic.c_str()); const String statTopic = rootTopic + "/state"; const String cmdTopic = rootTopic + "/set"; const String attrTopic = rootTopic + "/attributes"; const String logTopic = rootTopic + "/log"; const String heartbeatTopic = rootTopic + "/heartbeat"; strcpy(m_statTopic, statTopic.c_str()); strcpy(m_attrTopic, attrTopic.c_str()); strcpy(m_cmdTopic, cmdTopic.c_str()); strcpy(m_logTopic, logTopic.c_str()); strcpy(m_heartbeatTopic, heartbeatTopic.c_str()); StaticJsonDocument<1024> configJson; configJson["~"] = rootTopic; configJson["name"] = deviceName; configJson["ret"] = true; configJson["unique_id"] = (char*) deviceID; configJson["cmd_t"] = "~/set"; configJson["stat_t"] = "~/state"; configJson["json_attr_t"] = "~/attributes"; configJson["schema"] = "json"; configJson["brightness"] = true; configJson["rgb"] = true; int i = 0; for(const Sequencer::Scene& scene : m_sequencer->scenes()) { configJson["fx_list"][i++] = scene.name; } configJson["dev"]["name"] = "Renderbug"; #ifdef PLATFORM_PHOTON configJson["dev"]["mdl"] = "Photon"; #elif defined(BOARD_ESP32) configJson["dev"]["mdl"] = "ESP32"; #elif defined(BOARD_ESP8266) configJson["dev"]["mdl"] = "ESP8266"; #else configJson["dev"]["mdl"] = "Unknown"; #endif configJson["dev"]["sw"] = RENDERBUG_VERSION; configJson["dev"]["mf"] = "Phong Robotics"; configJson["dev"]["ids"][0] = (char*)deviceID; char buf[1024]; serializeJson(configJson, buf, sizeof(buf)); Log.verbose("Publish %s %s", configTopic.c_str(), buf); m_mqtt.publish(configTopic.c_str(), buf); m_mqtt.subscribe(m_cmdTopic); } else { Log.warning("Could not connect to MQTT"); } } else { if (evt.intent == InputEvent::SetPower) { StaticJsonDocument<256> doc; char buf[256]; doc["state"] = evt.asInt() ? "ON" : "OFF"; serializeJson(doc, buf, sizeof(buf)); m_mqtt.publish(m_statTopic, buf); } else if (evt.intent == InputEvent::SetBrightness) { StaticJsonDocument<256> doc; char buf[256]; doc["brightness"] = evt.asInt(); doc["state"] = "ON"; serializeJson(doc, buf, sizeof(buf)); m_mqtt.publish(m_statTopic, buf); } else if (evt.intent == InputEvent::SetColor) { StaticJsonDocument<256> doc; char buf[256]; CRGB color = evt.asRGB(); doc["color"]["r"] = color.r; doc["color"]["g"] = color.g; doc["color"]["b"] = color.b; doc["state"] = "ON"; serializeJson(doc, buf, sizeof(buf)); m_mqtt.publish(m_statTopic, buf); } else if (evt.intent == InputEvent::SetPattern) { StaticJsonDocument<256> doc; char buf[256]; doc["effect"] = evt.asString(); doc["state"] = "ON"; serializeJson(doc, buf, sizeof(buf)); m_mqtt.publish(m_statTopic, buf); } else if (evt.intent == InputEvent::FirmwareUpdate) { m_mqtt.publish("renderbug/debug/firmware", "firmware update!"); } } } void MQTTTelemetry::loop() { BufferedInputSource::loop(); OnlineTaskMixin::loop(); } void MQTTTelemetry::loopOnline() { m_mqtt.loop(); EVERY_N_SECONDS(10) { m_needHeartbeat = true; } if (m_needHeartbeat) { char buf[512]; StaticJsonDocument<512> response; response["fps"] = FastLED.getFPS(); response["RSSI"] = WiFi.RSSI(); response["localip"] = WiFi.localIP().toString(); response["free_ram"] = ESP.getFreeHeap(); response["os_version"] = ESP.getSdkVersion(); response["sketch_version"] = ESP.getSketchMD5(); serializeJson(response, buf, sizeof(buf)); m_mqtt.publish(m_attrTopic, buf); m_mqtt.publish(m_heartbeatTopic, buf); Log.notice("Heartbeat: %s", buf); response.clear(); auto sched = MainLoop::instance()->scheduler; for(auto task : sched.tasks) { response[task->name] = task->state == Task::Running; } serializeJson(response, buf, sizeof(buf)); m_mqtt.publish(m_heartbeatTopic, buf); m_needHeartbeat = false; } } void MQTTTelemetry::callback(char* topic, byte* payload, unsigned int length) { DynamicJsonDocument doc(1024); deserializeJson(doc, payload, length); if (doc.containsKey("state")) { if (doc["state"] == "ON") { setEvent(InputEvent{InputEvent::SetPower, true}); } else if (doc["state"] == "OFF") { setEvent(InputEvent{InputEvent::SetPower, false}); } } if (doc.containsKey("start")) { strcpy(m_patternBuf, doc["start"].as()); setEvent(InputEvent{InputEvent::StartThing, m_patternBuf}); } if (doc.containsKey("stop")) { if (doc["stop"] == name) { Log.notice("You can't kill an idea, or stop the MQTT Task via MQTT."); } else { strcpy(m_patternBuf, doc["stop"].as()); setEvent(InputEvent{InputEvent::StopThing, m_patternBuf}); } } if (doc.containsKey("pixelCount")) { setEvent(InputEvent{InputEvent::SetDisplayLength, (int)doc["pixelCount"]}); } if (doc.containsKey("startPixel")) { setEvent(InputEvent{InputEvent::SetDisplayOffset, (int)doc["startPixel"]}); } if (doc.containsKey("save")) { setEvent(InputEvent{InputEvent::SaveConfigurationRequest}); } if (doc.containsKey("effect")) { strcpy(m_patternBuf, doc["effect"].as()); setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf}); } if (doc.containsKey("color")) { uint8_t r = doc["color"]["r"]; uint8_t g = doc["color"]["g"]; uint8_t b = doc["color"]["b"]; setEvent(InputEvent{InputEvent::SetColor, CRGB(r, g, b)}); } if (doc.containsKey("brightness")) { setEvent(InputEvent{InputEvent::SetBrightness, (int)doc["brightness"]}); } } void MQTTTelemetry::s_callback(char* topic, byte* payload, unsigned int length) { Static::instance()->callback(topic, payload, length); } STATIC_ALLOC(MQTTTelemetry);