renderbug-cpp/src/platform/arduino/MQTTTelemetry.cpp

375 lines
12 KiB
C++

#include "MQTTTelemetry.h"
#include <ArduinoJson.h>
#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<ConfigService>::instance()->coordMap()->physicalPixelCount();
m_json["loadedProfile"] = Static<ConfigService>::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<const char*>());
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<const char*>());
setEvent(InputEvent{InputEvent::StopThing, m_patternBuf});
}
}
if (m_json.containsKey("pixelCount")) {
Log.notice("Pixel count changed");
setEvent(InputEvent{InputEvent::SetDisplayLength, m_json["pixelCount"].as<int>()});
}
if (m_json.containsKey("startPixel")) {
Log.notice("Start pixel changed");
setEvent(InputEvent{InputEvent::SetDisplayOffset, m_json["startPixel"].as<int>()});
}
if (m_json.containsKey("loadConfig")) {
Log.notice("Loading new config");
setEvent(InputEvent{InputEvent::LoadConfigurationByName, m_json["loadConfig"].as<const char*>()});
}
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<const char*>());
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<int>()});
}
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<MQTTTelemetry>::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);