#include "MQTT/MQTT.h" class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin { public: MQTTTelemetry() : BufferedInputSource("MQTT"), m_client("relay.malloc.hackerbots.net", 1883, 512, 15, MQTTTelemetry::s_callback, true) { strcpy(m_deviceName, System.deviceID().c_str()); } void loop() override { BufferedInputSource::loop(); OnlineTaskMixin::loop(); } void handleEventOnline(const InputEvent& event) override { char response[255]; if (event.intent == InputEvent::SetPower) { JSONBufferWriter writer(response, sizeof(response)); writer.beginObject(); writer.name("state").value(event.asInt() == 0 ? "OFF" : "ON"); writer.endObject(); writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; m_client.publish(m_statTopic, response, MQTT::QOS1); } else if (event.intent == InputEvent::SetBrightness) { JSONBufferWriter writer(response, sizeof(response)); writer.beginObject(); writer.name("brightness").value(event.asInt()); writer.endObject(); writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; m_client.publish(m_statTopic, response, MQTT::QOS1); } else if (event.intent == InputEvent::SetColor) { JSONBufferWriter writer(response, sizeof(response)); writer.beginObject(); writer.name("color").beginObject(); CRGB rgb = event.asRGB(); writer.name("r").value(rgb.r); writer.name("g").value(rgb.g); writer.name("b").value(rgb.b); writer.endObject(); writer.endObject(); writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; m_client.publish(m_statTopic, response, MQTT::QOS1); } else if (event.intent == InputEvent::SetPattern) { JSONBufferWriter writer(response, sizeof(response)); writer.beginObject(); writer.name("effect").value(event.asString()); writer.endObject(); writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; m_client.publish(m_statTopic, response, MQTT::QOS1); } else { if (m_lastIntent != event.intent) { m_lastIntent = event.intent; JSONBufferWriter writer(response, sizeof(response)); writer.beginObject(); writer.name("intent").value(event.intent); writer.endObject(); writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; m_client.publish("renderbug/events", response, MQTT::QOS1); } } } void loopOnline() override { if (m_client.isConnected()) { m_client.loop(); EVERY_N_SECONDS(10) { char heartbeatBuf[255]; JSONBufferWriter writer(heartbeatBuf, sizeof(heartbeatBuf)); writer.beginObject(); writer.name("fps").value(FastLED.getFPS()); writer.name("os_version").value(System.version()); writer.name("free_ram").value((unsigned int)System.freeMemory()); //writer.name("uptime").value(System.uptime()); /*WiFiSignal sig = WiFi.RSSI(); writer.name("strength").value(sig.getStrength()); writer.name("quality").value(sig.getQuality());*/ writer.name("RSSI").value(WiFi.RSSI()); writer.name("SSID").value(WiFi.SSID()); //writer.name("MAC").value(WiFi.macAddress()); writer.name("localip").value(WiFi.localIP().toString()); writer.name("startPixel").value(Static::instance()->coordMap()->startPixel); writer.name("pixelCount").value(Static::instance()->coordMap()->pixelCount); writer.endObject(); writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; m_client.publish(m_attrTopic, heartbeatBuf); } } else { m_client.connect("renderbug_" + String(m_deviceName) + "_" + String(Time.now())); char response[512]; JSONBufferWriter writer(response, sizeof(response)); String devTopic = String("homeassistant/light/renderbug/") + m_deviceName; writer.beginObject(); writer.name("~").value(devTopic.c_str()); writer.name("name").value("Renderbug Photon"); writer.name("unique_id").value(m_deviceName); writer.name("cmd_t").value("~/set"); writer.name("stat_t").value("~/state"); writer.name("json_attr_t").value("~/attributes"); writer.name("schema").value("json"); writer.name("brightness").value(true); writer.name("rgb").value(true); writer.name("ret").value(true); writer.name("dev").beginObject(); writer.name("name").value("Renderbug"); writer.name("mdl").value("Renderbug"); writer.name("sw").value(RENDERBUG_VERSION); writer.name("mf").value("Phong Robotics"); writer.name("ids").beginArray(); writer.value(m_deviceName); writer.endArray(); writer.endObject(); writer.name("fx_list").beginArray(); for(const Sequencer::Scene& scene : sequencer.scenes()) { writer.value(scene.name); } writer.endArray(); writer.endObject(); writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; String configTopic = devTopic + "/config"; m_client.publish(configTopic, response, true); String statTopic = devTopic + "/state"; String cmdTopic = devTopic + "/set"; String attrTopic = devTopic + "/attributes"; strcpy(m_statTopic, statTopic.c_str()); strcpy(m_attrTopic, attrTopic.c_str()); strcpy(m_cmdTopic, cmdTopic.c_str()); m_client.subscribe(m_cmdTopic, MQTT::QOS1); } } private: void callback(char* topic, byte* payload, unsigned int length) { MainLoop::instance()->dispatch(InputEvent::NetworkActivity); if (!strcmp(topic, m_cmdTopic)) { JSONValue cmd = JSONValue::parseCopy((char*)payload, length); JSONObjectIterator cmdIter(cmd); while(cmdIter.next()) { if (cmdIter.name() == "state" && cmdIter.value().toString() == "ON") { setEvent({InputEvent::SetPower, 1}); } else if (cmdIter.name() == "state" && cmdIter.value().toString() == "OFF") { setEvent({InputEvent::SetPower, 0}); } if (cmdIter.name() == "color") { JSONObjectIterator colorIter(cmdIter.value()); uint8_t r, g, b; while(colorIter.next()) { if (colorIter.name() == "r") { r = colorIter.value().toInt(); } else if (colorIter.name() == "g") { g = colorIter.value().toInt(); } else if (colorIter.name() == "b") { b = colorIter.value().toInt(); } } setEvent(InputEvent{InputEvent::SetColor, CRGB{r, g, b}}); } if (cmdIter.name() == "brightness") { uint8_t brightness = cmdIter.value().toInt(); setEvent(InputEvent{InputEvent::SetBrightness, brightness}); } if (cmdIter.name() == "effect") { strcpy(m_patternBuf, cmdIter.value().toString().c_str()); setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf}); } if (cmdIter.name() == "pixelCount") { setEvent(InputEvent{InputEvent::SetDisplayLength, cmdIter.value().toInt()}); } if (cmdIter.name() == "startPixel") { setEvent(InputEvent{InputEvent::SetDisplayOffset, cmdIter.value().toInt()}); } if (cmdIter.name() == "save") { setEvent(InputEvent{InputEvent::SaveConfigurationRequest}); } } } } static void s_callback(char* topic, byte* payload, unsigned int length) { Static::instance()->callback(topic, payload, length); }; MQTT m_client; InputEvent::Intent m_lastIntent; char m_deviceName[100]; char m_statTopic[100]; char m_attrTopic[100]; char m_cmdTopic[100]; char m_patternBuf[48]; }; STATIC_ALLOC(MQTTTelemetry);