This commit is contained in:
Torrie Fischer 2021-03-28 14:50:31 -07:00
parent cadfd40b61
commit 92d5e73bd8
9 changed files with 141 additions and 96 deletions

View File

@ -29,19 +29,19 @@ InputSource::loop()
InputEvent InputEvent
BufferedInputSource::read() BufferedInputSource::read()
{ {
InputEvent ret = m_lastEvent; InputEvent ret;
m_lastEvent = InputEvent{}; m_eventQueue.take(ret);
return ret; return ret;
} }
void void
BufferedInputSource::setEvent(InputEvent &&evt) BufferedInputSource::setEvent(InputEvent &&evt)
{ {
m_lastEvent = std::move(evt); m_eventQueue.insert(std::move(evt));
} }
void void
BufferedInputSource::setEvent(InputEvent::Intent intent, Variant &&v) BufferedInputSource::setEvent(InputEvent::Intent intent, Variant &&v)
{ {
m_lastEvent = InputEvent{intent, std::move(v)}; m_eventQueue.insert(InputEvent{intent, std::move(v)});
} }

View File

@ -2,6 +2,7 @@
#include "application.h" #include "application.h"
#include "./Geometry.h" #include "./Geometry.h"
#include "./Figment.h" #include "./Figment.h"
#include "./Ringbuf.h"
#include "FastLED/FastLED.h" #include "FastLED/FastLED.h"
typedef Vector3d<int> MotionVec; typedef Vector3d<int> MotionVec;
@ -136,7 +137,7 @@ protected:
void setEvent(InputEvent::Intent intent, Variant &&v); void setEvent(InputEvent::Intent intent, Variant &&v);
private: private:
InputEvent m_lastEvent; Ringbuf<InputEvent, 5> m_eventQueue;
}; };
class InputMapper: public BufferedInputSource { class InputMapper: public BufferedInputSource {

View File

@ -3,6 +3,7 @@
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include "./Input.h" #include "./Input.h"
#include "./Ringbuf.h"
class Task; class Task;
class InputSource; class InputSource;
@ -50,42 +51,6 @@ struct Scheduler {
iterator end() { return iterator(*this, tasks.size()); } iterator end() { return iterator(*this, tasks.size()); }
}; };
template<typename T, int Size>
struct Ringbuf {
Ringbuf() : m_head(0), m_tail(0) {}
void clear() {
m_head = 0;
m_tail = 0;
}
bool take(T& dest) {
if (m_head == m_tail) {
return false;
}
const int cur = m_head;
const int nextHead = (m_head + 1) % Size;
m_head = nextHead;
dest = m_items[cur];
return true;
}
void insert(const T& src) {
const int cur = m_tail;
const int nextTail = (m_tail + 1) % Size;
if (nextTail == m_head) {
return;
} else {
m_tail = nextTail;
}
m_items[cur] = src;
}
private:
int m_head = 0;
int m_tail = 0;
std::array<T, Size> m_items;
};
struct MainLoop { struct MainLoop {
Scheduler scheduler; Scheduler scheduler;

View File

@ -0,0 +1,39 @@
#pragma once
#include <array>
template<typename T, int Size>
struct Ringbuf {
Ringbuf() : m_head(0), m_tail(0) {}
void clear() {
m_head = 0;
m_tail = 0;
}
bool take(T& dest) {
if (m_head == m_tail) {
return false;
}
const int cur = m_head;
const int nextHead = (m_head + 1) % Size;
m_head = nextHead;
dest = m_items[cur];
return true;
}
void insert(const T& src) {
const int cur = m_tail;
const int nextTail = (m_tail + 1) % Size;
if (nextTail == m_head) {
return;
} else {
m_tail = nextTail;
}
m_items[cur] = src;
}
private:
int m_head = 0;
int m_tail = 0;
std::array<T, Size> m_items;
};

View File

@ -36,7 +36,12 @@ Sequencer::handleEvent(const InputEvent& evt)
} else if (evt.intent == InputEvent::PreviousPattern) { } else if (evt.intent == InputEvent::PreviousPattern) {
m_idx--; m_idx--;
} else { } else {
m_idx = evt.asInt(); //m_idx = evt.asInt();
for(m_idx = 0; m_idx < m_scenes.size(); m_idx++) {
if (!strcmp(evt.asString(), m_scenes[m_idx].name)) {
break;
}
}
} }
if (m_idx < 0) { if (m_idx < 0) {

View File

@ -1,9 +1,11 @@
#include "../Figments/Figments.h" #include "../Figments/Figments.h"
using CRGB = NSFastLED::CRGB;
template<int Period> template<int Period>
class ColorSequenceInput: public InputSource { class ColorSequenceInput: public InputSource {
public: public:
ColorSequenceInput(const std::vector<NSFastLED::CRGB> &colors, const char* name, Task::State initialState) ColorSequenceInput(const std::vector<CRGB> &colors, const char* name, Task::State initialState)
: InputSource(name, initialState), m_colors(colors) {} : InputSource(name, initialState), m_colors(colors) {}
InputEvent read() override { InputEvent read() override {
@ -32,7 +34,7 @@ public:
} }
private: private:
std::vector<NSFastLED::CRGB> m_colors; std::vector<CRGB> m_colors;
int m_idx = 0; int m_idx = 0;
bool m_reset = true; bool m_reset = true;
bool m_override = false; bool m_override = false;

View File

@ -76,7 +76,7 @@ FigmentFunc configDisplay([](Display* dpy) {
class InputBlip: public Figment { class InputBlip: public Figment {
public: public:
InputBlip() : Figment("InputBlip") {} InputBlip() : Figment("InputBlip", Task::Stopped) {}
void handleEvent(const InputEvent& evt) override { void handleEvent(const InputEvent& evt) override {
if (evt.intent != InputEvent::None) { if (evt.intent != InputEvent::None) {
@ -139,13 +139,13 @@ DrainAnimation drain{Task::Stopped};
Flashlight flashlight{Task::Stopped}; Flashlight flashlight{Task::Stopped};
Sequencer sequencer{{ Sequencer sequencer{{
{"Solid", {"Solid"}}, {"Idle", {"Solid", "MPU5060", "Pulse", "Hackerbots", "Kieryn", "CircadianRhythm"}},
{"Drain", {"Drain"}}, {"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}},
{"Interactive", {"Drain", "CircadianRhythm"}},
{"Flashlight", {"Flashlight"}}, {"Flashlight", {"Flashlight"}},
{"Fiercewater", {"Solid", "Kieryn", "Hackerbots"}}, {"Nightlight", {"Drain", "Pulse", "Noisebridge"}},
{"Nightlight", {"Drain", "Noisebridge"}}, {"Gay", {"Solid", "Pulse", "Rainbow", "Hackerbots", "Kieryn"}},
{"Gay", {"Solid", "Rainbow", "Hackerbots", "Kieryn"}}, {"Acid", {"Chimes", "Pulse", "MPU5060", "Hackerbots", "Rainbow"}},
{"Acid", {"Chimes", "Hackerbots", "Rainbow"}},
}}; }};
@ -176,7 +176,7 @@ class OnlineTaskMixin : public virtual Loopable {
public: public:
void handleEvent(const InputEvent &evt) override { void handleEvent(const InputEvent &evt) override {
if (evt.intent == InputEvent::NetworkStatus) { if (evt.intent == InputEvent::NetworkStatus) {
m_online = true; m_online = evt.asInt();
} }
if (m_online) { if (m_online) {
handleEventOnline(evt); handleEventOnline(evt);
@ -199,13 +199,17 @@ class OnlineTaskMixin : public virtual Loopable {
#include "MQTT/MQTT.h" #include "MQTT/MQTT.h"
class MQTTTelemetry : public Task, OnlineTaskMixin { class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
public: public:
MQTTTelemetry() : Task("MQTT"), m_client("relay.malloc.hackerbots.net", 1883, 512, 15, MQTTTelemetry::s_callback, true) { MQTTTelemetry() : BufferedInputSource("MQTT"), m_client("relay.malloc.hackerbots.net", 1883, 512, 15, MQTTTelemetry::s_callback, true) {
s_instance = this;
strcpy(m_deviceName, System.deviceID().c_str()); strcpy(m_deviceName, System.deviceID().c_str());
} }
void loop() override {
BufferedInputSource::loop();
OnlineTaskMixin::loop();
}
void handleEventOnline(const InputEvent& event) override { void handleEventOnline(const InputEvent& event) override {
char response[255]; char response[255];
if (event.intent == InputEvent::SetPower) { if (event.intent == InputEvent::SetPower) {
@ -234,20 +238,23 @@ class MQTTTelemetry : public Task, OnlineTaskMixin {
writer.endObject(); writer.endObject();
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
m_client.publish(m_statTopic, response, MQTT::QOS1); 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 { } else {
/*root["intent"] = event.intent; if (m_lastIntent != event.intent) {
switch(event.type) { m_lastIntent = event.intent;
case InputEvent::Null: JSONBufferWriter writer(response, sizeof(response));
root["value"] = 0;break; writer.beginObject();
case InputEvent::Integer: writer.name("intent").value(event.intent);
root["value"] = event.asInt();break; writer.endObject();
case InputEvent::String: writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
root["value"] = event.asString();break; m_client.publish("renderbug/events", response, MQTT::QOS1);
case InputEvent::Color:
root["value"] = "RGB";break;
} }
//root.printTo(response, sizeof(response));
//m_client.publish("renderbug/event", response);*/
} }
} }
@ -258,7 +265,7 @@ class MQTTTelemetry : public Task, OnlineTaskMixin {
char heartbeatBuf[255]; char heartbeatBuf[255];
JSONBufferWriter writer(heartbeatBuf, sizeof(heartbeatBuf)); JSONBufferWriter writer(heartbeatBuf, sizeof(heartbeatBuf));
writer.beginObject(); writer.beginObject();
writer.name("fps").value(NSFastLED::FastLED.getFPS()); writer.name("fps").value(FastLED.getFPS());
writer.name("os_version").value(System.version()); writer.name("os_version").value(System.version());
writer.name("free_ram").value((unsigned int)System.freeMemory()); writer.name("free_ram").value((unsigned int)System.freeMemory());
//writer.name("uptime").value(System.uptime()); //writer.name("uptime").value(System.uptime());
@ -329,13 +336,13 @@ class MQTTTelemetry : public Task, OnlineTaskMixin {
void callback(char* topic, byte* payload, unsigned int length) { void callback(char* topic, byte* payload, unsigned int length) {
MainLoop::instance()->dispatch(InputEvent::NetworkActivity); MainLoop::instance()->dispatch(InputEvent::NetworkActivity);
if (!strcmp(topic, m_cmdTopic)) { if (!strcmp(topic, m_cmdTopic)) {
JSONValue cmd = JSONValue::parse((char*)payload, length); JSONValue cmd = JSONValue::parseCopy((char*)payload, length);
JSONObjectIterator cmdIter(cmd); JSONObjectIterator cmdIter(cmd);
while(cmdIter.next()) { while(cmdIter.next()) {
if (cmdIter.name() == "state" && cmdIter.value().toString() == "ON") { if (cmdIter.name() == "state" && cmdIter.value().toString() == "ON") {
MainLoop::instance()->dispatch({InputEvent::SetPower, 1}); setEvent({InputEvent::SetPower, 1});
} else if (cmdIter.name() == "state" && cmdIter.value().toString() == "OFF") { } else if (cmdIter.name() == "state" && cmdIter.value().toString() == "OFF") {
MainLoop::instance()->dispatch({InputEvent::SetPower, 0}); setEvent({InputEvent::SetPower, 0});
} }
if (cmdIter.name() == "color") { if (cmdIter.name() == "color") {
@ -350,31 +357,36 @@ class MQTTTelemetry : public Task, OnlineTaskMixin {
b = colorIter.value().toInt(); b = colorIter.value().toInt();
} }
} }
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetColor, CRGB{r, g, b}}); setEvent(InputEvent{InputEvent::SetColor, CRGB{r, g, b}});
} }
if (cmdIter.name() == "brightness") { if (cmdIter.name() == "brightness") {
uint8_t brightness = cmdIter.value().toInt(); uint8_t brightness = cmdIter.value().toInt();
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetBrightness, brightness}); setEvent(InputEvent{InputEvent::SetBrightness, brightness});
}
if (cmdIter.name() == "effect") {
strcpy(m_patternBuf, (const char*) cmdIter.value().toString());
setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf});
} }
} }
} }
} }
static void s_callback(char* topic, byte* payload, unsigned int length) { static void s_callback(char* topic, byte* payload, unsigned int length) {
s_instance->callback(topic, payload, length); Static<MQTTTelemetry>::instance()->callback(topic, payload, length);
}; };
static MQTTTelemetry* s_instance;
MQTT m_client; MQTT m_client;
InputEvent::Intent m_lastIntent;
char m_deviceName[100]; char m_deviceName[100];
char m_statTopic[100]; char m_statTopic[100];
char m_attrTopic[100]; char m_attrTopic[100];
char m_cmdTopic[100]; char m_cmdTopic[100];
char m_patternBuf[48];
}; };
MQTTTelemetry mqttTelemetry; STATIC_ALLOC(MQTTTelemetry);
MQTTTelemetry* MQTTTelemetry::s_instance = 0;
WebTelemetry webTelemetry(sequencer); WebTelemetry webTelemetry(sequencer);
@ -386,7 +398,7 @@ ColorSequenceInput<13> kierynCycle{{
colorForName("Electric Purple").rgb, colorForName("Electric Purple").rgb,
colorForName("Emerald").rgb, colorForName("Emerald").rgb,
colorForName("Sky Magenta").rgb colorForName("Sky Magenta").rgb
}, "Kieryn", Task::Running}; }, "Kieryn", Task::Stopped};
ColorSequenceInput<7> rainbowCycle{{ ColorSequenceInput<7> rainbowCycle{{
colorForName("Red").rgb, colorForName("Red").rgb,
@ -402,7 +414,7 @@ ColorSequenceInput<7> hackerbotsCycle{{
colorForName("Purple").rgb, colorForName("Purple").rgb,
colorForName("White").rgb, colorForName("White").rgb,
colorForName("Cyan").rgb, colorForName("Cyan").rgb,
}, "Hackerbots", Task::Running}; }, "Hackerbots", Task::Stopped};
struct ConfigInputTask: public BufferedInputSource { struct ConfigInputTask: public BufferedInputSource {
public: public:
@ -540,8 +552,11 @@ uint8_t brightnessForTime(uint8_t hour, uint8_t minute) {
InputFunc circadianRhythm([]() { InputFunc circadianRhythm([]() {
static Phase lastPhase = Null; static Phase lastPhase = Null;
static bool needsUpdate = true; static bool needsUpdate = true;
EVERY_N_SECONDS(60) {
if (Time.isValid()) { if (Time.isValid()) {
EVERY_N_SECONDS(60) {
needsUpdate = true;
}
if (needsUpdate) {
return InputEvent{InputEvent::SetBrightness, brightnessForTime(Time.hour(), Time.minute())}; return InputEvent{InputEvent::SetBrightness, brightnessForTime(Time.hour(), Time.minute())};
} }
} }
@ -561,15 +576,11 @@ MainLoop configApp{{
// Read hardware inputs // Read hardware inputs
new Buttons(), new Buttons(),
&randomPulse,
//new MPU5060(),
// Map input buttons to configuration commands // Map input buttons to configuration commands
new ConfigInputTask(), new ConfigInputTask(),
// Fill the entire display with a color, to see size // Fill the entire display with a color, to see size
&rainbowCycle,
&drain,
&configDisplay, &configDisplay,
// Render some basic input feedback // Render some basic input feedback
&inputBlip, &inputBlip,
@ -638,7 +649,7 @@ MainLoop renderbugApp{{
&webTelemetry, &webTelemetry,
// MQTT telemetry // MQTT telemetry
&mqttTelemetry, Static<MQTTTelemetry>::instance(),
// Network discovery // Network discovery
&mdnsService, &mdnsService,
@ -647,15 +658,24 @@ MainLoop renderbugApp{{
MainLoop &runner = renderbugApp; MainLoop &runner = renderbugApp;
retained bool LAST_BOOT_WAS_FLASH;
retained bool LAST_BOOT_WAS_SERIAL;
struct BootOptions { struct BootOptions {
BootOptions() { static void initPins() {
pinMode(2, INPUT_PULLDOWN); pinMode(2, INPUT_PULLDOWN);
pinMode(3, INPUT_PULLDOWN); pinMode(3, INPUT_PULLDOWN);
pinMode(4, INPUT_PULLDOWN); pinMode(4, INPUT_PULLDOWN);
}
BootOptions() {
isSetup = digitalRead(2) == HIGH; isSetup = digitalRead(2) == HIGH;
isSerial = digitalRead(3) == HIGH; isSerial = digitalRead(3) == HIGH || LAST_BOOT_WAS_SERIAL;
isFlash = digitalRead(4) == HIGH; isFlash = digitalRead(4) == HIGH;
LAST_BOOT_WAS_FLASH = isFlash;
LAST_BOOT_WAS_SERIAL |= isSerial;
configStatus.setActive(isSetup); configStatus.setActive(isSetup);
serialStatus.setActive(isSerial); serialStatus.setActive(isSerial);
} }
@ -672,16 +692,27 @@ struct BootOptions {
LEDStatus configStatus = LEDStatus(RGB_COLOR_YELLOW, LED_PATTERN_FADE, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT); LEDStatus configStatus = LEDStatus(RGB_COLOR_YELLOW, LED_PATTERN_FADE, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT);
}; };
BootOptions bootopts; STARTUP(BootOptions::initPins());
retained BootOptions bootopts;
ApplicationWatchdog *wd; ApplicationWatchdog *wd;
void watchdogHandler() { void watchdogHandler() {
for(int i = 0; i < 8; i++) {
leds[i] = CRGB(i % 3 ? 35 : 255, 0, 0);
}
FastLED.show();
if (LAST_BOOT_WAS_FLASH) {
System.dfu();
} else {
System.enterSafeMode(); System.enterSafeMode();
} }
}
// Tune in, // Tune in,
void setup() { void setup() {
System.enableFeature(FEATURE_RETAINED_MEMORY);
wd = new ApplicationWatchdog(5000, watchdogHandler, 1536); wd = new ApplicationWatchdog(5000, watchdogHandler, 1536);
Serial.begin(115200); Serial.begin(115200);
if (bootopts.isFlash) { if (bootopts.isFlash) {

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
using namespace NSFastLED;
class Blob { class Blob {
uint16_t m_pos; uint16_t m_pos;
int8_t m_velocity; int8_t m_velocity;
@ -56,12 +58,12 @@ public:
for(uint8_t i = 0;i < scaledWidth; i++) { for(uint8_t i = 0;i < scaledWidth; i++) {
// Blobs desaturate towards their tail // Blobs desaturate towards their tail
NSFastLED::CHSV blobColor(m_hue, m_saturation, NSFastLED::quadwave8((i / (double)scaledWidth) * m_brightness)); CHSV blobColor(m_hue, m_saturation, quadwave8((i / (double)scaledWidth) * m_brightness));
PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), 0}; PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), 0};
NSFastLED::CRGB src(display->pixelAt(pos)); CRGB src(display->pixelAt(pos));
display->pixelAt(pos) = NSFastLED::blend(NSFastLED::CRGB(blobColor), src, 200); display->pixelAt(pos) = blend(CRGB(blobColor), src, 200);
} }
} }
}; };

View File

@ -67,7 +67,7 @@ public:
dpy->pixelAt(i + m_offset) = CHSV(0, 0, 0); dpy->pixelAt(i + m_offset) = CHSV(0, 0, 0);
} else { } else {
uint8_t distance = m_pos - i; uint8_t distance = m_pos - i;
uint8_t brightness = NSFastLED::scale8(NSFastLED::quadwave8((ChimeLength / (double)distance) * 255), m_brightness); uint8_t brightness = scale8(quadwave8((ChimeLength / (double)distance) * 255), m_brightness);
if (brightness <= 0.2) if (brightness <= 0.2)
brightness = 0; brightness = 0;
dpy->pixelAt(VirtualCoordinates{i + m_offset, 0}) = CHSV(m_hue, min(m_saturation, brightness), brightness); dpy->pixelAt(VirtualCoordinates{i + m_offset, 0}) = CHSV(m_hue, min(m_saturation, brightness), brightness);