If the code hasn't been touched in this long, its probably release-worthy.

This commit is contained in:
2022-06-11 11:02:27 +02:00
parent 0c9eb831dd
commit d14fa7fde1
59 changed files with 1610 additions and 842 deletions

View File

@ -8,9 +8,13 @@ constexpr uint16_t HardwareConfig::MAX_LED_NUM;
HardwareConfig
HardwareConfig::load() {
HardwareConfig ret;
#ifndef BOARD_TEENSY
EEPROM.begin(sizeof(ret));
#endif
EEPROM.get(0, ret);
#ifndef BOARD_TEENSY
EEPROM.end();
#endif
Log.notice("Loaded config version %d, CRC %d", ret.version, ret.checksum);
return ret;
}
@ -19,10 +23,14 @@ void
HardwareConfig::save() {
HardwareConfig dataCopy{*this};
dataCopy.checksum = getCRC();
#ifndef BOARD_TEENSY
EEPROM.begin(sizeof(dataCopy));
#endif
EEPROM.put(0, dataCopy);
#ifndef BOARD_TEENSY
EEPROM.commit();
EEPROM.end();
#endif
}
LinearCoordinateMapping
@ -72,13 +80,6 @@ ConfigService::onStart()
m_coordMap = m_config.toCoordMap();
Log.notice("Configured to use %d pixels, starting at %d", m_config.data.pixelCount, m_config.data.startPixel);
/*Log.notice("Loading task states...");
for(int i = 0; i < 32; i++) {
auto svc = m_config.data.serviceStates[i];
if (strnlen(svc.name, 16) > 0) {
Log.notice("* %s: %s", svc.name, svc.isDisabled? "DISABLED" : "ENABLED");
}
}*/
}
void
@ -112,3 +113,4 @@ ConfigService::handleEvent(const InputEvent &evt)
}
STATIC_ALLOC(ConfigService);
STATIC_TASK(ConfigService);

View File

@ -1,17 +1,75 @@
#pragma once
#include <Figments.h>
//#define PLATFORM_PHOTON
//#define PLATFORM_ARDUINO
#define RENDERBUG_VERSION 2
struct MaskCoordinateMapping : CoordinateMapping {
struct Span {
int length = 0;
int x = 0;
int y = 0;
Span(int length, int x, int y) : length(length), x(x), y(y) {}
};
Span displayMap[13] = {
{6, 0, 6},
{6, 1, 6},
{7, 2, 6},
{9, 3, 4},
{14, 4, 4},
{17, 5, 0},
{12, 6, 2},
{18, 7, 0},
{14, 8, 4},
{9, 9, 5},
{7, 10, 4},
{6, 11, 5},
{6, 12, 5}
};
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const override {
int offset = localCoords.x;
for(int i = 0; i < 12; i++) {
if (offset > displayMap[i].length) {
offset -= displayMap[i].length;
} else {
return VirtualCoordinates{i, offset};
}
}
}
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override {
const uint8_t spanIdx = scale8(12, virtualCoords.x);
const uint8_t spanOffset = scale8(17, virtualCoords.y);
return PhysicalCoordinates{spanIdx, spanOffset};
}
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override {
uint8_t idx = 0;
bool inverse = false;
for(int i = 0; i < localCoords.x; i++) {
idx += displayMap[i].length;
inverse = !inverse;
}
if (inverse) {
idx += std::max(0, displayMap[localCoords.x].length - 1 - std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
} else {
idx += std::min((int)displayMap[localCoords.x].length - 1, std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
}
return idx;
}
unsigned int physicalPixelCount() const override {
int total = 0;
for(int i = 0; i < 13; i++) {
total += displayMap[i].length;
}
return total;
}
};
struct HardwareConfig {
uint8_t version = 2;
uint8_t version = 3;
uint8_t checksum = 0;
struct TaskState {
char name[16] = {0};
bool isDisabled = false;
};
struct Data {
uint16_t pixelCount = 255;
uint16_t startPixel = 0;
@ -19,7 +77,6 @@ struct HardwareConfig {
uint8_t lastGreen = 255;
uint8_t lastBlue = 255;
char lastScene[16] = {0};
TaskState serviceStates[32];
};
Data data;
@ -28,13 +85,7 @@ struct HardwareConfig {
bool isValid() const;
LinearCoordinateMapping toCoordMap() const;
static constexpr uint16_t MAX_LED_NUM = 255;
#ifdef PLATFORM_PHOTON
static constexpr bool HAS_MPU_6050 = true;
#else
static constexpr bool HAS_MPU_6050 = false;
#endif
private:
uint8_t getCRC() const;
@ -50,9 +101,10 @@ struct ConfigService: public Task {
void onStart();
void loop() override;
void handleEvent(const InputEvent &evt) override;
const LinearCoordinateMapping* coordMap() const { return &m_coordMap; }
const CoordinateMapping* coordMap() const { return /*&m_maskMap;*/ &m_coordMap; }
private:
HardwareConfig m_config;
MaskCoordinateMapping m_maskMap;
LinearCoordinateMapping m_coordMap;
};

View File

@ -2,99 +2,96 @@
#include "Static.h"
#include <ArduinoLog.h>
static const char* eventValue(const InputEvent& evt);
const char*
LogService::intentName(InputEvent::Intent intent)
{
switch(intent) {
case InputEvent::BeatDetect:
return "beat-detection";
case InputEvent::Beat:
return "beat";
case InputEvent::ReadyToRoll:
return "ready-to-roll";
case InputEvent::PowerToggle:
return "power-toggle";
case InputEvent::SetPower:
return "set-power";
case InputEvent::PreviousPattern:
return "previous-pattern";
case InputEvent::NextPattern:
return "next-pattern";
case InputEvent::SetPattern:
return "set-pattern";
case InputEvent::SetColor:
return "set-color";
case InputEvent::Acceleration:
return "acceleration";
case InputEvent::UserInput:
return "user";
case InputEvent::SetBrightness:
return "set-brightness";
case InputEvent::FirmwareUpdate:
return "firmware-update";
case InputEvent::NetworkStatus:
return "network-status";
case InputEvent::NetworkActivity:
return "network-activity";
case InputEvent::StartThing:
return "start-thing";
case InputEvent::StopThing:
return "stop-thing";
case InputEvent::SetDisplayOffset:
return "set-display-offset";
case InputEvent::SetDisplayLength:
return "set-display-length";
case InputEvent::SaveConfigurationRequest:
return "save-configuration";
default:
return NULL;
}
}
char LogService::s_valueBuf[255];
const char*
LogService::eventValue(const InputEvent& evt)
{
switch(evt.type) {
case InputEvent::Null:
snprintf(s_valueBuf, sizeof(s_valueBuf), "null");break;
case InputEvent::Integer:
snprintf(s_valueBuf, sizeof(s_valueBuf), "%d %02x", evt.asInt(), evt.asInt());break;
case InputEvent::String:
snprintf(s_valueBuf, sizeof(s_valueBuf), "\"%s\"", evt.asString());break;
case InputEvent::Color:
snprintf(s_valueBuf, sizeof(s_valueBuf), "[%d, %d, %d]", evt.asRGB().r, evt.asRGB().g, evt.asRGB().b);break;
}
return s_valueBuf;
}
void
LogService::handleEvent(const InputEvent& evt) {
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 (evt.intent != m_lastEvent.intent) {
if (m_duplicateEvents > 0) {
Log.notice("Suppressed reporting %d duplicate events.", m_duplicateEvents);
Log.trace("Suppressed reporting %d duplicate events.", m_duplicateEvents);
}
const char* sourceName = intentName(evt.intent);;
const char* valueBuf = eventValue(evt);
if (sourceName == 0) {
Log.trace("Event: intent: %d value: %s", evt.intent, valueBuf);
} else {
Log.trace("Event: intent: %s value: %s", sourceName, valueBuf);
}
Log.verbose("Event: %s", buf);
m_duplicateEvents = 0;
m_lastEvent = evt;
//Particle.publish("renderbug/event", buf, PRIVATE);
} else {
m_duplicateEvents++;
}
/*if (m_online) {
} else {
Log.info("[offline] Event: %s", buf);
}*/
}
}
STATIC_ALLOC(LogService);
STATIC_TASK(LogService);

View File

@ -6,7 +6,11 @@ class LogService : public Task {
void handleEvent(const InputEvent& event) override;
void loop() override {}
static const char* intentName(InputEvent::Intent intent);
static const char* eventValue(const InputEvent& evt);
private:
static char s_valueBuf[255];
uint16_t m_duplicateEvents = 0;
InputEvent m_lastEvent;
};

View File

@ -1,12 +1,15 @@
#include "Platform.h"
#include <ArduinoLog.h>
#include "Static.h"
#include <time.h>
#ifdef BOARD_ESP32
#ifdef CONFIG_WIFI
#include <WiFi.h>
#endif
#include <esp_task_wdt.h>
#include <time.h>
#elif defined(BOARD_ESP8266)
#ifdef CONFIG_WIFI
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
@ -16,11 +19,14 @@ WiFiUDP wifiUdp;
//NTPClient timeClient(wifiUdp, "pool.ntp.org", 3600 * -7);
NTPClient timeClient(wifiUdp, "10.0.0.1", 3600 * -7);
#endif
#endif
#ifdef PLATFORM_PHOTON
STARTUP(BootOptions::initPins());
#else
#ifdef CONFIG_MQTT
#include "platform/arduino/MQTTTelemetry.h"
#endif
void printNewline(Print* logOutput)
{
logOutput->print("\r\n");
@ -33,6 +39,8 @@ int printEspLog(const char* fmt, va_list args)
#endif
int Platform::s_timezone = 0;
Platform::TaskRegistration* Platform::firstTask = NULL;
Platform::TaskRegistration* Platform::lastTask = NULL;
const char*
Platform::name()
@ -78,7 +86,11 @@ Platform::preSetup()
Log.notice("\xf0\x9f\x94\x8c Serial connected");
}
#else
Log.begin(LOG_LEVEL_VERBOSE, Static<MQTTTelemetry>::instance()->logPrinter());
#ifdef CONFIG_MQTT
Log.begin(LOG_LEVEL_TRACE, Static<MQTTTelemetry>::instance()->logPrinter());
#else
Log.begin(LOG_LEVEL_TRACE, &Serial);
#endif
Log.setSuffix(printNewline);
#endif
@ -101,8 +113,10 @@ Platform::setup()
constexpr int dst = 1;
configTime(s_timezone* 3600, 3600 * dst, "pool.ntp.org");
#elif defined(BOARD_ESP8266)
#ifdef CONFIG_WIFI
timeClient.begin();
#endif
#endif
}
void
@ -114,15 +128,24 @@ Platform::bootSplash()
Log.notice(u8" 3: Serial - %d", bootopts.isSerial);
Log.notice(u8" 4: Flash - %d", bootopts.isFlash);
#endif
Log.trace("Registered tasks:");
auto it = beginTasks();
while (it != endTasks()) {
Log.trace((*it)->name);
++it;
}
}
void
Platform::loop()
{
#ifdef BOARD_ESP8266
#ifdef CONFIG_WIFI
if (WiFi.status() == WL_CONNECTED) {
timeClient.update();
}
#endif
ESP.wdtFeed();
#elif defined(BOARD_ESP32)
esp_task_wdt_reset();
@ -141,13 +164,18 @@ Platform::getLocalTime(struct tm* timedata)
return false;
#elif defined(BOARD_ESP32)
time_t rawtime;
memset(&rawtime, 0, sizeof(rawtime));
time(&rawtime);
(*timedata) = (*localtime(&rawtime));
return (timedata->tm_year > (2016-1990));
//return getLocalTime(timedata);
#else
#ifdef CONFIG_WIFI
timedata->tm_hour = timeClient.getHours();
timedata->tm_min = timeClient.getMinutes();
#else
memset(timedata, sizeof(struct tm), 0);
return false;
#endif
return true;
#endif
}
@ -172,3 +200,4 @@ char
Platform::s_deviceID[15];
STATIC_ALLOC(Platform);
STATIC_TASK(Platform);

View File

@ -13,14 +13,7 @@ class Platform : public Task {
static int getTimezone() { return s_timezone; }
static void addLEDs(CRGB* leds, unsigned int ledCount) {
#ifdef PLATFORM_PHOTON
FastLED.addLeds<NEOPIXEL, 6>(leds, ledCount);
#elif defined(BOARD_ESP32)
FastLED.addLeds<WS2812B, 13, GRB>(leds, ledCount);
#else
//FastLED.addLeds<WS2812B, 14, GRB>(leds, ledCount);
FastLED.addLeds<WS2812B, 14, RGB>(leds, ledCount);
#endif
FastLED.addLeds<WS2812B, RENDERBUG_LED_PIN, RENDERBUG_LED_PACKING>(leds, ledCount);
}
static const char* name();
@ -39,5 +32,58 @@ class Platform : public Task {
void loop() override;
static bool getLocalTime(struct tm* timedata);
static const char* deviceID();
};
struct TaskRegistration {
Task* task = 0;
TaskRegistration* next = 0;
TaskRegistration(Task* task) : task(task) {}
};
static TaskRegistration* firstTask;
static TaskRegistration* lastTask;
static void registerTask(TaskRegistration* reg) {
if (firstTask == NULL) {
firstTask = reg;
lastTask = firstTask;
} else {
lastTask->next = reg;
lastTask = reg;
}
}
struct task_iterator: public std::iterator<std::input_iterator_tag, Task*> {
TaskRegistration* cur;
explicit task_iterator() : cur(NULL) {}
explicit task_iterator(TaskRegistration* head) : cur(head) {}
task_iterator& operator++() {
if (cur) {
cur = cur->next;
}
return *this;
}
task_iterator operator++(int) {task_iterator ret = *this; ++(*this); return ret;}
bool operator==(task_iterator other) const { return cur == other.cur; }
bool operator!=(task_iterator other) const { return !(*this == other); }
Task* operator*() const { return cur->task; }
};
static task_iterator beginTasks() {
return task_iterator(firstTask);
}
static task_iterator endTasks() {
return task_iterator(NULL);
}
static void restart() {
#ifdef BOARD_ESP8266
ESP.wdtDisable();
ESP.restart();
#elif defined(BOARD_ESP32)
ESP.restart();
#endif
}
};

View File

@ -3,6 +3,14 @@
Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes) :
Task("SceneSequencer"),
m_idx(0),
m_scenes(std::move(scenes))
{
}
Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes, int startIndex) :
Task("SceneSequencer"),
m_idx(startIndex),
m_scenes(std::move(scenes))
{
}
@ -22,16 +30,28 @@ Sequencer::scenes() const
return m_scenes;
}
void
Sequencer::onStart()
{
}
void
Sequencer::handleEvent(const InputEvent& evt)
{
if (evt.intent == InputEvent::ReadyToRoll) {
Log.notice("Starting pattern %s!", m_scenes[m_idx].name);
for(const char* pattern : m_scenes[m_idx].patterns) {
Log.verbose("Starting pattern task %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
}
}
if (evt.intent == InputEvent::SetPattern && evt.asString() == m_scenes[m_idx].name) {
return;
}
if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) {
Log.notice("Switching pattern!");
for(const char* pattern : m_scenes[m_idx].patterns) {
//Log.notice("Stopping %s", pattern);
Log.verbose("Stopping pattern task %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern});
}
@ -57,7 +77,7 @@ Sequencer::handleEvent(const InputEvent& evt)
}
for(const char* pattern : m_scenes[m_idx].patterns) {
//Log.notice("Starting %s", pattern);
Log.verbose("Starting pattern task %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
}
}

View File

@ -13,14 +13,16 @@ public:
};
Sequencer(std::vector<Scene> &&scenes);
Sequencer(std::vector<Scene> &&scenes, int startingIndex);
void loop() override;
void onStart() override;
void handleEvent(const InputEvent& evt) override;
const char* currentSceneName();
const std::vector<Scene> scenes() const;
private:
int m_idx = 0;
int m_idx;
std::vector<Scene> m_scenes;
};

View File

@ -1,4 +1,5 @@
#pragma once
#include "Platform.h"
// Utility class mostly for when certain inputs need singleton callback handlers
template<typename T> class Static {
@ -10,7 +11,24 @@ private:
static T* s_instance;
};
template<typename T> struct StaticTaskRegistration : public Platform::TaskRegistration {
StaticTaskRegistration() : Platform::TaskRegistration(Static<T>::instance()) {
Platform::registerTask(this);
}
};
struct AutoTaskRegistration : public Platform::TaskRegistration {
explicit AutoTaskRegistration(Task* task) : Platform::TaskRegistration(task) {
Platform::registerTask(this);
}
};
#define NAMED_STATIC_ALLOC(Cls, StaticName) static Cls _staticAlloc__ ## StaticName;\
template<> Cls* Static<Cls>::s_instance=&_staticAlloc__ ## StaticName;
#define STATIC_ALLOC(Cls) NAMED_STATIC_ALLOC(Cls, Cls)
#define NAMED_STATIC_TASK(Cls, StaticName) static StaticTaskRegistration<Cls> _staticTask_ ## StaticName;
#define STATIC_TASK(Cls) NAMED_STATIC_TASK(Cls, Cls)
#define REGISTER_TASK(TaskName) static AutoTaskRegistration _autoTask__ ## TaskName(&TaskName);

View File

@ -1,75 +1,74 @@
#include "../Figments/Figments.h"
#include "../sprites/Chime.h"
#include "../sprites/Blob.h"
#include "Chimes.h"
#include "../Static.h"
#define CHIME_LENGTH 23
#define CHIME_COUNT 4
#define BLOB_COUNT 10
ChimesAnimation::ChimesAnimation() : Figment("Chimes", Task::Stopped) {
}
class ChimesAnimation: public Figment {
public:
ChimesAnimation(Task::State initialState) : Figment("Chimes", initialState) {
m_chimes.forEach([](Chime<CHIME_LENGTH> &chime) {
chime.setPos(random(Chime<CHIME_LENGTH>::Length * 5));
chime.setHue(random(255));
chime.setSpeed(random(90) + 138);
chime.setBrightness(200);
chime.setOffset(random(1024));
});
m_blobs.forEach([](Blob &blob) {
blob.setPos(random(255));
blob.setHue(random(255));
blob.setBrightness(random(255));
if (random(255) % 2) {
blob.setVelocity(-1);
} else {
blob.setVelocity(1);
void ChimesAnimation::randomize() {
m_isRandom = true;
m_chimes.forEach([](Chime<CHIME_LENGTH> &chime) {
chime.setPos(random(Chime<CHIME_LENGTH>::Length * 5));
chime.setHue(random(255));
chime.setSpeed(random(90) + 138);
chime.setBrightness(200);
chime.setOffset(random(1024));
});
m_blobs.forEach([](Blob &blob) {
blob.setPos(random(255));
blob.setHue(random(255));
blob.setBrightness(random(255));
if (random(255) % 2) {
blob.setVelocity(-1);
} else {
blob.setVelocity(1);
}
});
}
void ChimesAnimation::handleEvent(const InputEvent& evt) {
if (evt.intent == InputEvent::UserInput) {
if (strcmp(evt.asString(), "blobs") == 0) {
m_blobs.toggle();
} else if (strcmp(evt.asString(), "chimes") == 0) {
m_chimes.toggle();
}
});
} else if (evt.intent == InputEvent::SetColor) {
m_flashBrightness.set(255, 0);
m_flashColor = evt.asRGB();
uint8_t flashHue = rgb2hsv_approximate(m_flashColor).hue;
m_blobs.forEach([&](Blob& blob) {
blob.setHue(flashHue);
});
m_chimes.forEach([&](Chime<CHIME_LENGTH>& chime) {
chime.setHue(flashHue);
});
} else if (evt.intent == InputEvent::Beat) {
m_isRandom = false;
}
}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::UserInput) {
if (strcmp(evt.asString(), "blobs") == 0) {
m_blobs.toggle();
} else if (strcmp(evt.asString(), "chimes") == 0) {
m_chimes.toggle();
}
} else if (evt.intent == InputEvent::SetColor) {
m_flashBrightness.set(255, 0);
m_flashColor = evt.asRGB();
uint8_t flashHue = rgb2hsv_approximate(m_flashColor).hue;
m_blobs.forEach([&](Blob& blob) {
blob.setHue(flashHue);
});
m_chimes.forEach([&](Chime<CHIME_LENGTH>& chime) {
chime.setHue(flashHue);
});
}
}
void ChimesAnimation::loop() {
if (!m_isRandom) {
randomize();
}
m_chimes.update();
m_blobs.update();
m_flashColor.update();
EVERY_N_MILLISECONDS(5) {
m_flashBrightness.update();
}
}
void loop() override {
m_chimes.update();
m_blobs.update();
m_flashColor.update();
EVERY_N_MILLISECONDS(5) {
m_flashBrightness.update();
}
}
void ChimesAnimation::render(Display* dpy) const {
m_chimes.render(dpy);
m_blobs.render(dpy);
Surface fullSurface(dpy, {0, 0}, {255, 0});
CRGB scaledColor = CRGB(m_flashColor).nscale8_video(std::max((uint8_t)10, ease8InOutCubic(m_flashBrightness)));
fullSurface.paintWith([&](CRGB& pixel) {
pixel = blend(scaledColor, pixel, 200);
//pixel = scaledColor;
});
}
void render(Display* dpy) const override {
m_chimes.render(dpy);
m_blobs.render(dpy);
Surface fullSurface(dpy, {0, 0}, {255, 0});
CRGB scaledColor = CRGB(m_flashColor).nscale8_video(std::max((uint8_t)10, ease8InOutCubic(m_flashBrightness)));
fullSurface.paintWith([&](CRGB& pixel) {
pixel = blend(scaledColor, pixel, 200);
//pixel = scaledColor;
});
}
SpriteList<Chime<CHIME_LENGTH>, CHIME_COUNT> m_chimes;
SpriteList<Blob, BLOB_COUNT> m_blobs;
AnimatedRGB m_flashColor;
AnimatedNumber m_flashBrightness;
};
STATIC_ALLOC(ChimesAnimation);
STATIC_TASK(ChimesAnimation);

24
src/animations/Chimes.h Normal file
View File

@ -0,0 +1,24 @@
#include "../Figments/Figments.h"
#include "../sprites/Chime.h"
#include "../sprites/Blob.h"
#define CHIME_LENGTH 23
#define CHIME_COUNT 4
#define BLOB_COUNT 10
class ChimesAnimation: public Figment {
public:
ChimesAnimation();
void handleEvent(const InputEvent& evt) override;
void loop() override;
void render(Display* dpy) const override;
private:
SpriteList<Chime<CHIME_LENGTH>, CHIME_COUNT> m_chimes;
SpriteList<Blob, BLOB_COUNT> m_blobs;
AnimatedRGB m_flashColor;
AnimatedNumber m_flashBrightness;
void randomize();
bool m_isRandom = false;
};

View File

@ -1,64 +1,58 @@
#include <Figments.h>
#include <ArduinoLog.h>
#include "Drain.h"
#include "../Static.h"
class DrainAnimation: public Figment {
public:
DrainAnimation::DrainAnimation() : Figment("Drain", Task::Stopped) {}
DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {}
void loop() override {
EVERY_N_MILLISECONDS(8) {
m_pos++;
m_fillColor.update();
}
EVERY_N_MILLISECONDS(50) {
if (random(255) >= 10) {
m_burst -= m_burst / 10;
}
void DrainAnimation::loop() {
EVERY_N_MILLISECONDS(8) {
m_pos++;
m_fillColor.update();
}
EVERY_N_MILLISECONDS(50) {
if (random(255) >= 10) {
m_burst -= m_burst / 10;
}
}
}
void handleEvent(const InputEvent& event) override {
if (event.intent == InputEvent::SetColor) {
m_fillColor = event.asRGB();
} else if (event.intent == InputEvent::Acceleration) {
m_pos += log10(event.asInt());
uint16_t burstInc = event.asInt() / 6;
m_burst = (m_burst > 0xFFFF - burstInc) ? 0xFFFF : m_burst + burstInc;
}
void DrainAnimation::handleEvent(const InputEvent& event) {
if (event.intent == InputEvent::SetColor) {
m_fillColor = event.asRGB();
} else if (event.intent == InputEvent::Acceleration) {
m_pos += log10(event.asInt());
uint16_t burstInc = event.asInt() / 6;
m_burst = (m_burst > 0xFFFF - burstInc) ? 0xFFFF : m_burst + burstInc;
}
}
void DrainAnimation::render(Display* dpy) const {
dpy->clear();
Surface leftPanel{dpy, {0, 0}, {128, 0}};
Surface rightPanel{dpy, {128, 0}, {255, 0}};
fillRange(dpy, leftPanel.start, leftPanel.end, rgb2hsv_approximate(m_fillColor));
fillRange(dpy, rightPanel.end, rightPanel.start, rgb2hsv_approximate(m_fillColor));
}
void DrainAnimation::fillRange(Display* dpy, const PhysicalCoordinates &start, const PhysicalCoordinates& end, const CHSV &baseColor) const {
int length = end.x - start.x;
int direction = 1;
if (length < 0) {
direction = -1;
}
AnimatedRGB m_fillColor;
uint8_t frac = length == 0 ? 0 : 255 / std::abs(length);
for(int i = 0; i < std::abs(length); i++) {
auto coords = PhysicalCoordinates((start.x + (i * direction)), 0);
void render(Display* dpy) const override {
dpy->clear();
Surface leftPanel{dpy, {0, 0}, {128, 0}};
Surface rightPanel{dpy, {128, 0}, {255, 0}};
fillRange(dpy, leftPanel.start, leftPanel.end, rgb2hsv_approximate(m_fillColor));
fillRange(dpy, rightPanel.end, rightPanel.start, rgb2hsv_approximate(m_fillColor));
const uint8_t localScale = inoise8(i * 80, m_pos * 3);
const uint8_t dimPosition = lerp8by8(50, 190, scale8(sin8((frac * i) / 2), localScale));
const uint8_t withBurst = ease8InOutCubic(lerp16by16(dimPosition, 255, m_burst));
auto scaledColor = CHSV(baseColor.hue, lerp8by8(100, 255, localScale), withBurst);
CRGB src(dpy->pixelAt(coords));
dpy->pixelAt(coords) = blend(scaledColor, src, 200);
}
}
void fillRange(Display* dpy, const PhysicalCoordinates &start, const PhysicalCoordinates& end, const CHSV &baseColor) const {
int length = end.x - start.x;
int direction = 1;
if (length < 0) {
direction = -1;
}
uint8_t frac = 255 / std::abs(length);
for(int i = 0; i < std::abs(length); i++) {
auto coords = PhysicalCoordinates((start.x + (i * direction)), 0);
const uint8_t localScale = inoise8(i * 80, m_pos * 3);
const uint8_t dimPosition = lerp8by8(50, 190, scale8(sin8((frac * i) / 2), localScale));
const uint8_t withBurst = ease8InOutCubic(lerp16by16(dimPosition, 255, m_burst));
auto scaledColor = CHSV(baseColor.hue, lerp8by8(100, 255, localScale), withBurst);
CRGB src(dpy->pixelAt(coords));
dpy->pixelAt(coords) = blend(scaledColor, src, 200);
}
}
uint16_t m_pos;
uint16_t m_burst;
};
STATIC_ALLOC(DrainAnimation);
STATIC_TASK(DrainAnimation);

View File

@ -1,63 +1,16 @@
#pragma once
#include <Figments.h>
#include <ArduinoLog.h>
class DrainAnimation: public Figment {
public:
DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {}
void loop() override {
EVERY_N_MILLISECONDS(8) {
m_pos++;
m_fillColor.update();
}
EVERY_N_MILLISECONDS(50) {
if (random(255) >= 10) {
m_burst -= m_burst / 10;
}
}
}
void handleEvent(const InputEvent& event) override {
if (event.intent == InputEvent::SetColor) {
m_fillColor = event.asRGB();
} else if (event.intent == InputEvent::Acceleration) {
m_pos += log10(event.asInt());
uint16_t burstInc = event.asInt() / 6;
m_burst = (m_burst > 0xFFFF - burstInc) ? 0xFFFF : m_burst + burstInc;
}
}
DrainAnimation();
void loop() override;
void handleEvent(const InputEvent& event) override;
void render(Display* dpy) const override;
private:
AnimatedRGB m_fillColor;
void render(Display* dpy) const override {
dpy->clear();
Surface leftPanel{dpy, {0, 0}, {128, 0}};
Surface rightPanel{dpy, {128, 0}, {255, 0}};
fillRange(dpy, leftPanel.start, leftPanel.end, rgb2hsv_approximate(m_fillColor));
fillRange(dpy, rightPanel.end, rightPanel.start, rgb2hsv_approximate(m_fillColor));
}
void fillRange(Display* dpy, const PhysicalCoordinates &start, const PhysicalCoordinates& end, const CHSV &baseColor) const {
int length = end.x - start.x;
int direction = 1;
if (length < 0) {
direction = -1;
}
uint8_t frac = 255 / std::abs(length);
for(int i = 0; i < std::abs(length); i++) {
auto coords = PhysicalCoordinates((start.x + (i * direction)), 0);
const uint8_t localScale = inoise8(i * 80, m_pos * 3);
const uint8_t dimPosition = lerp8by8(50, 190, scale8(sin8((frac * i) / 2), localScale));
const uint8_t withBurst = ease8InOutCubic(lerp16by16(dimPosition, 255, m_burst));
auto scaledColor = CHSV(baseColor.hue, lerp8by8(100, 255, localScale), withBurst);
CRGB src(dpy->pixelAt(coords));
dpy->pixelAt(coords) = blend(scaledColor, src, 200);
}
}
void fillRange(Display* dpy, const PhysicalCoordinates &start, const PhysicalCoordinates& end, const CHSV &baseColor) const;
uint16_t m_pos;
uint16_t m_burst;
};

View File

@ -1,47 +1,33 @@
#pragma once
#include "Flashlight.h"
#include "../Static.h"
#include <Figments.h>
#include "../sprites/Blob.h"
class Flashlight: public Figment {
public:
Flashlight(Task::State initialState) : Figment("Flashlight", initialState) {
m_blobs.forEach([](Blob &blob) {
blob.setHue(random(255));
blob.setSaturation(10);
blob.setPos(random(255));
});
}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::Acceleration) {
if (evt.asInt() > 10) {
m_blobs.forEach([](Blob& blob) {blob.update();});
}
Flashlight::Flashlight() : Figment("Flashlight", Task::Stopped) {
m_blobs.forEach([](Blob &blob) {
blob.setHue(random(255));
blob.setSaturation(10);
blob.setPos(random(255));
if (random(255) >= 128) {
blob.setVelocity(-1);
}
});
}
/*if (evt.intent() == InputEvent::UserInput) {
if (evt.asInt() == 1) {
m_blobs.forEach([](Blob& blob) {blob.setPos(random(255));});
}
if (evt.asInt() == 2) {
m_blobs.forEach([](Blob& chime) {blob.setPos(0);});
}
}*/
}
void loop() override {
m_blobs.update();
}
void render(Display* dpy) const override {
m_blobs.render(dpy);
for(int i = 0; i < dpy->pixelCount();i++) {
dpy->pixelAt(i) |= 100;
void Flashlight::handleEvent(const InputEvent& evt) {
if (evt.intent == InputEvent::Acceleration) {
if (evt.asInt() > 10) {
m_blobs.forEach([](Blob& blob) {blob.update();});
}
}
}
}
private:
static constexpr int blobCount = 30;
SpriteList<Blob, blobCount> m_blobs;
};
void Flashlight::loop() {
m_blobs.update();
}
void Flashlight::render(Display* dpy) const {
m_blobs.render(dpy);
Surface(dpy, {0, 0}, {255, 0}) |= 100;
}
STATIC_ALLOC(Flashlight);
STATIC_TASK(Flashlight);

View File

@ -0,0 +1,15 @@
#pragma once
#include <Figments.h>
#include "../sprites/Blob.h"
class Flashlight: public Figment {
public:
Flashlight();
void handleEvent(const InputEvent& evt) override;
void loop() override;
void render(Display* dpy) const override;
private:
static constexpr int blobCount = 30;
SpriteList<Blob, blobCount> m_blobs;
};

View File

@ -1,42 +0,0 @@
#include <Figments.h>
template<uint8_t MaxBrightness = 255, uint32_t MaxMilliAmps = 500, uint32_t Voltage = 5>
class Power: public Figment {
public:
Power() : Figment("Power") {}
void handleEvent(const InputEvent& evt) override {
switch (evt.intent) {
case InputEvent::PowerToggle:
m_powerState = m_powerState.value() <= 128 ? 255 : 0;
//Log.info("POWER TOGGLE %d", m_powerState.value());
break;
case InputEvent::SetPower:
m_powerState = evt.asInt() == 0 ? 0 : 255;
break;
case InputEvent::SetBrightness:
m_brightness = evt.asInt();
default:
return;
}
}
void loop() override {
m_powerState.update();
m_brightness.update();
}
void render(Display* dpy) const override {
const uint8_t clippedBrightness = min((uint8_t)m_brightness, MaxBrightness);
const uint8_t scaledBrightness = scale8(m_powerState, clippedBrightness);
const uint8_t videoBrightness = brighten8_video(scaledBrightness);
const uint8_t powerBrightness = calculate_max_brightness_for_power_mW(videoBrightness, Watts);
FastLED.setBrightness(powerBrightness);
}
static constexpr uint32_t Watts = Voltage * MaxMilliAmps;
private:
AnimatedNumber m_powerState = 255;
AnimatedNumber m_brightness = MaxBrightness;
};

54
src/animations/Power.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include <Figments.h>
template<uint8_t MaxBrightness = 255, uint32_t MaxMilliAmps = 500, uint32_t Voltage = 5>
class Power: public Figment {
public:
Power() : Figment("Power") {}
void handleEvent(const InputEvent& evt) override {
switch (evt.intent) {
case InputEvent::PowerToggle:
m_powerState = m_powerState.value() <= 128 ? 255 : 0;
//Log.info("POWER TOGGLE %d", m_powerState.value());
break;
case InputEvent::SetPower:
m_powerState = evt.asInt() == 0 ? 0 : 255;
Log.notice("Power is now %d", m_powerState);
break;
case InputEvent::SetBrightness:
m_brightness = evt.asInt();
m_brightness = 255;
break;
case InputEvent::Beat:
m_beatDecay.set(0, 255);
break;
default:
return;
}
}
void loop() override {
m_powerState.update();
m_brightness.update();
EVERY_N_MILLISECONDS(20) {
m_beatDecay.update(13);
}
}
void render(Display* dpy) const override {
const uint8_t decayedBrightness = scale8((uint8_t)m_brightness, ease8InOutCubic((uint8_t)m_beatDecay));
const uint8_t clippedBrightness = std::min(decayedBrightness, MaxBrightness);
const uint8_t scaledBrightness = scale8(m_powerState, clippedBrightness);
const uint8_t videoBrightness = brighten8_video(scaledBrightness);
const uint8_t powerBrightness = calculate_max_brightness_for_power_mW(videoBrightness, Watts);
FastLED.setBrightness(powerBrightness);
}
static constexpr uint32_t Watts = Voltage * MaxMilliAmps;
private:
AnimatedNumber m_powerState = 255;
AnimatedNumber m_brightness = MaxBrightness;
AnimatedNumber m_beatDecay = 255;
};

View File

@ -1,50 +1,72 @@
#include <Figments.h>
#include "../sprites/Blob.h"
#include "SolidAnimation.h"
#include "../Static.h"
class SolidAnimation: public Figment {
private:
AnimatedNumber m_red, m_green, m_blue;
static constexpr int blobCount = 20;
SpriteList<Blob, blobCount> m_blobs;
SolidAnimation::SolidAnimation() : Figment("Solid", Task::Stopped) {
}
public:
SolidAnimation(Task::State initialState) : Figment("Solid", initialState) {
m_blobs.forEach([](Blob& blob) {
blob.setPos(random(140));
blob.setBrightness(random(255));
if (random(255) % 2) {
blob.setVelocity(-1);
}
void SolidAnimation::randomize() {
m_isRandom = true;
m_blobs.forEach([](Blob& blob) {
blob.setPos(random(140));
blob.setBrightness(random(255));
if (random(255) % 2) {
blob.setVelocity(-1);
}
});
}
void SolidAnimation::handleEvent(const InputEvent& evt) {
if (evt.intent == InputEvent::SetColor) {
CRGB nextColor = evt.asRGB();
m_red.set(nextColor.red);
m_green.set(nextColor.green);
m_blue.set(nextColor.blue);
m_changePct.set(0, 255);
m_prevColor = m_curColor;
m_curColor = nextColor;
m_horizontal = !m_horizontal;
} else if (evt.intent == InputEvent::Beat) {
m_isRandom = false;
}
}
void SolidAnimation::loop() {
if (!m_isRandom) {
randomize();
}
m_red.update(15);
m_green.update(15);
m_blue.update(15);
EVERY_N_MILLIS(16) {
m_changePct.update(12);
}
EVERY_N_MILLIS(6) {
CRGB rgb{m_red, m_green, m_blue};
CHSV hsv = rgb2hsv_approximate(rgb);
m_blobs.forEach([=](Blob& blob) {
blob.setHue(hsv.hue);
blob.setSaturation(hsv.saturation);
});
m_blobs.update();
}
}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::SetColor) {
CRGB nextColor = evt.asRGB();
m_red.set(nextColor.red);
m_green.set(nextColor.green);
m_blue.set(nextColor.blue);
}
}
#include <Perfcounter.h>
void loop() override {
m_red.update();
m_green.update();
m_blue.update();
EVERY_N_MILLIS(6) {
CRGB rgb{m_red, m_green, m_blue};
CHSV hsv = rgb2hsv_approximate(rgb);
m_blobs.forEach([=](Blob& blob) {
blob.setHue(hsv.hue);
blob.setSaturation(hsv.saturation);
});
m_blobs.update();
}
void SolidAnimation::render(Display* dpy) const {
PerfCounter _("solidRender");
CRGB color(m_red.value(), m_green.value(), m_blue.value());
uint8_t frame = ease8InOutApprox(m_changePct);
if (frame == 255) {
Surface(dpy, {0, 0}, {255, 255}) = color;
} else {
uint8_t cutoff = (frame / 2);
uint8_t rotation = m_horizontal ? 0 : 128;
Surface(dpy, {0, 0}, {128 - cutoff, 255}, rotation) = m_prevColor;
Surface(dpy, {128 - cutoff, 0}, {128 + cutoff, 255}, rotation) = color;
Surface(dpy, {128 + cutoff, 0}, {255, 255}, rotation) = m_prevColor;
}
void render(Display* dpy) const override {
CRGB color(m_red.value(), m_green.value(), m_blue.value());
Surface(dpy, {0, 0}, {255, 0}) = color;
m_blobs.render(dpy);
}
};
m_blobs.render(dpy);
}
STATIC_ALLOC(SolidAnimation);
STATIC_TASK(SolidAnimation);

View File

@ -0,0 +1,21 @@
#pragma once
#include <Figments.h>
#include "../sprites/Blob.h"
class SolidAnimation: public Figment {
private:
AnimatedNumber m_red, m_green, m_blue, m_changePct;
CRGB m_curColor;
CRGB m_prevColor;
static constexpr int blobCount = 20;
SpriteList<Blob, blobCount> m_blobs;
void randomize();
bool m_isRandom = false;
bool m_horizontal = false;
public:
SolidAnimation();
void handleEvent(const InputEvent& evt) override;
void loop() override;
void render(Display* dpy) const override;
};

View File

@ -32,3 +32,4 @@ UpdateStatus::render(Display* dpy) const
}
STATIC_ALLOC(UpdateStatus);
STATIC_TASK(UpdateStatus);

View File

@ -1,3 +1,4 @@
#pragma once
#include <Figments.h>
class UpdateStatus: public Figment {

View File

@ -39,3 +39,4 @@ Buttons::read()
}
STATIC_ALLOC(Buttons);
STATIC_TASK(Buttons);

View File

@ -21,6 +21,14 @@ public:
return InputEvent{};
}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::Beat) {
m_idx %= m_colors.size();
m_reset = true;
m_idx++;
}
}
void onStart() override {
m_reset = true;
}

View File

@ -63,3 +63,4 @@ MPU5060::read()
}
STATIC_ALLOC(MPU5060);
STATIC_TASK(MPU5060);

View File

@ -5,8 +5,7 @@
#ifndef PLATFORM_PHOTON
#include <ArduinoLog.h>
#include <NTP.h>
#endif
#endif // !PLATFORM_PHOTON
#include "Platform.h"
@ -16,29 +15,22 @@
#include "Sequencer.h"
#include "LogService.h"
#include "animations/Power.cpp"
#include "animations/SolidAnimation.cpp"
#include "animations/Chimes.cpp"
#include "animations/Flashlight.cpp"
#include "animations/Drain.cpp"
#include <time.h>
#include "animations/Power.h"
#include "animations/SolidAnimation.h"
#include "animations/Chimes.h"
#include "animations/Flashlight.h"
#include "animations/Drain.h"
#include "animations/UpdateStatus.h"
#include "inputs/ColorCycle.h"
#include "inputs/Buttons.h"
#include "inputs/MPU6050.h"
#ifdef PLATFORM_PHOTON
#include "platform/particle/inputs/Photon.h"
#include "platform/particle/inputs/CloudStatus.h"
#include "platform/particle/PhotonTelemetry.h"
#include "platform/particle/WebTelemetry.cpp"
#include "platform/particle/MDNSService.cpp"
#else
#include "WiFiTask.h"
#include "platform/arduino/BluetoothSerialTelemetry.h"
#include "platform/arduino/MQTTTelemetry.h"
#include <ArduinoOTA.h>
#endif
#endif // PLATFORM_PHOTON
//SerialLogHandler logHandler;
@ -51,6 +43,7 @@
// Enable system thread, so rendering happens while booting
//SYSTEM_THREAD(ENABLED);
// Setup FastLED and the display
CRGB leds[HardwareConfig::MAX_LED_NUM];
Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()->coordMap());
@ -58,7 +51,9 @@ Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()
// Setup power management
Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power;
FigmentFunc configDisplay([](Display* dpy) {
REGISTER_TASK(power);
/*FigmentFunc configDisplay([](Display* dpy) {
uint8_t brightness = brighten8_video(beatsin8(60));
auto coords = Static<ConfigService>::instance()->coordMap();
for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) {
@ -68,7 +63,7 @@ FigmentFunc configDisplay([](Display* dpy) {
dpy->pixelAt(i) += CRGB(255 - brightness, 255 - brightness, 255 - brightness);
}
}
});
});*/
class InputBlip: public Figment {
public:
@ -94,42 +89,7 @@ private:
};
InputBlip inputBlip;
class ArduinoOTAUpdater : public BufferedInputSource {
public:
ArduinoOTAUpdater() : BufferedInputSource("ArduinoOTA") {
ArduinoOTA.onStart(&ArduinoOTAUpdater::s_onStart);
ArduinoOTA.onProgress(&ArduinoOTAUpdater::s_onProgress);
}
void loop() override {
if (m_online) {
ArduinoOTA.handle();
}
BufferedInputSource::loop();
}
void handleEvent(const InputEvent& evt) {
if (evt.intent == InputEvent::NetworkStatus && evt.asInt()) {
Log.notice("Booting OTA");
m_online = true;
ArduinoOTA.begin();
}
}
private:
bool m_online = false;
static void s_onStart() {
Log.notice("OTA Start!");
Static<ArduinoOTAUpdater>::instance()->setEvent(InputEvent::FirmwareUpdate);
}
static void s_onProgress(unsigned int progress, unsigned int total) {
Log.notice("OTA Progress! %d / %d", progress, total);
Static<ArduinoOTAUpdater>::instance()->setEvent(InputEvent{InputEvent::FirmwareUpdate, progress});
}
};
STATIC_ALLOC(ArduinoOTAUpdater);
REGISTER_TASK(inputBlip);
InputFunc randomPulse([]() {
static unsigned int pulse = 0;
@ -145,7 +105,9 @@ InputFunc randomPulse([]() {
}
}
return InputEvent{};
}, "Pulse", Task::Running);
}, "Pulse", Task::Stopped);
REGISTER_TASK(randomPulse);
InputMapper keyMap([](const InputEvent& evt) {
if (evt.intent == InputEvent::UserInput) {
@ -167,38 +129,96 @@ InputMapper keyMap([](const InputEvent& evt) {
return InputEvent::None;
}, "Keymap");
ChimesAnimation chimes{Task::Stopped};
SolidAnimation solid{Task::Running};
DrainAnimation drain{Task::Stopped};
Flashlight flashlight{Task::Stopped};
REGISTER_TASK(keyMap);
#ifndef DEFAULT_PATTERN_INDEX
#define DEFAULT_PATTERN_INDEX 0
#endif
Sequencer sequencer{{
{"Idle", {"Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"}},
{"Acid", {"Chimes", "Pulse", "MPU5060", "IdleColors", "Rainbow"}},
{"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}},
{"Interactive", {"Drain", "MPU5060", "CircadianRhythm"}},
{"Flashlight", {"Flashlight"}},
{"Gay", {"Solid", "Pulse", "Rainbow", "Rainbow"}},
{"Acid", {"Chimes", "Pulse", "MPU5060", "IdleColors", "Rainbow"}},
}};
{"Gay", {"Solid", "Pulse", "Rainbow"}},
}, DEFAULT_PATTERN_INDEX};
REGISTER_TASK(sequencer);
class BPM : public InputSource {
public:
BPM() : InputSource("BPM") {}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::BeatDetect) {
m_nextBpm = millis();
m_timings.insert(millis());
Log.notice("%d timings", m_timings.size());
if (m_timings.size() >= 5) {
updateBPM();
}
}
}
InputEvent read() override {
if (m_bpm > 0) {
uint16_t now = millis();
if (now >= m_nextBpm) {
m_nextBpm += m_bpm;
return InputEvent{InputEvent::Beat, m_bpm};
}
if (now >= m_nextLearn && m_nextLearn != 0) {
m_timings.clear();
m_nextLearn = 0;
}
}
return InputEvent{};
}
private:
uint16_t m_bpm = 0;
uint16_t m_nextBpm = 0;
uint16_t m_nextLearn = 0;
Ringbuf<uint16_t, 7> m_timings;
void updateBPM() {
uint16_t avgDelta = 0;
for(uint8_t i = 0; i < m_timings.size() - 1; i++) {
uint16_t delta = m_timings.peek(i+1) - m_timings.peek(i);
Log.notice("Timing %d Delta %d", m_timings.peek(i), delta);
avgDelta += delta;
}
m_bpm = avgDelta / 4;
m_nextLearn = m_bpm * 5 + millis();
Log.notice("BPM is now %d", m_bpm);
uint16_t trash;
m_timings.take(trash);
}
};
STATIC_ALLOC(BPM);
STATIC_TASK(BPM);
// Render all layers to the displays
Renderer renderer{
{&dpy},
{
&chimes,
&drain,
&solid,
&flashlight,
Static<ChimesAnimation>::instance(),
Static<DrainAnimation>::instance(),
Static<SolidAnimation>::instance(),
Static<Flashlight>::instance(),
Static<UpdateStatus>::instance(),
&inputBlip,
&power,
}
};
REGISTER_TASK(renderer);
Renderer configRenderer{
{&dpy},
{&drain, &configDisplay, &inputBlip, &power}
{Static<DrainAnimation>::instance(), /*&configDisplay,*/ &inputBlip, &power}
};
// Cycle some random colors
@ -209,7 +229,9 @@ ColorSequenceInput<9> idleCycle{{
CRGB(128, 0, 128), // Purple
CRGB(255, 255, 255), // White
CRGB(0, 255, 255), // Cyan
}, "IdleColors", Task::Running};
}, "IdleColors", Task::Stopped};
REGISTER_TASK(idleCycle);
ColorSequenceInput<7> rainbowCycle{{
CRGB(255, 0, 0), // Red
@ -219,7 +241,9 @@ ColorSequenceInput<7> rainbowCycle{{
CRGB(128, 0, 128), // Purple
}, "Rainbow", Task::Stopped};
struct ConfigInputTask: public BufferedInputSource {
REGISTER_TASK(rainbowCycle);
/*struct ConfigInputTask: public BufferedInputSource {
public:
ConfigInputTask() : BufferedInputSource("ConfigInput") {}
@ -292,7 +316,7 @@ private:
return InputEvent::None;
}
}
};
};*/
struct ScheduleEntry {
uint8_t hour;
@ -368,9 +392,13 @@ class CircadianRhythm : public InputSource {
uint8_t minute = 0;
needsUpdate = false;
struct tm timeinfo;
Platform::getLocalTime(&timeinfo);
hour = timeinfo.tm_hour;
minute = timeinfo.tm_min;
if (Platform::getLocalTime(&timeinfo)) {
hour = timeinfo.tm_hour;
minute = timeinfo.tm_min;
} else {
hour = 0;
minute = 0;
}
Log.notice("Current time: %d:%d", hour, minute);
return InputEvent{InputEvent::SetBrightness, brightnessForTime(hour, minute)};
}
@ -379,20 +407,16 @@ class CircadianRhythm : public InputSource {
};
STATIC_ALLOC(CircadianRhythm);
STATIC_TASK(CircadianRhythm);
// A special mainloop app for configuring hardware settings that reboots the
// device when the user is finished.
MainLoop configApp{{
/*MainLoop configApp{{
Static<Platform>::instance(),
// Manage read/write of configuration data
Static<ConfigService>::instance(),
#ifdef PLATFORM_PHOTON
// Update photon telemetry
Static<PhotonTelemetry>::instance(),
#endif
// Read hardware inputs
Static<Buttons>::instance(),
@ -408,12 +432,14 @@ MainLoop configApp{{
&inputBlip,
// Render it all
&configRenderer,
}};
}};*/
MainLoop configApp{std::vector<Task*>()};
TaskFunc safeModeNag([]{
static uint8_t frame = 0;
EVERY_N_SECONDS(30) {
Log.notice("I am running in safe mode!");
Log.fatal("I am running in safe mode!");
}
EVERY_N_MILLISECONDS(16) {
frame++;
@ -430,149 +456,72 @@ TaskFunc safeModeNag([]{
}
});
MainLoop safeModeApp({
#ifdef CONFIG_WIFI
#include "platform/arduino/WiFiTask.h"
#endif // CONFIG_WIFI
#ifdef CONFIG_OTA
#include "platform/arduino/OTA.h"
#endif // CONFIG_OTA
#ifdef CONFIG_MQTT
#include "platform/arduino/MQTTTelemetry.h"
#endif // CONFIG_MQTT
MainLoop safeModeApp{{
Static<Platform>::instance(),
// System logging
Static<LogService>::instance(),
&safeModeNag,
#ifdef CONFIG_WIFI
// ESP Wifi
Static<WiFiTask>::instance(),
// System logging
Static<LogService>::instance(),
#endif // CONFIG_WIFI
#ifdef CONFIG_MQTT
// MQTT
Static<MQTTTelemetry>::instance(),
#endif // CONFIG_MQTT
#ifdef CONFIG_OTA
// OTA Updates
Static<ArduinoOTAUpdater>::instance(),
&safeModeNag,
});
// Turn on,
MainLoop renderbugApp{{
Static<Platform>::instance(),
// Load/update graphics configuration from EEPROM
Static<ConfigService>::instance(),
// Platform inputs
// TODO: Merge cloud and esp wifi tasks into a common networking base
#ifdef PLATFORM_PHOTON
// Particle cloud status
Static<CloudStatus>::instance(),
// Monitor network state and provide particle API events
Static<PhotonInput>::instance(),
#else
// ESP Wifi
//Static<WiFiTask>::instance(),
#endif
#ifdef BOARD_ESP32
// ESP32 Bluetooth
Static<BluetoothSerialTelemetry>::instance(),
#endif
// System logging
Static<LogService>::instance(),
#ifdef CONFIG_MPU5060
// Hardware drivers
Static<MPU5060>::instance(),
#endif
#ifdef CONFIG_BUTTONS
Static<Buttons>::instance(),
// Map buttons to events
&keyMap,
#endif
// Pattern sequencer
&sequencer,
// Daily rhythm activities
Static<CircadianRhythm>::instance(),
// Periodic motion input
//&randomPulse,
// Periodic color inputs
&idleCycle,
&rainbowCycle,
// Animations
&chimes,
&drain,
&solid,
&flashlight,
// Update UI layer
&power,
Static<UpdateStatus>::instance(),
&inputBlip,
// Render everything
&renderer,
// Platform telemetry
// TODO: Combine some of these services into a unified telemetry API with
// platform-specific backends?
// Or at least, just the MQTT and watchdog ones.
#ifdef PLATFORM_PHOTON
// Update photon telemetry
Static<PhotonTelemetry>::instance(),
// Web telemetry UI
Static<WebTelemetry>::instance(),
// MQTT telemetry
Static<MQTTTelemetry>::instance(),
// Network discovery
Static<MDNSService>::instance(),
//Watchdog
Static<Watchdog>::instance(),
#else
// MQTT
Static<MQTTTelemetry>::instance(),
// OTA Updates
Static<ArduinoOTAUpdater>::instance(),
#endif
#endif // CONFIG_OTA
}};
MainLoop &runner = renderbugApp;
MainLoop* runner = &safeModeApp;
// Tune in,
void setup() {
// Turn on,
Platform::preSetup();
#ifdef CONFIG_MQTT
Static<MQTTTelemetry>::instance()->setSequencer(&sequencer);
#endif // CONFIG_MQTT
Log.notice(u8"🐛 Booting Renderbug!");
Log.notice(u8"🐞 I am built for %d LEDs running on %dmA", HardwareConfig::MAX_LED_NUM, PSU_MILLIAMPS);
Log.notice(u8"📡 Platform %s version %s", Platform::name(), Platform::version());
Platform::bootSplash();
Log.notice(u8"Setting timezone to -7 (PST)");
Platform::setTimezone(-7);
Log.notice(u8" Setting up platform...");
Platform::setup();
Platform::bootSplash();
Log.notice(u8"💡 Starting FastLED...");
Platform::addLEDs(leds, HardwareConfig::MAX_LED_NUM);
runner = new MainLoop{std::vector<Task*>{Platform::beginTasks(), Platform::endTasks()}};
// Tune in,
if (Platform::bootopts.isSafeMode) {
Log.notice(u8"⚠️ Starting Figment in safe mode!!!");
runner = safeModeApp;
runner = &safeModeApp;
FastLED.showColor(CRGB(5, 0, 0));
FastLED.show();
} else if (Platform::bootopts.isSetup) {
Log.notice(u8"🔧 Starting Figment in configuration mode...");
runner = configApp;
//runner = &configApp;
} else {
Log.notice(u8"🌌 Starting Figment...");
}
Serial.flush();
runner.start();
runner->start();
//Log.info(u8"💽 %lu bytes of free RAM", System.freeMemory());
Log.notice(u8"🚀 Setup complete! Ready to rock and roll.");
@ -581,6 +530,8 @@ void setup() {
// Drop out.
void loop() {
//Platform::loop();
runner.loop();
EVERY_N_SECONDS(5) {
Log.notice("FPS: %d", FastLED.getFPS());
}
runner->loop();
}

View File

@ -26,7 +26,7 @@ BluetoothSerialTelemetry::read()
// Overwrite the '*' character, to leave us with a complete command
commandBuf[cmdSize-1] = 0;
//Log.notice("Bluetooth read %s", commandBuf);
Log.verbose("Bluetooth read %s", commandBuf);
if (commandBuf[0] == 'R') {
m_color = CRGB(std::atoi(&commandBuf[1]), m_color.g, m_color.b);
@ -49,6 +49,12 @@ BluetoothSerialTelemetry::read()
return InputEvent{InputEvent::SetPower, 0};
} else if (commandBuf[0] == 'p') {
return InputEvent{InputEvent::SetPattern, &commandBuf[1]};
} else if (commandBuf[0] == 'b') {
return InputEvent::BeatDetect;
} else if (commandBuf[0] == 'c') {
return InputEvent{InputEvent::SetDisplayLength, std::atoi(&commandBuf[1])};
} else if (commandBuf[0] == 'Y') {
return InputEvent::SaveConfigurationRequest;
} else if (commandBuf[0] == 'A') {
char* axisVal = strtok(&commandBuf[1], ",");
const uint8_t accelX = std::atof(axisVal) * 10;
@ -75,12 +81,13 @@ BluetoothSerialTelemetry::read()
void
BluetoothSerialTelemetry::onStart()
{
Log.notice("Starting up Bluetooth...");
Log.trace("Starting up Bluetooth...");
if (m_serial.begin(Platform::deviceName())) {
Log.notice("Bluetooth started!");
Log.notice("Bluetooth started! Device name is %s", Platform::deviceName());
} else {
Log.warning("Bluetooth could not be started!");
}
}
STATIC_ALLOC(BluetoothSerialTelemetry);
STATIC_TASK(BluetoothSerialTelemetry);

View File

@ -117,39 +117,37 @@ MQTTTelemetry::handleEventOnline(const InputEvent& evt)
Log.notice("Connected to MQTT");
m_needHeartbeat = true;
StaticJsonDocument<1024> configJson;
Lightswitch.toJson(configJson);
m_json.clear();
Lightswitch.toJson(m_json);
int i = 0;
for(const Sequencer::Scene& scene : m_sequencer->scenes()) {
configJson["fx_list"][i++] = scene.name;
m_json["fx_list"][i++] = scene.name;
}
configJson["brightness"] = true;
configJson["rgb"] = true;
m_json["brightness"] = true;
m_json["rgb"] = true;
char buf[1024];
serializeJson(configJson, buf, sizeof(buf));
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);
//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());
configJson.clear();
flashlightSwitch.toJson(configJson, false);
configJson["cmd_t"] = "~/set";
configJson["ret"] = true;
serializeJson(configJson, buf, sizeof(buf));
m_mqtt.publish(flashlightSwitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
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());
configJson.clear();
FPSSensor.toJson(configJson, false);
configJson["unit_of_meas"] = "Frames/s";
serializeJson(configJson, buf, sizeof(buf));
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);
//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
@ -172,39 +170,28 @@ MQTTTelemetry::handleEventOnline(const InputEvent& evt)
String flashlightStatTopic = flashlightSwitch.statTopic();
m_mqtt.publish(flashlightStatTopic.c_str(), "ON");
} else if (evt.intent == InputEvent::SetPower) {
StaticJsonDocument<256> doc;
char buf[256];
m_json.clear();
m_isOn = evt.asInt() ? true : false;
doc["state"] = m_isOn ? "ON" : "OFF";
serializeJson(doc, buf, sizeof(buf));
m_mqtt.publish(statTopic.c_str(), buf);
m_json["state"] = m_isOn ? "ON" : "OFF";
publishDoc(statTopic.c_str());
} else if (evt.intent == InputEvent::SetBrightness) {
StaticJsonDocument<256> doc;
char buf[256];
doc["brightness"] = evt.asInt();
doc["state"] = m_isOn ? "ON" : "OFF";
serializeJson(doc, buf, sizeof(buf));
m_mqtt.publish(statTopic.c_str(), buf);
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) {
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"] = m_isOn ? "ON" : "OFF";
serializeJson(doc, buf, sizeof(buf));
m_mqtt.publish(statTopic.c_str(), buf);
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) {
StaticJsonDocument<256> doc;
char buf[256];
doc["effect"] = evt.asString();
doc["state"] = m_isOn ? "ON" : "OFF";
serializeJson(doc, buf, sizeof(buf));
m_mqtt.publish(statTopic.c_str(), buf);
} else if (evt.intent == InputEvent::FirmwareUpdate) {
String updateTopic = m_debugTopic + "/firmware";
m_mqtt.publish(updateTopic.c_str(), "firmware update!");
m_json.clear();
m_json["effect"] = evt.asString();
m_json["state"] = m_isOn ? "ON" : "OFF";
publishDoc(statTopic.c_str());
}
}
}
@ -241,22 +228,20 @@ MQTTTelemetry::loopOnline()
m_needHeartbeat = true;
}
if (m_needHeartbeat) {
char buf[512];
StaticJsonDocument<512> response;
response["device_id"] = Platform::deviceID();
response["sketch_version"] = ESP.getSketchMD5();
response["os_version"] = ESP.getSdkVersion();
response["localip"] = WiFi.localIP().toString();
response["pixelCount"] = Static<ConfigService>::instance()->coordMap()->pixelCount;
response["startPixel"] = Static<ConfigService>::instance()->coordMap()->startPixel;
response["RSSI"] = WiFi.RSSI();
response["free_ram"] = ESP.getFreeHeap();
response["fps"] = FastLED.getFPS();
serializeJson(response, buf, sizeof(buf));
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["startPixel"] = Static<ConfigService>::instance()->coordMap()->startPixel;
m_json["RSSI"] = WiFi.RSSI();
m_json["free_ram"] = ESP.getFreeHeap();
m_json["fps"] = FastLED.getFPS();
String availTopic = m_rootTopic + "/available";
m_mqtt.publish(Lightswitch.heartbeatTopic().c_str(), buf);
publishDoc(Lightswitch.heartbeatTopic().c_str());
m_mqtt.publish(Device.availabilityTopic.c_str(), "online");
//Log.notice("Heartbeat: %s", buf);
//Log.trace("Heartbeat: %s", buf);
String fpsCounter = String(FastLED.getFPS());
m_mqtt.publish(FPSSensor.statTopic().c_str(), fpsCounter.c_str());
@ -280,89 +265,106 @@ MQTTTelemetry::callback(char* topic, const char* payload)
setEvent(InputEvent{InputEvent::SetPattern, "Idle"});
}
} else if (Lightswitch.isCommandTopic(topic)) {
StaticJsonDocument<512> doc;
deserializeJson(doc, payload);
deserializeJson(m_json, payload);
if (doc.containsKey("state")) {
if (doc["state"] == "ON") {
if (m_json.containsKey("state")) {
if (m_json["state"] == "ON") {
Log.notice("Turning on power");
setEvent(InputEvent{InputEvent::SetPower, true});
} else if (doc["state"] == "OFF") {
} else if (m_json["state"] == "OFF") {
Log.notice("Turning off power");
setEvent(InputEvent{InputEvent::SetPattern, "Idle"});
setEvent(InputEvent{InputEvent::SetPower, false});
}
}
if (doc.containsKey("start")) {
strcpy(m_patternBuf, doc["start"].as<const char*>());
if (m_json.containsKey("start")) {
strcpy(m_patternBuf, m_json["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.");
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, doc["stop"].as<const char*>());
strcpy(m_patternBuf, m_json["stop"].as<const char*>());
setEvent(InputEvent{InputEvent::StopThing, m_patternBuf});
}
}
if (doc.containsKey("pixelCount")) {
setEvent(InputEvent{InputEvent::SetDisplayLength, (int)doc["pixelCount"]});
if (m_json.containsKey("pixelCount")) {
Log.notice("Pixel count changed");
setEvent(InputEvent{InputEvent::SetDisplayLength, m_json["pixelCount"].as<int>()});
}
if (doc.containsKey("startPixel")) {
setEvent(InputEvent{InputEvent::SetDisplayOffset, (int)doc["startPixel"]});
if (m_json.containsKey("startPixel")) {
Log.notice("Start pixel changed");
setEvent(InputEvent{InputEvent::SetDisplayOffset, m_json["startPixel"].as<int>()});
}
if (doc.containsKey("save")) {
if (m_json.containsKey("save")) {
setEvent(InputEvent{InputEvent::SaveConfigurationRequest});
}
if (doc.containsKey("restart")) {
#ifdef BOARD_ESP8266
ESP.wdtDisable();
ESP.restart();
#endif
if (m_json.containsKey("restart")) {
Platform::restart();
}
if (doc.containsKey("reconnect")) {
if (m_json.containsKey("reconnect")) {
m_mqtt.disconnect();
}
if (doc.containsKey("ping")) {
if (m_json.containsKey("ping")) {
m_needHeartbeat = true;
Log.notice("Queuing up heartbeat");
}
if (doc.containsKey("effect")) {
strcpy(m_patternBuf, doc["effect"].as<const char*>());
if (m_json.containsKey("effect")) {
strcpy(m_patternBuf, m_json["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"];
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 (doc.containsKey("brightness")) {
setEvent(InputEvent{InputEvent::SetBrightness, (int)doc["brightness"]});
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)
{
char topicBuf[128];
char payloadBuf[512];
strcpy(topicBuf, topic);
memcpy(payloadBuf, payload, length);
payloadBuf[std::min(sizeof(payloadBuf) - 1, length)] = 0;
Static<MQTTTelemetry>::instance()->callback(topicBuf, payloadBuf);
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);

View File

@ -12,6 +12,7 @@
#include <WiFi.h>
#endif
#include <ArduinoJson.h>
class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
public:
@ -25,13 +26,16 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
public:
LogPrinter(MQTTTelemetry* telemetry) : telemetry(telemetry) {};
size_t write(uint8_t byte) {
char outBuf[512];
if (byte == '\n') {
size_t bufSize = buf.write(outBuf);
outBuf[std::min(sizeof(outBuf), bufSize)] = 0;
Serial.println(outBuf);
char c;
String logTopic = telemetry->m_debugTopic + "/log";
telemetry->m_mqtt.publish(logTopic.c_str(), outBuf);
telemetry->m_mqtt.beginPublish(logTopic.c_str(), buf.size(), false);
while (buf.take(c)) {
Serial.write(c);
telemetry->m_mqtt.write(c);
}
Serial.println();
telemetry->m_mqtt.endPublish();
} else {
buf.insert(byte);
}
@ -61,9 +65,15 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
char m_patternBuf[48];
bool m_needHeartbeat = false;
bool m_isOn = true;
static char s_topicBuf[128];
static char s_payloadBuf[512];
void publishDoc(const char* topic);
void publishDoc(const char* topic, bool retain);
Sequencer *m_sequencer = 0;
WiFiClient m_wifi;
PubSubClient m_mqtt;
LogPrinter m_logPrinter;
StaticJsonDocument<1024> m_json;
};

View File

@ -0,0 +1,34 @@
#include "OTA.h"
#include "../../Static.h"
ArduinoOTAUpdater::ArduinoOTAUpdater() : BufferedInputSource("ArduinoOTA") {
ArduinoOTA.onStart(&ArduinoOTAUpdater::s_onStart);
ArduinoOTA.onProgress(&ArduinoOTAUpdater::s_onProgress);
}
void ArduinoOTAUpdater::loop() {
if (m_online) {
ArduinoOTA.handle();
}
BufferedInputSource::loop();
}
void ArduinoOTAUpdater::handleEvent(const InputEvent& evt) {
if (evt.intent == InputEvent::NetworkStatus && evt.asInt()) {
Log.notice("Booting OTA");
m_online = true;
ArduinoOTA.begin();
}
}
void ArduinoOTAUpdater::s_onStart() {
Log.notice("OTA Start!");
Static<ArduinoOTAUpdater>::instance()->setEvent(InputEvent::FirmwareUpdate);
}
void ArduinoOTAUpdater::s_onProgress(unsigned int progress, unsigned int total) {
Log.notice("OTA Progress! %d / %d", progress, total);
Static<ArduinoOTAUpdater>::instance()->setEvent(InputEvent{InputEvent::FirmwareUpdate, progress});
}
STATIC_ALLOC(ArduinoOTAUpdater);
STATIC_TASK(ArduinoOTAUpdater);

View File

@ -0,0 +1,14 @@
#include <ArduinoOTA.h>
#include <Figments.h>
class ArduinoOTAUpdater : public BufferedInputSource {
public:
ArduinoOTAUpdater();
void loop() override;
void handleEvent(const InputEvent& evt) override;
private:
bool m_online = false;
static void s_onStart();
static void s_onProgress(unsigned int progress, unsigned int total);
};

View File

@ -0,0 +1,232 @@
#include <Figments.h>
#include <U8g2lib.h>
#include "../../Static.h"
#include <ArduinoLog.h>
#include "../../LogService.h"
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, 16, 15, 4);
class U8Display : public Task {
public:
U8Display() : Task("U8Display") {}
enum ScreenState {
BootSplash,
Running,
Message,
Idle = Running
};
void onStart() {
xTaskCreatePinnedToCore(
&U8Display::redrawTask,
name,
2000,
this,
1,
&m_renderTask, 0
);
}
void handleEvent(const InputEvent& evt) {
m_lastEvent = evt;
if (m_state == Idle) {
switch(evt.intent) {
case InputEvent::NetworkStatus:
m_nextState = Message;
m_message = evt.asBool() ? "Online!" : "Offline :[";
break;
case InputEvent::SetPattern:
m_nextState = Message;
m_message = "Pattern: " + String(evt.asString());
break;
case InputEvent::SetPower:
m_nextState = Message;
m_message = evt.asBool() ? "Power On" : "Power Off";
break;
case InputEvent::SaveConfigurationRequest:
m_nextState = Message;
m_message = "Settings Saved!";
break;
case InputEvent::FirmwareUpdate:
m_nextState = Message;
m_message = "Firmware update";
break;
case InputEvent::PreviousPattern:
m_nextState = Message;
m_message = "Previous Pattern";
break;
case InputEvent::NextPattern:
m_nextState = Message;
m_message = "Next Pattern";
break;
}
}
}
void drawSplash() {
//u8g2.setFont(u8g2_font_VCR_OSD_mr);
u8g2.setFont(u8g2_font_HelvetiPixelOutline_tr);
u8g2.setDrawColor(1);
uint8_t splashWidth = u8g2.getStrWidth("Renderbug!");
u8g2.drawStr(64 - splashWidth / 2, 32 - 15, "Renderbug!");
u8g2.setFont(u8g2_font_7x13B_mr);
u8g2.setCursor(15, 64 - 7);
u8g2.print("Version ");
u8g2.print(RENDERBUG_VERSION);
for(int i = 0; i < 3; i++) {
uint8_t sparkleX = (64 - splashWidth/2) + scale8(7-i, beatsin8(40)) * (splashWidth/7) + 5;
uint8_t sparkleY = scale8(3+i, beatsin8(40)) * 3 + 7;
u8g2.setDrawColor(2);
if (beatsin8(60*4) + i * 3 >= 170) {
u8g2.drawLine(sparkleX - 3, sparkleY - 3, sparkleX + 3, sparkleY + 3);
u8g2.drawLine(sparkleX + 3, sparkleY - 3, sparkleX - 3, sparkleY + 3);
} else if (beatsin8(60*4) + i * 2 >= 82) {
u8g2.drawLine(sparkleX - 4, sparkleY - 4, sparkleX + 2, sparkleY + 2);
u8g2.drawLine(sparkleX - 2, sparkleY - 2, sparkleX + 4, sparkleY + 4);
u8g2.drawLine(sparkleX + 4, sparkleY - 4, sparkleX - 2, sparkleY + 2);
u8g2.drawLine(sparkleX + 2, sparkleY - 2, sparkleX - 4, sparkleY + 4);
} else {
u8g2.drawLine(sparkleX - 2, sparkleY, sparkleX + 2, sparkleY);
u8g2.drawLine(sparkleX, sparkleY - 2, sparkleX, sparkleY + 2);
}
}
}
void drawMessage() {
//u8g2.setFont(u8g2_font_VCR_OSD_mr);
u8g2.setFont(u8g2_font_HelvetiPixelOutline_tr);
uint8_t splashWidth = u8g2.getStrWidth(m_message.c_str());
if (splashWidth >= 128) {
u8g2.setFont(u8g2_font_7x13B_mr);
splashWidth = u8g2.getStrWidth(m_message.c_str());
}
u8g2.drawStr(64 - splashWidth / 2, 32 - 15, m_message.c_str());
}
void drawTime() {
u8g2.setFont(u8g2_font_7x13O_tn);
u8g2.setCursor(0, 64);
struct tm timeinfo;
Platform::getLocalTime(&timeinfo);
uint8_t hour = timeinfo.tm_hour;
uint8_t minute = timeinfo.tm_min;
u8g2.print(hour);
u8g2.print(":");
u8g2.print(minute);
}
void drawState(ScreenState state) {
switch(state) {
case BootSplash:
drawSplash();
break;
case Message:
drawMessage();
break;
case Running:
uint8_t y = 11;
u8g2.setFont(u8g2_font_7x13B_mr);
u8g2.setCursor(0, y);
u8g2.print("FPS: ");
u8g2.setFont(u8g2_font_7x13O_tn);
u8g2.print(FastLED.getFPS());
y += 12;
u8g2.setCursor(0, y);
u8g2.setFont(u8g2_font_7x13B_mr);
u8g2.print("Last event: ");
y += 7;
u8g2.setCursor(10, y);
u8g2.setFont(u8g2_font_4x6_tr);
const char* intentName = LogService::intentName(m_lastEvent.intent);
if (intentName) {
u8g2.print(intentName);
} else {
u8g2.print("<");
u8g2.print(m_lastEvent.intent);
u8g2.print(">");
}
y += 12;
u8g2.setCursor(15, y);
u8g2.setFont(u8g2_font_7x13O_tf);
u8g2.print(LogService::eventValue(m_lastEvent));
drawTime();
break;
}
}
void loop() {
EVERY_N_MILLISECONDS(8) {
xTaskNotifyGive(m_renderTask);
}
}
private:
ScreenState m_state = BootSplash;
ScreenState m_nextState = BootSplash;
uint8_t m_transitionFrame = 0;
uint8_t m_screenStartTime = 0;
InputEvent m_lastEvent;
String m_message;
TaskHandle_t m_renderTask;
void redraw() {
if (m_state != m_nextState) {
EVERY_N_MILLISECONDS(8) {
constexpr uint8_t speed = 11;
if (m_transitionFrame <= 255 - speed) {
m_transitionFrame += speed;
} else {
m_transitionFrame = 255;
}
uint8_t offset = ease8InOutApprox(m_transitionFrame);
u8g2.clearBuffer();
if (m_transitionFrame <= 128) {
uint8_t width = scale8(128, offset * 2);
u8g2.setDrawColor(1);
drawState(m_state);
u8g2.drawBox(0, 0, width, 64);
u8g2.setDrawColor(2);
u8g2.drawBox(width, 0, 8, 64);
} else {
uint8_t width = scale8(128, offset/2)*2;
u8g2.setDrawColor(1);
drawState(m_nextState);
u8g2.setDrawColor(2);
u8g2.drawBox(std::max(0, width - 8), 0, 8, 64);
u8g2.setDrawColor(1);
u8g2.drawBox(width, 0, 128, 64);
}
u8g2.sendBuffer();
if (m_transitionFrame == 255) {
m_state = m_nextState;
m_screenStartTime = millis();
m_transitionFrame = 0;
}
}
} else {
u8g2.clearBuffer();
drawState(m_state);
u8g2.sendBuffer();
uint16_t screenTime = millis() - m_screenStartTime;
if (screenTime >= 7000 && m_state != Idle) {
m_nextState = Idle;
}
}
}
static void redrawTask(void* data) {
u8g2.begin();
U8Display* self = static_cast<U8Display*>(data);
self->m_screenStartTime = millis();
while (true) {
self->redraw();
ulTaskNotifyTake(0, portMAX_DELAY);
}
}
};
STATIC_ALLOC(U8Display);
STATIC_TASK(U8Display);

View File

@ -39,3 +39,4 @@ WiFiTask::read()
}
STATIC_ALLOC(WiFiTask);
STATIC_TASK(WiFiTask);

View File

@ -33,3 +33,4 @@ class MDNSService : public Task {
};
STATIC_ALLOC(MDNSService);
STATIC_TASK(MDNSService);

View File

@ -202,4 +202,4 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
};
STATIC_ALLOC(MQTTTelemetry);
STATIC_TASK(MQTTTelemetry);

View File

@ -67,3 +67,4 @@ PhotonTelemetry::handleEvent(const InputEvent &evt)
}
STATIC_ALLOC(PhotonTelemetry);
STATIC_TASK(PhotonTelemetry);

View File

@ -28,3 +28,4 @@ class Watchdog : public Task {
};
STATIC_ALLOC(Watchdog);
STATIC_TASK(Watchdog);

View File

@ -154,4 +154,5 @@ class WebTelemetry : public Task {
}
};
STATIC_ALLOC(WebTelemetry);
STATIC_TASK(WebTelemetry);

View File

@ -26,3 +26,4 @@ CloudStatus::initNetwork(system_event_t event, int param) {
}
STATIC_ALLOC(CloudStatus);
STATIC_TASK(CloudStatus);

View File

@ -182,6 +182,5 @@ PhotonInput::setStartPixel(String command)
return 0;
}
STATIC_ALLOC(PhotonInput);
STATIC_TASK(PhotonInput);

View File

@ -1,9 +1,11 @@
#pragma once
#include <Figments.h>
#include <ArduinoLog.h>
#include <Perfcounter.h>
class Blob {
uint16_t m_pos;
uint8_t m_pos;
int8_t m_velocity;
uint8_t m_hue;
int16_t m_brightness;
@ -49,13 +51,26 @@ public:
}
void render(Display* display) const {
PerfCounter _("blobRender");
const uint8_t width = 25;
//Log.notice("get coords");
auto map = display->coordinateMapping();
// Grab the physical pixel we'll start with
PhysicalCoordinates startPos = map->virtualToPhysicalCoords({m_pos, 0});
PhysicalCoordinates endPos = map->virtualToPhysicalCoords({m_pos + width, 0});
uint8_t scaledWidth = std::abs(endPos.x - startPos.x);
//PhysicalCoordinates startPos = map->virtualToPhysicalCoords({m_pos, m_pos});
//PhysicalCoordinates endPos = map->virtualToPhysicalCoords({m_pos + width, m_pos});
Surface sfc{display, {m_pos, m_pos}, {qadd8(m_pos, width), qadd8(m_pos, width)}};
sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) {
uint8_t pixelMod = std::min(surfaceCoords.y, surfaceCoords.x);
pixelMod = surfaceCoords.x;
// Blobs desaturate and dim towards their edges
uint8_t saturation = lerp8by8(0, m_brightness, pixelMod);
uint8_t val = lerp8by8(0, m_brightness, pixelMod);
CHSV blobColor(m_hue, m_saturation, val);
//PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), startPos.y};
pixel += blend(CRGB(blobColor), pixel, 80);
});
/*uint8_t scaledWidth = std::abs(endPos.x - startPos.x);
//Log.notice("blob w=%d x=%d", scaledWidth, startPos.x);
for(uint8_t i = 0;i < scaledWidth; i++) {
@ -66,10 +81,10 @@ public:
//CHSV blobColor(m_hue, m_saturation, quadwave8((i / (double)scaledWidth) * m_brightness));
CHSV blobColor(m_hue, m_saturation, quadwave8(val));
PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), 0};
PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), startPos.y};
CRGB src(display->pixelAt(pos));
display->pixelAt(pos) = blend(CRGB(blobColor), src, 200);
}
display->pixelAt(pos) = blend(CRGB(blobColor), src, 140);
}*/
}
};