bump
This commit is contained in:
parent
cadfd40b61
commit
92d5e73bd8
@ -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)});
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
|
39
firmware/Figments/Ringbuf.h
Normal file
39
firmware/Figments/Ringbuf.h
Normal 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;
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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()) {
|
||||
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() {
|
||||
System.enterSafeMode();
|
||||
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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user