231 lines
7.2 KiB
C++
231 lines
7.2 KiB
C++
#include "MQTTTelemetry.h"
|
|
|
|
#ifdef BOARD_ESP8266
|
|
#include <ESP8266WiFi.h>
|
|
#elif defined(BOARD_ESP32)
|
|
#include <WiFi.h>
|
|
#endif
|
|
|
|
#include <ArduinoJson.h>
|
|
|
|
#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<const char*>());
|
|
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<const char*>());
|
|
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<const char*>());
|
|
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<MQTTTelemetry>::instance()->callback(topic, payload, length);
|
|
}
|
|
|
|
STATIC_ALLOC(MQTTTelemetry);
|