#include "MQTTTelemetry.h" #include #include "../../Static.h" #include "../../Config.h" #include "../../Platform.h" struct MQTTDevice { const String id; const String name; const String model; const String softwareVersion; const String manufacturer; const String availabilityTopic; void toJson(const JsonObject& json) const { json["name"] = name; json["mdl"] = model; json["sw"] = softwareVersion; json["mf"] = manufacturer; json["ids"][0] = id; } }; const String availTopic = String("renderbug/") + Platform::deviceID() + "/availability"; const MQTTDevice Device{ Platform::deviceID(), Platform::deviceName(), Platform::model(), #ifdef BOARD_ESP8266 ESP.getSketchMD5(), #else "", #endif "Phong Robotics", availTopic }; struct MQTTEntity { const MQTTDevice& device; String name; String entityId; String rootTopic; MQTTEntity(const String& domain, const MQTTDevice& device, const String& name) : device(device), name(Platform::deviceName() + " " + name) { entityId = String(device.id) + "-" + name; rootTopic = String("homeassistant/") + domain + String("/renderbug/") + entityId; } String configTopic() const { return rootTopic + "/config"; } String commandTopic() const { return rootTopic + "/set"; } String heartbeatTopic() const { return String("renderbug/") + Device.id + "/heartbeat"; } String statTopic() const { return rootTopic + "/state"; } bool isCommandTopic(const char* topic) const { if (strncmp(topic, rootTopic.c_str(), rootTopic.length()) == 0) { return strncmp(&topic[rootTopic.length()], "/set", sizeof("/set")) == 0; } return false; } void toJson(JsonDocument& jsonBuf, bool isInteractive = true) const { jsonBuf["~"] = rootTopic.c_str(); jsonBuf["name"] = name; jsonBuf["unique_id"] = entityId; if (isInteractive) { jsonBuf["cmd_t"] = "~/set"; jsonBuf["ret"] = true; jsonBuf["schema"] = "json"; } else { } jsonBuf["stat_t"] = "~/state"; jsonBuf["json_attr_t"] = heartbeatTopic(); jsonBuf["avty_t"] = device.availabilityTopic; device.toJson(jsonBuf.createNestedObject("dev")); } }; const MQTTEntity Lightswitch { "light", Device, "lightswitch" }; const MQTTEntity flashlightSwitch { "switch", Device, "flashlight" }; const MQTTEntity FPSSensor { "sensor", Device, "fps" }; MQTTTelemetry::MQTTTelemetry() : BufferedInputSource("MQTT"), m_mqtt(m_wifi), m_logPrinter(this) { m_debugTopic = String("renderbug/") + Platform::deviceID(); } void MQTTTelemetry::handleEventOnline(const InputEvent& evt) { if (!m_mqtt.connected()) { Log.notice("Connecting to MQTT as %s on %s...", Platform::deviceID(), Device.availabilityTopic.c_str()); if (m_mqtt.connect(Platform::deviceID(), NULL, NULL, Device.availabilityTopic.c_str(), 0, true, "offline")) { Log.notice("Connected to MQTT"); m_needHeartbeat = true; m_json.clear(); Lightswitch.toJson(m_json); int i = 0; for(const Sequencer::Scene& scene : m_sequencer->scenes()) { m_json["fx_list"][i++] = scene.name; } m_json["brightness"] = true; m_json["rgb"] = true; publishDoc(Lightswitch.configTopic().c_str(), true); //Log.verbose("Publish %s %s", Lightswitch.configTopic().c_str(), buf); //m_mqtt.publish(Lightswitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true); m_mqtt.subscribe(Lightswitch.commandTopic().c_str()); m_json.clear(); flashlightSwitch.toJson(m_json, false); m_json["cmd_t"] = "~/set"; m_json["ret"] = true; publishDoc(flashlightSwitch.configTopic().c_str(), true); //m_mqtt.publish(flashlightSwitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true); m_mqtt.subscribe(flashlightSwitch.commandTopic().c_str()); m_json.clear(); FPSSensor.toJson(m_json, false); m_json["unit_of_meas"] = "Frames/s"; publishDoc(FPSSensor.configTopic().c_str(), true); //Log.verbose("Publish %s %s", FPSSensor.configTopic().c_str(), buf); //m_mqtt.publish(FPSSensor.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true); m_mqtt.subscribe(FPSSensor.commandTopic().c_str()); #ifdef BOARD_ESP8266 struct rst_info resetInfo = *ESP.getResetInfoPtr(); if (resetInfo.reason != 0) { char buff[200]; sprintf(&buff[0], "Fatal exception:%d flag:%d (%s) epc1:0x%08x epc2:0x%08x epc3:0x%08x excvaddr:0x%08x depc:0x%08x", resetInfo.exccause, resetInfo.reason, (resetInfo.reason == 0 ? "DEFAULT" : resetInfo.reason == 1 ? "WDT" : resetInfo.reason == 2 ? "EXCEPTION" : resetInfo.reason == 3 ? "SOFT_WDT" : resetInfo.reason == 4 ? "SOFT_RESTART" : resetInfo.reason == 5 ? "DEEP_SLEEP_AWAKE" : resetInfo.reason == 6 ? "EXT_SYS_RST" : "???"), resetInfo.epc1, resetInfo.epc2, resetInfo.epc3, resetInfo.excvaddr, resetInfo.depc); Log.warning("Previous crash detected! %s", buff); } #endif } else { Log.warning("Could not connect to MQTT"); } } else { String statTopic = Lightswitch.statTopic(); if (evt.intent == InputEvent::StopThing && String(evt.asString()) == "Flashlight") { String flashlightStatTopic = flashlightSwitch.statTopic(); m_mqtt.publish(flashlightStatTopic.c_str(), "OFF"); } else if (evt.intent == InputEvent::StartThing && String(evt.asString()) == "Flashlight") { String flashlightStatTopic = flashlightSwitch.statTopic(); m_mqtt.publish(flashlightStatTopic.c_str(), "ON"); } else if (evt.intent == InputEvent::SetPower) { m_json.clear(); m_isOn = evt.asInt() ? true : false; m_json["state"] = m_isOn ? "ON" : "OFF"; publishDoc(statTopic.c_str()); } else if (evt.intent == InputEvent::SetBrightness) { m_json.clear(); m_json["brightness"] = evt.asInt(); m_json["state"] = m_isOn ? "ON" : "OFF"; publishDoc(statTopic.c_str()); } else if (evt.intent == InputEvent::SetColor) { CRGB color = evt.asRGB(); m_json.clear(); m_json["color"]["r"] = color.r; m_json["color"]["g"] = color.g; m_json["color"]["b"] = color.b; m_json["state"] = m_isOn ? "ON" : "OFF"; publishDoc(statTopic.c_str()); } else if (evt.intent == InputEvent::SetPattern) { m_json.clear(); m_json["effect"] = evt.asString(); m_json["state"] = m_isOn ? "ON" : "OFF"; publishDoc(statTopic.c_str()); } } } void MQTTTelemetry::loop() { BufferedInputSource::loop(); OnlineTaskMixin::loop(); } void MQTTTelemetry::onOnline() { const IPAddress server(10, 0, 0, 2); m_needHeartbeat = true; m_mqtt.setServer(server, 1883); m_mqtt.setBufferSize(1024); m_mqtt.setCallback(&MQTTTelemetry::s_callback); } void MQTTTelemetry::onOffline() { m_mqtt.disconnect(); } void MQTTTelemetry::loopOnline() { m_mqtt.loop(); EVERY_N_SECONDS(10) { m_needHeartbeat = true; } if (m_needHeartbeat) { m_json.clear(); m_json["device_id"] = Platform::deviceID(); m_json["sketch_version"] = ESP.getSketchMD5(); m_json["os_version"] = ESP.getSdkVersion(); m_json["localip"] = WiFi.localIP().toString(); m_json["pixelCount"] = Static::instance()->coordMap()->physicalPixelCount(); m_json["loadedProfile"] = Static::instance()->loadedProfile(); m_json["RSSI"] = WiFi.RSSI(); m_json["free_ram"] = ESP.getFreeHeap(); m_json["fps"] = FastLED.getFPS(); String availTopic = m_rootTopic + "/available"; publishDoc(Lightswitch.heartbeatTopic().c_str()); m_mqtt.publish(Device.availabilityTopic.c_str(), "online"); String fpsCounter = String(FastLED.getFPS()); m_mqtt.publish(FPSSensor.statTopic().c_str(), fpsCounter.c_str()); m_needHeartbeat = false; } } void MQTTTelemetry::callback(char* topic, const char* payload) { setEvent(InputEvent::NetworkActivity); if (flashlightSwitch.isCommandTopic(topic)) { if (!strncmp((char*)payload, "ON", sizeof("ON"))) { Log.notice("Turning on flashlight"); setEvent(InputEvent{InputEvent::SetPower, true}); setEvent(InputEvent{InputEvent::SetPattern, "Flashlight"}); setEvent(InputEvent{InputEvent::SetBrightness, 255}); } else if (!strncmp((char*)payload, "OFF", sizeof("OFF"))) { Log.notice("Turning off flashlight"); setEvent(InputEvent{InputEvent::SetPattern, "Idle"}); } } else if (Lightswitch.isCommandTopic(topic)) { deserializeJson(m_json, payload); if (m_json.containsKey("state")) { if (m_json["state"] == "ON") { Log.notice("Turning on power"); setEvent(InputEvent{InputEvent::SetPower, true}); } else if (m_json["state"] == "OFF") { Log.notice("Turning off power"); setEvent(InputEvent{InputEvent::SetPattern, "Idle"}); setEvent(InputEvent{InputEvent::SetPower, false}); } } if (m_json.containsKey("start")) { strcpy(m_patternBuf, m_json["start"].as()); setEvent(InputEvent{InputEvent::StartThing, m_patternBuf}); } if (m_json.containsKey("stop")) { if (m_json["stop"] == name) { Log.warning("You can't kill an idea, or stop the MQTT Task via MQTT."); } else { strcpy(m_patternBuf, m_json["stop"].as()); setEvent(InputEvent{InputEvent::StopThing, m_patternBuf}); } } if (m_json.containsKey("pixelCount")) { Log.notice("Pixel count changed"); setEvent(InputEvent{InputEvent::SetDisplayLength, m_json["pixelCount"].as()}); } if (m_json.containsKey("startPixel")) { Log.notice("Start pixel changed"); setEvent(InputEvent{InputEvent::SetDisplayOffset, m_json["startPixel"].as()}); } if (m_json.containsKey("loadConfig")) { Log.notice("Loading new config"); setEvent(InputEvent{InputEvent::LoadConfigurationByName, m_json["loadConfig"].as()}); } if (m_json.containsKey("save")) { setEvent(InputEvent{InputEvent::SaveConfigurationRequest}); } if (m_json.containsKey("restart")) { Platform::restart(); } if (m_json.containsKey("reconnect")) { m_mqtt.disconnect(); } if (m_json.containsKey("ping")) { m_needHeartbeat = true; Log.notice("Queuing up heartbeat"); } if (m_json.containsKey("effect")) { strcpy(m_patternBuf, m_json["effect"].as()); setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf}); } if (m_json.containsKey("color")) { uint8_t r = m_json["color"]["r"]; uint8_t g = m_json["color"]["g"]; uint8_t b = m_json["color"]["b"]; setEvent(InputEvent{InputEvent::SetColor, CRGB(r, g, b)}); } if (m_json.containsKey("brightness")) { setEvent(InputEvent{InputEvent::SetBrightness, m_json["brightness"].as()}); } Log.notice("Event done."); } } void MQTTTelemetry::s_callback(char* topic, byte* payload, unsigned int length) { strcpy(s_topicBuf, topic); memcpy(s_payloadBuf, payload, length); s_payloadBuf[std::min(sizeof(s_payloadBuf) - 1, length)] = 0; Static::instance()->callback(s_topicBuf, s_payloadBuf); } char MQTTTelemetry::s_topicBuf[128] = {0}; char MQTTTelemetry::s_payloadBuf[512] = {0}; void MQTTTelemetry::publishDoc(const char* topic, bool retain) { m_mqtt.beginPublish(topic, measureJson(m_json), retain); serializeJson(m_json, m_mqtt); m_mqtt.endPublish(); } void MQTTTelemetry::publishDoc(const char* topic) { publishDoc(topic, false); } STATIC_ALLOC(MQTTTelemetry); STATIC_TASK(MQTTTelemetry);