port to platformio
This commit is contained in:
163
src/platform/arduino/MQTTTelemetry.h
Normal file
163
src/platform/arduino/MQTTTelemetry.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#include <Input.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <ArduinoLog.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ArduinoUniqueID.h>
|
||||
|
||||
WiFiClient wifi;
|
||||
PubSubClient m_mqtt(wifi);
|
||||
|
||||
class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
|
||||
public:
|
||||
MQTTTelemetry() : BufferedInputSource("MQTT") {}
|
||||
|
||||
void handleEventOnline(const InputEvent& evt) override {
|
||||
if (!m_mqtt.connected()) {
|
||||
Log.notice("Connecting to MQTT...");
|
||||
const IPAddress server(10, 0, 0, 2);
|
||||
uint64_t chipid = ESP.getEfuseMac();
|
||||
char deviceID[15];
|
||||
snprintf(deviceID, 15, "%08X", (uint32_t)chipid);
|
||||
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");
|
||||
|
||||
const String deviceName = String("Renderbug ESP32 ") + (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";
|
||||
strcpy(m_statTopic, statTopic.c_str());
|
||||
strcpy(m_attrTopic, attrTopic.c_str());
|
||||
strcpy(m_cmdTopic, cmdTopic.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;
|
||||
|
||||
configJson["dev"]["name"] = "Renderbug";
|
||||
#ifdef PLATFORM_PHOTON
|
||||
configJson["dev"]["mdl"] = "Photon";
|
||||
#else
|
||||
configJson["dev"]["mdl"] = "ESP32";
|
||||
#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();
|
||||
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;
|
||||
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();
|
||||
serializeJson(doc, buf, sizeof(buf));
|
||||
m_mqtt.publish(m_statTopic, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
BufferedInputSource::loop();
|
||||
OnlineTaskMixin::loop();
|
||||
}
|
||||
|
||||
void loopOnline() override {
|
||||
m_mqtt.loop();
|
||||
EVERY_N_SECONDS(10) {
|
||||
char buf[254];
|
||||
StaticJsonDocument<200> 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);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
char m_statTopic[100];
|
||||
char m_attrTopic[100];
|
||||
char m_cmdTopic[100];
|
||||
|
||||
void 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("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"]});
|
||||
}
|
||||
}
|
||||
|
||||
static void s_callback(char* topic, byte* payload, unsigned int length) {
|
||||
Static<MQTTTelemetry>::instance()->callback(topic, payload, length);
|
||||
}
|
||||
|
||||
char m_patternBuf[48];
|
||||
};
|
||||
|
||||
STATIC_ALLOC(MQTTTelemetry);
|
35
src/platform/particle/MDNSService.cpp
Normal file
35
src/platform/particle/MDNSService.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "MDNS/MDNS.h"
|
||||
#include "Figments/Figments.h"
|
||||
#include "Figments/MainLoop.h"
|
||||
#include "Figments/Input.h"
|
||||
#include "colors.h"
|
||||
|
||||
class MDNSService : public Task {
|
||||
private:
|
||||
mdns::MDNS m_mdns;
|
||||
bool m_online = false;
|
||||
|
||||
public:
|
||||
MDNSService() : Task("MDNS") {
|
||||
}
|
||||
|
||||
void handleEvent(const InputEvent &evt) {
|
||||
if (evt.intent == InputEvent::NetworkStatus) {
|
||||
m_mdns.setHostname("renderbug");
|
||||
m_mdns.addService("tcp", "http", 80, "Renderbug");
|
||||
m_mdns.begin(true);
|
||||
m_online = true;
|
||||
}
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
if (m_online) {
|
||||
// Returns true when it reads at least one byte
|
||||
if (m_mdns.processQueries()) {
|
||||
MainLoop::instance()->dispatch(InputEvent::NetworkActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
STATIC_ALLOC(MDNSService);
|
205
src/platform/particle/MQTTTelemetry.cpp
Normal file
205
src/platform/particle/MQTTTelemetry.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
#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<ConfigService>::instance()->coordMap()->startPixel);
|
||||
writer.name("pixelCount").value(Static<ConfigService>::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<MQTTTelemetry>::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);
|
||||
|
165
src/platform/particle/PhotonTelemetry.cpp
Normal file
165
src/platform/particle/PhotonTelemetry.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "PhotonTelemetry.h"
|
||||
#include "../../Static.h"
|
||||
|
||||
using namespace NSFastLED;
|
||||
|
||||
PhotonTelemetry::PhotonTelemetry() : Task("PhotonTelemetry") {}
|
||||
|
||||
void
|
||||
PhotonTelemetry::onConnected()
|
||||
{
|
||||
Log.info("Connecting photon telemetry...");
|
||||
Particle.variable("frame", m_frameIdx);
|
||||
Particle.variable("brightness", m_currentBrightness);
|
||||
Particle.variable("fps", m_fps);
|
||||
Particle.variable("services", m_serviceList);
|
||||
Particle.variable("localip", m_localIP);
|
||||
m_online = true;
|
||||
}
|
||||
|
||||
void
|
||||
PhotonTelemetry::loop()
|
||||
{
|
||||
m_frameIdx++;
|
||||
if (m_rgbPulseFrame == 1) {
|
||||
m_ledStatus.setActive(false);
|
||||
} else if (m_rgbPulseFrame > 0) {
|
||||
m_rgbPulseFrame--;
|
||||
}
|
||||
|
||||
m_currentBrightness = NSFastLED::FastLED.getBrightness();
|
||||
NSFastLED::FastLED.countFPS();
|
||||
m_fps = NSFastLED::FastLED.getFPS();
|
||||
|
||||
if (m_online) {
|
||||
EVERY_N_SECONDS(30) {
|
||||
m_localIP = WiFi.localIP().toString();
|
||||
char valueBuf[255];
|
||||
snprintf(valueBuf, sizeof(valueBuf), "{\"fps\": %lu, \"localip\": \"%s\"}", m_fps, m_localIP.c_str());
|
||||
Log.info("Heartbeat: %s", valueBuf);
|
||||
Particle.publish("renderbug/heartbeat", valueBuf);
|
||||
auto sched = MainLoop::instance()->scheduler;
|
||||
m_serviceList = String{};
|
||||
for(auto task : sched.tasks) {
|
||||
m_serviceList.concat(task->name);
|
||||
m_serviceList.concat(':');
|
||||
if (task->state == Task::Running) {
|
||||
m_serviceList.concat(1);
|
||||
} else {
|
||||
m_serviceList.concat(0);
|
||||
}
|
||||
m_serviceList.concat(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PhotonTelemetry::handleEvent(const InputEvent &evt)
|
||||
{
|
||||
Serial.flush();
|
||||
if (evt.intent == InputEvent::NetworkStatus) {
|
||||
onConnected();
|
||||
}
|
||||
if (evt.intent != InputEvent::None) {
|
||||
const char* sourceName;
|
||||
switch(evt.intent) {
|
||||
case InputEvent::PowerToggle:
|
||||
sourceName = "power-toggle";
|
||||
break;
|
||||
case InputEvent::SetPower:
|
||||
sourceName = "set-power";
|
||||
break;
|
||||
case InputEvent::PreviousPattern:
|
||||
sourceName = "previous-pattern";
|
||||
break;
|
||||
case InputEvent::NextPattern:
|
||||
sourceName = "next-pattern";
|
||||
break;
|
||||
case InputEvent::SetPattern:
|
||||
sourceName = "set-pattern";
|
||||
break;
|
||||
case InputEvent::SetColor:
|
||||
sourceName = "set-color";
|
||||
break;
|
||||
case InputEvent::Acceleration:
|
||||
sourceName = "acceleration";
|
||||
break;
|
||||
case InputEvent::UserInput:
|
||||
sourceName = "user";
|
||||
break;
|
||||
case InputEvent::SetBrightness:
|
||||
sourceName = "set-brightness";
|
||||
break;
|
||||
case InputEvent::FirmwareUpdate:
|
||||
sourceName = "firmware-update";
|
||||
break;
|
||||
case InputEvent::NetworkStatus:
|
||||
sourceName = "network-status";
|
||||
break;
|
||||
case InputEvent::NetworkActivity:
|
||||
sourceName = "network-activity";
|
||||
break;
|
||||
case InputEvent::StartThing:
|
||||
sourceName = "start-thing";
|
||||
break;
|
||||
case InputEvent::StopThing:
|
||||
sourceName = "stop-thing";
|
||||
break;
|
||||
case InputEvent::SetDisplayOffset:
|
||||
sourceName = "set-display-offset";
|
||||
break;
|
||||
case InputEvent::SetDisplayLength:
|
||||
sourceName = "set-display-length";
|
||||
break;
|
||||
case InputEvent::SaveConfigurationRequest:
|
||||
sourceName = "save-configuration";
|
||||
break;
|
||||
default:
|
||||
sourceName = 0;
|
||||
break;
|
||||
}
|
||||
char valueBuf[255];
|
||||
switch(evt.type) {
|
||||
case InputEvent::Null:
|
||||
snprintf(valueBuf, sizeof(valueBuf), "null");break;
|
||||
case InputEvent::Integer:
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%d %02x", evt.asInt(), evt.asInt());break;
|
||||
case InputEvent::String:
|
||||
snprintf(valueBuf, sizeof(valueBuf), "\"%s\"", evt.asString());break;
|
||||
case InputEvent::Color:
|
||||
snprintf(valueBuf, sizeof(valueBuf), "[%d, %d, %d]", evt.asRGB().r, evt.asRGB().g, evt.asRGB().b);break;
|
||||
}
|
||||
char buf[255 * 2];
|
||||
if (sourceName == 0) {
|
||||
snprintf(buf, sizeof(buf), "{\"intent\": %d, \"value\": %s}", evt.intent, valueBuf);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "{\"intent\": \"%s\", \"value\": %s}", sourceName, valueBuf);
|
||||
}
|
||||
if (m_online) {
|
||||
if (evt.intent != m_lastEvent.intent) {
|
||||
if (m_duplicateEvents > 0) {
|
||||
Log.info("Suppressed reporting %ld duplicate events.", m_duplicateEvents);
|
||||
}
|
||||
Log.info("Event: %s", buf);
|
||||
m_duplicateEvents = 0;
|
||||
m_lastEvent = evt;
|
||||
Particle.publish("renderbug/event", buf, PRIVATE);
|
||||
} else {
|
||||
m_duplicateEvents++;
|
||||
}
|
||||
} else {
|
||||
Log.info("[offline] Event: %s", buf);
|
||||
}
|
||||
|
||||
if (evt.intent == InputEvent::SetColor) {
|
||||
NSFastLED::CRGB rgb {evt.asRGB()};
|
||||
uint32_t color = (rgb.r << 16) + (rgb.g << 8) + (rgb.b << 0);
|
||||
m_ledStatus.setColor(color);
|
||||
m_ledStatus.setActive(true);
|
||||
m_rgbPulseFrame = 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
STATIC_ALLOC(PhotonTelemetry);
|
25
src/platform/particle/PhotonTelemetry.h
Normal file
25
src/platform/particle/PhotonTelemetry.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "./Figments/Figments.h"
|
||||
|
||||
class PhotonTelemetry: public Task {
|
||||
public:
|
||||
PhotonTelemetry();
|
||||
void loop() override;
|
||||
void handleEvent(const InputEvent& evt) override;
|
||||
|
||||
private:
|
||||
void onConnected();
|
||||
|
||||
int m_frameIdx;
|
||||
String m_serviceList;
|
||||
String m_localIP;
|
||||
uint32_t m_currentBrightness;
|
||||
LEDStatus m_ledStatus = LEDStatus(0, LED_PATTERN_FADE, LED_SPEED_FAST);
|
||||
uint32_t m_rgbPulseFrame = 0;
|
||||
uint32_t m_pixelCount = 0;
|
||||
uint32_t m_startPixel = 0;
|
||||
uint32_t m_fps = 0;
|
||||
uint32_t m_duplicateEvents = 0;
|
||||
InputEvent m_lastEvent;
|
||||
bool m_online = false;
|
||||
};
|
30
src/platform/particle/Watchdog.cpp
Normal file
30
src/platform/particle/Watchdog.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include <Figments.h>
|
||||
|
||||
BootOpts bootopts;
|
||||
|
||||
void watchdogHandler() {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
leds[i] = CRGB(i % 3 ? 35 : 255, 0, 0);
|
||||
}
|
||||
FastLED.show();
|
||||
if (bootopts.lastBootWasFlash) {
|
||||
System.dfu();
|
||||
} else {
|
||||
System.enterSafeMode();
|
||||
}
|
||||
}
|
||||
|
||||
class Watchdog : public Task {
|
||||
public:
|
||||
Watchdog() : Task("Watchdog") {
|
||||
m_watchdog = new ApplicationWatchdog(5000, watchdogHandler, 1536);
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
m_watchdog->checkin();
|
||||
}
|
||||
private:
|
||||
ApplicationWatchdog *m_watchdog;
|
||||
};
|
||||
|
||||
STATIC_ALLOC(Watchdog);
|
157
src/platform/particle/WebTelemetry.cpp
Normal file
157
src/platform/particle/WebTelemetry.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#include "WebDuino/WebDuino.h"
|
||||
#include "Figments/Figments.h"
|
||||
#include "Figments/Input.h"
|
||||
#include "colors.h"
|
||||
#include "Sequencer.h"
|
||||
|
||||
class WebTelemetry : public Task {
|
||||
private:
|
||||
TCPServer m_server;
|
||||
TCPClient m_client;
|
||||
Sequencer& m_sequencer;
|
||||
|
||||
void onConnected() {
|
||||
m_server.begin();
|
||||
Log.info("HTTP server started on %s:80", WiFi.localIP().toString().c_str());
|
||||
}
|
||||
|
||||
void redirectToRoot() {
|
||||
m_server.write("HTTP/1.1 303 Redirect\n");
|
||||
m_server.write("Location: /\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
}
|
||||
|
||||
public:
|
||||
WebTelemetry(Sequencer& sequencer) : Task("WebTelemetry"), m_server(80), m_sequencer(sequencer) {
|
||||
}
|
||||
|
||||
void handleEvent(const InputEvent &evt) {
|
||||
if (evt.intent == InputEvent::NetworkStatus) {
|
||||
onConnected();
|
||||
}
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
static String taskName;
|
||||
if (m_client.connected()) {
|
||||
if (m_client.available()) {
|
||||
MainLoop::instance()->dispatch(InputEvent::NetworkActivity);
|
||||
String requestLine = m_client.readStringUntil('\n');
|
||||
Log.info("%s %s", m_client.remoteIP().toString().c_str(), requestLine.c_str());
|
||||
if (requestLine.startsWith("GET")) {
|
||||
int httpVersionIdx = requestLine.lastIndexOf(" ");
|
||||
String uri = requestLine.substring(4, httpVersionIdx);
|
||||
if (uri.equals("/")) {
|
||||
m_server.write("HTTP/1.1 200 Renderbug is here!\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
m_server.write("<!DOCTYPE HTML><html><body>");
|
||||
m_server.write("<p>Scenes</p><table>");
|
||||
auto curScene = m_sequencer.currentSceneName();
|
||||
for(auto scene : m_sequencer.scenes()) {
|
||||
bool isEnabled = strcmp(curScene, scene.name) == 0;
|
||||
m_server.write("<tr><td>");
|
||||
if (isEnabled) {
|
||||
m_server.write("<strong>");
|
||||
}
|
||||
m_server.write(scene.name);
|
||||
if (isEnabled) {
|
||||
m_server.write("</strong>");
|
||||
}
|
||||
m_server.write("</td><td><ul>");
|
||||
for(auto patternName : scene.patterns) {
|
||||
m_server.write("<li>");
|
||||
m_server.write(patternName);
|
||||
m_server.write("</li>");
|
||||
}
|
||||
m_server.write("</ul></td></tr>");
|
||||
}
|
||||
m_server.write("</table>");
|
||||
m_server.write("<form><button name=\"pattern\" value=\"prev\">Previous pattern</button><button name=\"pattern\" value=\"next\">Next pattern</button></form>");
|
||||
m_server.write("<form><select name=\"color\">");
|
||||
const ColorInfo* colors = allColors();
|
||||
for(int i = 0; colors[i].name != 0;i++) {
|
||||
m_server.write("<option>");
|
||||
m_server.write(colors[i].name);
|
||||
m_server.write("</option>");
|
||||
}
|
||||
m_server.write("</select>");
|
||||
m_server.write("<button>Set Color</button>");
|
||||
m_server.write("</form>");
|
||||
m_server.write("<p>Tasks</p><table>");
|
||||
auto sched = MainLoop::instance()->scheduler;
|
||||
for(auto task : sched.tasks) {
|
||||
bool isFigment = task->isFigment();
|
||||
|
||||
m_server.write("<tr><td>");
|
||||
if (isFigment) {
|
||||
m_server.write("<strong>");
|
||||
}
|
||||
m_server.write(task->name);
|
||||
if (isFigment) {
|
||||
m_server.write("</strong>");
|
||||
}
|
||||
m_server.write("</td><td>");
|
||||
if (task->state == Task::Running) {
|
||||
m_server.write("Running");
|
||||
} else {
|
||||
m_server.write("Paused");
|
||||
}
|
||||
m_server.write("</td><td>");
|
||||
m_server.write("<a href=\"/?stop=");
|
||||
m_server.write(task->name);
|
||||
m_server.write("\">Stop</a></td><td>");
|
||||
m_server.write("<a href=\"/?start=");
|
||||
m_server.write(task->name);
|
||||
m_server.write("\">Start</a></td></tr>");
|
||||
}
|
||||
m_server.write("</table>");
|
||||
m_server.write("<a href='/reboot'>Reboot Renderbug</a>");
|
||||
m_server.write("<a href='/save'>Save configuration</a>");
|
||||
} else if (uri.startsWith("/save")) {
|
||||
MainLoop::instance()->dispatch(InputEvent::SaveConfigurationRequest);
|
||||
redirectToRoot();
|
||||
} else if (uri.startsWith("/?color=")) {
|
||||
int varStart = uri.indexOf("=");
|
||||
String colorName = uri.substring(varStart + 1);
|
||||
colorName.replace('+', ' ');
|
||||
colorName.replace("%20", " ");
|
||||
ColorInfo nextColor = colorForName(colorName);
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetColor, nextColor.rgb});
|
||||
redirectToRoot();
|
||||
} else if (uri.startsWith("/?start=")) {
|
||||
int varStart = uri.indexOf("=");
|
||||
taskName = uri.substring(varStart + 1);
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, taskName.c_str()});
|
||||
redirectToRoot();
|
||||
} else if (uri.startsWith("/?stop=")) {
|
||||
int varStart = uri.indexOf("=");
|
||||
taskName = uri.substring(varStart + 1);
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, taskName.c_str()});
|
||||
redirectToRoot();
|
||||
} else if (uri.equals("/?pattern=prev")) {
|
||||
redirectToRoot();
|
||||
MainLoop::instance()->dispatch(InputEvent::PreviousPattern);
|
||||
} else if (uri.equals("/?pattern=next")) {
|
||||
redirectToRoot();
|
||||
MainLoop::instance()->dispatch(InputEvent::NextPattern);
|
||||
} else if (uri.equals("/reboot")) {
|
||||
m_server.write("HTTP/1.1 200 Ok\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
m_server.write("Rebooting!");
|
||||
} else {
|
||||
m_server.write("HTTP/1.1 404 Not found\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
}
|
||||
} else {
|
||||
m_server.write("HTTP/1.1 501 Not Implemented\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
}
|
||||
}
|
||||
m_client.stop();
|
||||
} else {
|
||||
m_client = m_server.available();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
28
src/platform/particle/inputs/CloudStatus.cpp
Normal file
28
src/platform/particle/inputs/CloudStatus.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "CloudStatus.h"
|
||||
#include "../../../Static.h"
|
||||
|
||||
void
|
||||
CloudStatus::onStart()
|
||||
{
|
||||
SINGLE_THREADED_BLOCK() {
|
||||
if (Particle.connected()) {
|
||||
initNetwork(0, cloud_status_connected);
|
||||
} else {
|
||||
System.on(cloud_status, &CloudStatus::initNetwork);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CloudStatus::initNetwork(system_event_t event, int param) {
|
||||
if (param == cloud_status_connected) {
|
||||
Log.info("Connected to T H E C L O U D");
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::NetworkStatus, true});
|
||||
} else if (param == cloud_status_disconnected) {
|
||||
Log.info("Lost cloud connection!!");
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::NetworkStatus, false});
|
||||
}
|
||||
}
|
||||
|
||||
STATIC_ALLOC(CloudStatus);
|
11
src/platform/particle/inputs/CloudStatus.h
Normal file
11
src/platform/particle/inputs/CloudStatus.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "../../../Figments/Figments.h"
|
||||
|
||||
class CloudStatus: public Task {
|
||||
public:
|
||||
CloudStatus() : Task("Cloud") {}
|
||||
void loop() override {}
|
||||
void onStart() override;
|
||||
|
||||
private:
|
||||
static void initNetwork(system_event_t event, int param);
|
||||
};
|
187
src/platform/particle/inputs/Photon.cpp
Normal file
187
src/platform/particle/inputs/Photon.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
#include "Particle.h"
|
||||
#include "../../../Figments/Figments.h"
|
||||
#include "../../../colors.h"
|
||||
#include "../../../Static.h"
|
||||
#include "./Photon.h"
|
||||
|
||||
void
|
||||
PhotonInput::onConnected()
|
||||
{
|
||||
Log.info("Connecting photon input...");
|
||||
Particle.function("save", &PhotonInput::save, this);
|
||||
Particle.function("power", &PhotonInput::setPower, this);
|
||||
Particle.function("next", &PhotonInput::nextPattern, this);
|
||||
Particle.function("input", &PhotonInput::input, this);
|
||||
Particle.function("previous", &PhotonInput::previousPattern, this);
|
||||
Particle.function("pattern", &PhotonInput::setPattern, this);
|
||||
Particle.function("setHue", &PhotonInput::setHue, this);
|
||||
Particle.function("brightness", &PhotonInput::setBrightness, this);
|
||||
Particle.function("reboot", &PhotonInput::reboot, this);
|
||||
Particle.function("start", &PhotonInput::startThing, this);
|
||||
Particle.function("stop", &PhotonInput::stopThing, this);
|
||||
|
||||
//Log.info("Connecting photon configuration...");
|
||||
Particle.function("pixelCount", &PhotonInput::setPixelCount, this);
|
||||
Particle.function("startPixel", &PhotonInput::setStartPixel, this);
|
||||
|
||||
Particle.function("save", &PhotonInput::photonSave, this);
|
||||
Particle.variable("pixelCount", m_pixelCountInt);
|
||||
Particle.variable("startPixel", m_startPixelInt);
|
||||
|
||||
publishConfig();
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::startThing(String command)
|
||||
{
|
||||
command.toCharArray(m_commandBuf, sizeof(m_commandBuf));
|
||||
setEvent(InputEvent(InputEvent::StartThing, m_commandBuf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::stopThing(String command)
|
||||
{
|
||||
command.toCharArray(m_commandBuf, sizeof(m_commandBuf));
|
||||
setEvent(InputEvent(InputEvent::StopThing, m_commandBuf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::onStart()
|
||||
{
|
||||
System.on(firmware_update, &PhotonInput::onFirmwareUpdate);
|
||||
System.on(button_click, &PhotonInput::onButtonClick);
|
||||
System.on(reset, &PhotonInput::onReset);
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::handleEvent(const InputEvent &evt)
|
||||
{
|
||||
if (evt.intent == InputEvent::NetworkStatus) {
|
||||
onConnected();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::reboot(String command)
|
||||
{
|
||||
System.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::onReset(system_event_t event, int param)
|
||||
{
|
||||
NSFastLED::FastLED.clear();
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::onButtonClick(system_event_t event, int param)
|
||||
{
|
||||
Static<PhotonInput>::instance()->setEvent(InputEvent{InputEvent::NextPattern, param});
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::onFirmwareUpdate(system_event_t event, int param)
|
||||
{
|
||||
Static<PhotonInput>::instance()->setEvent(InputEvent{InputEvent::FirmwareUpdate, param});
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::input(String command)
|
||||
{
|
||||
command.toCharArray(m_commandBuf, sizeof(m_commandBuf));
|
||||
setEvent(InputEvent(InputEvent::UserInput, m_commandBuf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setPattern(String command)
|
||||
{
|
||||
command.toCharArray(m_commandBuf, sizeof(m_commandBuf));
|
||||
setEvent(InputEvent(InputEvent::SetPattern, m_commandBuf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setHue(String colorName)
|
||||
{
|
||||
ColorInfo nextColor = colorForName(colorName);
|
||||
setEvent(InputEvent(InputEvent::SetColor, nextColor.rgb));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::nextPattern(String command)
|
||||
{
|
||||
setEvent(InputEvent(InputEvent::NextPattern));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::previousPattern(String command)
|
||||
{
|
||||
setEvent(InputEvent(InputEvent::PreviousPattern));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::save(String command) {
|
||||
setEvent(InputEvent::SaveConfigurationRequest);
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setPower(String command)
|
||||
{
|
||||
if (command == "off") {
|
||||
setEvent(InputEvent(InputEvent::SetPower, 0));
|
||||
} else if (command == "on") {
|
||||
setEvent(InputEvent(InputEvent::SetPower, 1));
|
||||
} else {
|
||||
setEvent(InputEvent(InputEvent::PowerToggle));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setBrightness(String command)
|
||||
{
|
||||
setEvent(InputEvent(InputEvent::SetBrightness, command.toInt()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::publishConfig() const
|
||||
{
|
||||
char buf[255];
|
||||
snprintf(buf, sizeof(buf), "{\"pixels\": \"%d\", \"offset\": \"%d\"}", m_config.data.pixelCount, m_config.data.startPixel);
|
||||
Particle.publish("renderbug/config", buf, PRIVATE);
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::photonSave(String command)
|
||||
{
|
||||
setEvent(InputEvent::SaveConfiguration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setPixelCount(String command)
|
||||
{
|
||||
m_pixelCount = command.toInt();
|
||||
setEvent(InputEvent{InputEvent::SetDisplayLength, m_pixelCount})
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setStartPixel(String command)
|
||||
{
|
||||
m_startPixel = command.toInt();
|
||||
setEvent(InputEvent{InputEvent::SetDisplayLength, m_startPixel})
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
STATIC_ALLOC(PhotonInput);
|
38
src/platform/particle/inputs/Photon.h
Normal file
38
src/platform/particle/inputs/Photon.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "Particle.h"
|
||||
#include "../../../Figments/Figments.h"
|
||||
|
||||
class PhotonInput: public BufferedInputSource {
|
||||
public:
|
||||
PhotonInput() : BufferedInputSource("PhotonInput") {}
|
||||
void onStart() override;
|
||||
void handleEvent(const InputEvent &evt) override;
|
||||
|
||||
private:
|
||||
char m_commandBuf[16];
|
||||
|
||||
void onConnected();
|
||||
int reboot(String command);
|
||||
int input(String command);
|
||||
int setPattern(String command);
|
||||
int setHue(String colorName);
|
||||
int nextPattern(String command);
|
||||
int previousPattern(String command);
|
||||
int setPower(String command);
|
||||
int setBrightness(String command);
|
||||
int save(String command);
|
||||
|
||||
int startThing(String command);
|
||||
int stopThing(String command);
|
||||
|
||||
void publishConfig() const;
|
||||
int photonSave(String command);
|
||||
int setPixelCount(String command);
|
||||
int setStartPixel(String command);
|
||||
|
||||
static void onReset(system_event_t event, int param);
|
||||
static void onButtonClick(system_event_t event, int param);
|
||||
static void onFirmwareUpdate(system_event_t event, int param);
|
||||
|
||||
int m_startPixel;
|
||||
int m_pixelCount;
|
||||
};
|
Reference in New Issue
Block a user