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
BufferedInputSource::read()
{
InputEvent ret = m_lastEvent;
m_lastEvent = InputEvent{};
InputEvent ret;
m_eventQueue.take(ret);
return ret;
}
void
BufferedInputSource::setEvent(InputEvent &&evt)
{
m_lastEvent = std::move(evt);
m_eventQueue.insert(std::move(evt));
}
void
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 "./Geometry.h"
#include "./Figment.h"
#include "./Ringbuf.h"
#include "FastLED/FastLED.h"
typedef Vector3d<int> MotionVec;
@ -136,7 +137,7 @@ protected:
void setEvent(InputEvent::Intent intent, Variant &&v);
private:
InputEvent m_lastEvent;
Ringbuf<InputEvent, 5> m_eventQueue;
};
class InputMapper: public BufferedInputSource {

View File

@ -3,6 +3,7 @@
#include <vector>
#include <algorithm>
#include "./Input.h"
#include "./Ringbuf.h"
class Task;
class InputSource;
@ -50,42 +51,6 @@ struct Scheduler {
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 {
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) {
m_idx--;
} 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) {

View File

@ -1,9 +1,11 @@
#include "../Figments/Figments.h"
using CRGB = NSFastLED::CRGB;
template<int Period>
class ColorSequenceInput: public InputSource {
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) {}
InputEvent read() override {
@ -32,7 +34,7 @@ public:
}
private:
std::vector<NSFastLED::CRGB> m_colors;
std::vector<CRGB> m_colors;
int m_idx = 0;
bool m_reset = true;
bool m_override = false;

View File

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

View File

@ -1,5 +1,7 @@
#pragma once
using namespace NSFastLED;
class Blob {
uint16_t m_pos;
int8_t m_velocity;
@ -56,12 +58,12 @@ public:
for(uint8_t i = 0;i < scaledWidth; i++) {
// 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};
NSFastLED::CRGB src(display->pixelAt(pos));
display->pixelAt(pos) = NSFastLED::blend(NSFastLED::CRGB(blobColor), src, 200);
CRGB src(display->pixelAt(pos));
display->pixelAt(pos) = blend(CRGB(blobColor), src, 200);
}
}
};

View File

@ -67,7 +67,7 @@ public:
dpy->pixelAt(i + m_offset) = CHSV(0, 0, 0);
} else {
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)
brightness = 0;
dpy->pixelAt(VirtualCoordinates{i + m_offset, 0}) = CHSV(m_hue, min(m_saturation, brightness), brightness);