platform: drop particle platform
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Torrie Fischer 2023-02-18 18:46:42 +01:00
parent 9a36831658
commit ef4c6e016d
11 changed files with 0 additions and 794 deletions

View File

@ -181,8 +181,3 @@ lib_deps =
#[env:home_lighting-12f]
#extends = env:esp8266_wifi config_u8display
#board = esp12e
;[env:photon]
;platform = particlephoton
;board = photon
;framework = arduino

View File

@ -1,36 +0,0 @@
#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);
STATIC_TASK(MDNSService);

View File

@ -1,205 +0,0 @@
#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);
STATIC_TASK(MQTTTelemetry);

View File

@ -1,70 +0,0 @@
#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)
{
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);
STATIC_TASK(PhotonTelemetry);

View File

@ -1,25 +0,0 @@
#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;
};

View File

@ -1,31 +0,0 @@
#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);
STATIC_TASK(Watchdog);

View File

@ -1,158 +0,0 @@
#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();
}
}
};
STATIC_ALLOC(WebTelemetry);
STATIC_TASK(WebTelemetry);

View File

@ -1,29 +0,0 @@
#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);
STATIC_TASK(CloudStatus);

View File

@ -1,11 +0,0 @@
#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);
};

View File

@ -1,186 +0,0 @@
#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);
STATIC_TASK(PhotonInput);

View File

@ -1,38 +0,0 @@
#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;
};