If the code hasn't been touched in this long, its probably release-worthy.
This commit is contained in:
parent
0c9eb831dd
commit
d14fa7fde1
@ -1,39 +0,0 @@
|
|||||||
|
|
||||||
This directory is intended for project header files.
|
|
||||||
|
|
||||||
A header file is a file containing C declarations and macro definitions
|
|
||||||
to be shared between several project source files. You request the use of a
|
|
||||||
header file in your project source file (C, C++, etc) located in `src` folder
|
|
||||||
by including it, with the C preprocessing directive `#include'.
|
|
||||||
|
|
||||||
```src/main.c
|
|
||||||
|
|
||||||
#include "header.h"
|
|
||||||
|
|
||||||
int main (void)
|
|
||||||
{
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Including a header file produces the same results as copying the header file
|
|
||||||
into each source file that needs it. Such copying would be time-consuming
|
|
||||||
and error-prone. With a header file, the related declarations appear
|
|
||||||
in only one place. If they need to be changed, they can be changed in one
|
|
||||||
place, and programs that include the header file will automatically use the
|
|
||||||
new version when next recompiled. The header file eliminates the labor of
|
|
||||||
finding and changing all the copies as well as the risk that a failure to
|
|
||||||
find one copy will result in inconsistencies within a program.
|
|
||||||
|
|
||||||
In C, the usual convention is to give header files names that end with `.h'.
|
|
||||||
It is most portable to use only letters, digits, dashes, and underscores in
|
|
||||||
header file names, and at most one dot.
|
|
||||||
|
|
||||||
Read more about using header files in official GCC documentation:
|
|
||||||
|
|
||||||
* Include Syntax
|
|
||||||
* Include Operation
|
|
||||||
* Once-Only Headers
|
|
||||||
* Computed Includes
|
|
||||||
|
|
||||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
|
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <FastLED.h>
|
#include <FastLED.h>
|
||||||
#include "./Figment.h"
|
#include "./Figment.h"
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class Display;
|
class Display;
|
||||||
|
|
||||||
@ -24,6 +23,18 @@ struct AnimatedNumber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update(uint8_t speed) {
|
||||||
|
if (255 - speed >= m_idx) {
|
||||||
|
m_idx += speed;
|
||||||
|
} else {
|
||||||
|
m_idx = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFinished() const {
|
||||||
|
return m_idx == 255;
|
||||||
|
}
|
||||||
|
|
||||||
AnimatedNumber() {}
|
AnimatedNumber() {}
|
||||||
AnimatedNumber(uint8_t v) : m_end(v) {}
|
AnimatedNumber(uint8_t v) : m_end(v) {}
|
||||||
|
|
||||||
|
@ -15,11 +15,11 @@ struct LinearCoordinateMapping: CoordinateMapping {
|
|||||||
unsigned int startPixel = 0;
|
unsigned int startPixel = 0;
|
||||||
LinearCoordinateMapping() {}
|
LinearCoordinateMapping() {}
|
||||||
LinearCoordinateMapping(unsigned int count, unsigned int start) : pixelCount(count), startPixel(start) {}
|
LinearCoordinateMapping(unsigned int count, unsigned int start) : pixelCount(count), startPixel(start) {}
|
||||||
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const {
|
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const override {
|
||||||
return VirtualCoordinates{(uint8_t)((localCoords.x) / pixelCount), 0};
|
return VirtualCoordinates{map8(localCoords.x, 0, pixelCount), 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const {
|
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override {
|
||||||
return PhysicalCoordinates{scale8(pixelCount, virtualCoords.x), 0};
|
return PhysicalCoordinates{scale8(pixelCount, virtualCoords.x), 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ struct LinearCoordinateMapping: CoordinateMapping {
|
|||||||
return localCoords.x + startPixel;
|
return localCoords.x + startPixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int physicalPixelCount() const {
|
unsigned int physicalPixelCount() const override {
|
||||||
return pixelCount;
|
return pixelCount;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -22,15 +22,15 @@ struct Task : public virtual Loopable {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Task() {}
|
Task() {}
|
||||||
Task(State initialState) : Task(0, initialState) {}
|
explicit Task(State initialState) : Task(0, initialState) {}
|
||||||
Task(const char* name) : Task(name, Running) {}
|
explicit Task(const char* name) : Task(name, Running) {}
|
||||||
Task(const char* name, State initialState) : name(name), state(initialState) {}
|
Task(const char* name, State initialState) : name(name), state(initialState) {}
|
||||||
|
|
||||||
void start() { state = Running; onStart(); }
|
void start() { state = Running; onStart(); }
|
||||||
void stop() { onStop(); state = Stopped; }
|
void stop() { onStop(); state = Stopped; }
|
||||||
virtual bool isFigment() const { return false; }
|
virtual bool isFigment() const { return false; }
|
||||||
|
|
||||||
const char* name = 0;
|
const char* name = "";
|
||||||
State state = Running;
|
State state = Running;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,8 +42,8 @@ struct TaskFunc: public Task {
|
|||||||
|
|
||||||
struct Figment: public Task {
|
struct Figment: public Task {
|
||||||
Figment() : Task() {}
|
Figment() : Task() {}
|
||||||
Figment(State initialState) : Task(initialState) {}
|
explicit Figment(State initialState) : Task(initialState) {}
|
||||||
Figment(const char* name) : Task(name) {}
|
explicit Figment(const char* name) : Task(name) {}
|
||||||
Figment(const char* name, State initialState) : Task(name, initialState) {}
|
Figment(const char* name, State initialState) : Task(name, initialState) {}
|
||||||
virtual void render(Display* dpy) const = 0;
|
virtual void render(Display* dpy) const = 0;
|
||||||
bool isFigment() const override { return true; }
|
bool isFigment() const override { return true; }
|
||||||
|
@ -8,8 +8,8 @@ template<typename T> struct Coordinates {
|
|||||||
T y;
|
T y;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VirtualCoordinates: Coordinates<uint16_t> {
|
struct VirtualCoordinates: Coordinates<uint8_t> {
|
||||||
VirtualCoordinates(uint16_t _x, uint16_t _y) : Coordinates(_x, _y) {}
|
VirtualCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PhysicalCoordinates: Coordinates<uint16_t> {
|
struct PhysicalCoordinates: Coordinates<uint16_t> {
|
||||||
|
@ -20,10 +20,63 @@ Variant::asInt() const
|
|||||||
return m_value.asInt;
|
return m_value.asInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Variant::asBool() const
|
||||||
|
{
|
||||||
|
return (bool)m_value.asInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputSource::init()
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_THREADED_INPUTS
|
||||||
|
m_queue = xQueueCreate(32, sizeof(InputEvent));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_THREADED_INPUTS
|
||||||
|
void
|
||||||
|
InputSource::readThread(void* data)
|
||||||
|
{
|
||||||
|
InputSource* self = static_cast<InputSource*>(data);
|
||||||
|
while(true) {
|
||||||
|
InputEvent evt = self->read();
|
||||||
|
if (evt.intent != InputEvent::None) {
|
||||||
|
xQueueSend(m_queue, &evt, 0)
|
||||||
|
}
|
||||||
|
taskYIELD();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void
|
||||||
|
InputSource::onStart()
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_THREADED_INPUTS
|
||||||
|
m_threadLoop = MainLoop::instance();
|
||||||
|
xTaskCreate(
|
||||||
|
&InputSource::readThread,
|
||||||
|
name,
|
||||||
|
1000,
|
||||||
|
this,
|
||||||
|
1,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InputSource::loop()
|
InputSource::loop()
|
||||||
{
|
{
|
||||||
|
#ifndef CONFIG_THREADED_INPUTS
|
||||||
MainLoop::instance()->dispatch(read());
|
MainLoop::instance()->dispatch(read());
|
||||||
|
#else
|
||||||
|
InputEvent evt;
|
||||||
|
xQueueReceive(m_queue, &evt, 0);
|
||||||
|
if (evt.intent != InputEvent::None) {
|
||||||
|
MainLoop::instance()->dispatch(evt);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
InputEvent
|
InputEvent
|
||||||
|
@ -32,6 +32,7 @@ struct Variant {
|
|||||||
const char* asString() const;
|
const char* asString() const;
|
||||||
CRGB asRGB() const;
|
CRGB asRGB() const;
|
||||||
int asInt() const;
|
int asInt() const;
|
||||||
|
bool asBool() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
union {
|
union {
|
||||||
@ -74,6 +75,8 @@ struct InputEvent: public Variant {
|
|||||||
|
|
||||||
// Timekeeping
|
// Timekeeping
|
||||||
ScheduleChange,
|
ScheduleChange,
|
||||||
|
Beat,
|
||||||
|
BeatDetect,
|
||||||
|
|
||||||
// Task management
|
// Task management
|
||||||
StartThing,
|
StartThing,
|
||||||
@ -87,6 +90,8 @@ struct InputEvent: public Variant {
|
|||||||
|
|
||||||
// Firmware events
|
// Firmware events
|
||||||
FirmwareUpdate,
|
FirmwareUpdate,
|
||||||
|
|
||||||
|
ReadyToRoll,
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Value>
|
template<typename Value>
|
||||||
@ -102,14 +107,26 @@ struct InputEvent: public Variant {
|
|||||||
Intent intent;
|
Intent intent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MainLoop;
|
||||||
|
|
||||||
class InputSource: public Task {
|
class InputSource: public Task {
|
||||||
public:
|
public:
|
||||||
InputSource() : Task() {}
|
InputSource() : Task() {init();}
|
||||||
InputSource(const char* name) : Task(name) {}
|
explicit InputSource(const char* name) : Task(name) {init();}
|
||||||
InputSource(Task::State initialState) : Task(initialState) {}
|
explicit InputSource(Task::State initialState) : Task(initialState) {init();}
|
||||||
InputSource(const char* name, Task::State initialState) : Task(name, initialState) {}
|
InputSource(const char* name, Task::State initialState) : Task(name, initialState) {init();}
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
void onStart() override;
|
||||||
virtual InputEvent read() = 0;
|
virtual InputEvent read() = 0;
|
||||||
|
private:
|
||||||
|
void init();
|
||||||
|
#ifdef CONFIG_THREADED_INPUTS
|
||||||
|
static void readThread(void* data);
|
||||||
|
MainLoop* m_threadLoop;
|
||||||
|
static uint8_t m_threadBuf[sizeof(InputEvent) * 32];
|
||||||
|
QueueHandle_t m_queue;
|
||||||
|
StaticQueue_t m_threadQueue;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class InputFunc : public InputSource {
|
class InputFunc : public InputSource {
|
||||||
@ -128,7 +145,6 @@ private:
|
|||||||
|
|
||||||
class BufferedInputSource: public InputSource {
|
class BufferedInputSource: public InputSource {
|
||||||
public:
|
public:
|
||||||
BufferedInputSource() : InputSource() {}
|
|
||||||
BufferedInputSource(const char* name) : InputSource(name) {}
|
BufferedInputSource(const char* name) : InputSource(name) {}
|
||||||
InputEvent read() override;
|
InputEvent read() override;
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ MainLoop::dispatch(const InputEvent& evt)
|
|||||||
void
|
void
|
||||||
MainLoop::loop()
|
MainLoop::loop()
|
||||||
{
|
{
|
||||||
|
s_instance = this;
|
||||||
InputEvent evt;
|
InputEvent evt;
|
||||||
while (m_eventBuf.take(evt)) {
|
while (m_eventBuf.take(evt)) {
|
||||||
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
|
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
|
||||||
@ -23,10 +24,10 @@ MainLoop::loop()
|
|||||||
for(auto figmentJob: scheduler.tasks) {
|
for(auto figmentJob: scheduler.tasks) {
|
||||||
if (!strcmp(figmentJob->name, evt.asString())) {
|
if (!strcmp(figmentJob->name, evt.asString())) {
|
||||||
if (jobState) {
|
if (jobState) {
|
||||||
//Log.notice("Starting %s", figmentJob->name);
|
Log.trace("Starting task %s", figmentJob->name);
|
||||||
figmentJob->start();
|
figmentJob->start();
|
||||||
} else {
|
} else {
|
||||||
//Log.notice("Stopping %s", figmentJob->name);
|
Log.trace("Stopping task %s", figmentJob->name);
|
||||||
figmentJob->stop();
|
figmentJob->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,6 +35,9 @@ MainLoop::loop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(Task* task : scheduler) {
|
for(Task* task : scheduler) {
|
||||||
|
if (evt.intent == InputEvent::SetPower) {
|
||||||
|
Log.notice("Event %s", task->name);
|
||||||
|
}
|
||||||
task->handleEvent(evt);
|
task->handleEvent(evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,10 +48,18 @@ MainLoop::loop()
|
|||||||
Task* slowestTask = NULL;
|
Task* slowestTask = NULL;
|
||||||
for(Task* task : scheduler) {
|
for(Task* task : scheduler) {
|
||||||
//unsigned int start = millis();
|
//unsigned int start = millis();
|
||||||
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
unsigned int start = ESP.getCycleCount();
|
unsigned int start = ESP.getCycleCount();
|
||||||
|
#else
|
||||||
|
unsigned int start = millis();
|
||||||
|
#endif
|
||||||
|
Log.verbose("Running %s", task->name);
|
||||||
task->loop();
|
task->loop();
|
||||||
//unsigned int runtime = millis() - start;
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
unsigned int runtime = ESP.getCycleCount() - start;
|
unsigned int runtime = (ESP.getCycleCount() - start) / 160000;
|
||||||
|
#else
|
||||||
|
unsigned int runtime = millis() - start;
|
||||||
|
#endif
|
||||||
frameSpeed += runtime;
|
frameSpeed += runtime;
|
||||||
taskCount++;
|
taskCount++;
|
||||||
if (runtime > slowest) {
|
if (runtime > slowest) {
|
||||||
@ -57,19 +69,22 @@ MainLoop::loop()
|
|||||||
}
|
}
|
||||||
frameSpeed = millis() - frameStart;
|
frameSpeed = millis() - frameStart;
|
||||||
if (frameSpeed >= 23) {
|
if (frameSpeed >= 23) {
|
||||||
Log.notice("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestTask->name, slowest/160000);
|
const char* slowestName = (slowestTask->name ? slowestTask->name : "(Unnamed)");
|
||||||
|
Log.warning("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestTask->name, slowest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MainLoop::start()
|
MainLoop::start()
|
||||||
{
|
{
|
||||||
|
s_instance = this;
|
||||||
Log.notice("*** Starting %d tasks...", scheduler.tasks.size());
|
Log.notice("*** Starting %d tasks...", scheduler.tasks.size());
|
||||||
Serial.flush();
|
Serial.flush();
|
||||||
for(auto task: scheduler) {
|
for(auto task: scheduler) {
|
||||||
Log.notice("** Starting %s", task->name);
|
Log.notice("** Starting %s", task->name);
|
||||||
task->start();
|
task->start();
|
||||||
}
|
}
|
||||||
|
dispatch(InputEvent::ReadyToRoll);
|
||||||
}
|
}
|
||||||
|
|
||||||
MainLoop* MainLoop::s_instance;
|
MainLoop* MainLoop::s_instance;
|
||||||
|
@ -55,7 +55,7 @@ struct MainLoop {
|
|||||||
Scheduler scheduler;
|
Scheduler scheduler;
|
||||||
|
|
||||||
MainLoop(std::vector<Task*> &&tasks)
|
MainLoop(std::vector<Task*> &&tasks)
|
||||||
: scheduler(std::move(tasks)) {s_instance = this;}
|
: scheduler(std::move(tasks)) {}
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void loop();
|
void loop();
|
||||||
@ -63,7 +63,7 @@ struct MainLoop {
|
|||||||
static MainLoop* instance() { return s_instance; }
|
static MainLoop* instance() { return s_instance; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ringbuf<InputEvent, 10> m_eventBuf;
|
Ringbuf<InputEvent, 32> m_eventBuf;
|
||||||
|
|
||||||
static MainLoop* s_instance;
|
static MainLoop* s_instance;
|
||||||
};
|
};
|
||||||
|
12
lib/Figments/Perfcounter.h
Normal file
12
lib/Figments/Perfcounter.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoLog.h>
|
||||||
|
|
||||||
|
struct PerfCounter {
|
||||||
|
PerfCounter(const char* name) {}
|
||||||
|
~PerfCounter() {}
|
||||||
|
/*PerfCounter(const char* name) : start(millis()), name(name) {}
|
||||||
|
~PerfCounter() {Log.notice("%s: %d", name, millis() - start);}*/
|
||||||
|
uint16_t start;
|
||||||
|
const char* name;
|
||||||
|
};
|
@ -9,11 +9,18 @@ Renderer::loop()
|
|||||||
for(Display* dpy : m_displays) {
|
for(Display* dpy : m_displays) {
|
||||||
for(Figment* figment : m_figments) {
|
for(Figment* figment : m_figments) {
|
||||||
if (figment->state == Task::Running) {
|
if (figment->state == Task::Running) {
|
||||||
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
unsigned int frameStart = ESP.getCycleCount();
|
unsigned int frameStart = ESP.getCycleCount();
|
||||||
|
#endif
|
||||||
|
Log.verbose("Render %s", figment->name);
|
||||||
figment->render(dpy);
|
figment->render(dpy);
|
||||||
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
unsigned int runtime = (ESP.getCycleCount() - frameStart) / 160000;
|
unsigned int runtime = (ESP.getCycleCount() - frameStart) / 160000;
|
||||||
|
#else
|
||||||
|
unsigned int runtime = 0;
|
||||||
|
#endif
|
||||||
if (runtime >= 8) {
|
if (runtime >= 8) {
|
||||||
Log.notice("SLOW RENDER: %s took %dms!", figment->name, runtime);
|
Log.warning("SLOW RENDER: %s took %dms!", figment->name, runtime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,7 @@ class Display;
|
|||||||
|
|
||||||
struct Renderer: public Task {
|
struct Renderer: public Task {
|
||||||
public:
|
public:
|
||||||
Renderer(std::vector<Display*> displays, const std::vector<Figment*> &figments) : Task("Renderer"), m_figments(figments), m_displays(displays) {}
|
Renderer(std::vector<Display*>&& displays, const std::vector<Figment*> &figments) : Task("Renderer"), m_figments(figments), m_displays(std::move(displays)) {}
|
||||||
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void onStart() override;
|
void onStart() override;
|
||||||
|
@ -10,6 +10,11 @@ struct Ringbuf {
|
|||||||
m_tail = 0;
|
m_tail = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
T peek(int offset) const {
|
||||||
|
const int nextHead = (m_head + offset) % Size;
|
||||||
|
return m_items[nextHead];
|
||||||
|
}
|
||||||
|
|
||||||
bool take(T& dest) {
|
bool take(T& dest) {
|
||||||
if (m_head == m_tail) {
|
if (m_head == m_tail) {
|
||||||
return false;
|
return false;
|
||||||
@ -41,6 +46,22 @@ struct Ringbuf {
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t write(Print& stream) {
|
||||||
|
T val;
|
||||||
|
size_t ret = 0;
|
||||||
|
while(take(val)) {
|
||||||
|
stream.write(val);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() {
|
||||||
|
if (m_tail > m_head) {
|
||||||
|
return m_tail - m_head;
|
||||||
|
}
|
||||||
|
return m_tail + (Size - m_head);
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
int m_head = 0;
|
int m_head = 0;
|
||||||
int m_tail = 0;
|
int m_tail = 0;
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
#include "./Surface.h"
|
#include "./Surface.h"
|
||||||
#include "./Display.h"
|
#include "./Display.h"
|
||||||
#include <ArduinoLog.h>
|
#include <ArduinoLog.h>
|
||||||
|
#include "Perfcounter.h"
|
||||||
|
|
||||||
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end)
|
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end)
|
||||||
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
|
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
|
||||||
end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)),
|
end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)),
|
||||||
|
virtStart(start),
|
||||||
|
virtEnd(end),
|
||||||
m_display(dpy)
|
m_display(dpy)
|
||||||
{
|
{
|
||||||
|
//assert(start.x <= end.x);
|
||||||
|
//assert(start.y <= end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end, uint8_t rotation)
|
||||||
|
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
|
||||||
|
end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)),
|
||||||
|
virtStart(start),
|
||||||
|
virtEnd(end),
|
||||||
|
m_display(dpy),
|
||||||
|
m_rotation(rotation)
|
||||||
|
{
|
||||||
|
//assert(start.x <= end.x);
|
||||||
|
//assert(start.y <= end.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface&
|
Surface&
|
||||||
@ -30,11 +47,27 @@ Surface::operator+=(const CRGB& color)
|
|||||||
void
|
void
|
||||||
Surface::paintWith(std::function<void(CRGB&)> func)
|
Surface::paintWith(std::function<void(CRGB&)> func)
|
||||||
{
|
{
|
||||||
//Log.verbose("Painting startx=%d endx=%d starty=%d endy=%d", start.x, end.x, start.y, end.y);
|
paintShader([=](CRGB& pixel, const VirtualCoordinates&, const PhysicalCoordinates&, const VirtualCoordinates&){ func(pixel); });
|
||||||
for(auto x = start.x; x <= end.x; x++) {
|
}
|
||||||
for(auto y = start.y; y <= end.y; y++) {
|
|
||||||
//Log.verbose("x=%d y=%d", x, y);
|
void
|
||||||
func(m_display->pixelAt(PhysicalCoordinates{x, y}));
|
Surface::paintShader(Surface::Shader shader)
|
||||||
|
{
|
||||||
|
PerfCounter _("paintShader");
|
||||||
|
const uint16_t width = end.x - start.x + 1;
|
||||||
|
const uint16_t height = end.y - start.y + 1;
|
||||||
|
const uint8_t xMod = 255 / width;
|
||||||
|
const uint8_t yMod = 255 / height;
|
||||||
|
for(auto x = 0; x < width; x++) {
|
||||||
|
for(auto y = 0; y < height; y++) {
|
||||||
|
PhysicalCoordinates coords{x + start.x, y + start.y};
|
||||||
|
VirtualCoordinates virtCoords{m_display->coordinateMapping()->physicalToVirtualCoords(coords)};
|
||||||
|
VirtualCoordinates surfaceCoords{xMod * x, yMod * y};
|
||||||
|
//Log.notice("width=%d height=%d vx=%d vy=%d sx=%d sy=%d x=%d y=%d px=%d py=%d", width, height, start.x, start.y, x, y, coords.x, coords.y);
|
||||||
|
// 256 = 1.0
|
||||||
|
// 128 = 0.0
|
||||||
|
// 0 = 1.0
|
||||||
|
shader(m_display->pixelAt(coords), virtCoords, coords, surfaceCoords);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
#include <FastLED.h>
|
#include <FastLED.h>
|
||||||
#include "./Geometry.h"
|
#include "./Geometry.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -7,15 +9,30 @@ class Display;
|
|||||||
class Surface {
|
class Surface {
|
||||||
public:
|
public:
|
||||||
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end);
|
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end);
|
||||||
|
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end, uint8_t rotation);
|
||||||
|
|
||||||
Surface& operator=(const CRGB& color);
|
Surface& operator=(const CRGB& color);
|
||||||
Surface& operator+=(const CRGB& color);
|
Surface& operator+=(const CRGB& color);
|
||||||
|
template<typename T>
|
||||||
|
Surface& operator|=(const T& val) {
|
||||||
|
paintWith([&](CRGB& pixel) {
|
||||||
|
pixel |= val;
|
||||||
|
});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
void paintWith(std::function<void(CRGB&)> func);
|
using Shader = std::function<void(CRGB&, const VirtualCoordinates& virtPos, const PhysicalCoordinates& realPos, const VirtualCoordinates& surfacePos)>;
|
||||||
|
using BrushFunc = std::function<void(CRGB&)>;
|
||||||
|
|
||||||
|
void paintWith(BrushFunc func);
|
||||||
|
void paintShader(Shader shader);
|
||||||
|
|
||||||
const PhysicalCoordinates start;
|
const PhysicalCoordinates start;
|
||||||
const PhysicalCoordinates end;
|
const PhysicalCoordinates end;
|
||||||
|
const VirtualCoordinates virtStart;
|
||||||
|
const VirtualCoordinates virtEnd;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Display* m_display;
|
Display* m_display;
|
||||||
|
uint8_t m_rotation = 0;
|
||||||
};
|
};
|
||||||
|
8
lib/Figments/library.json
Normal file
8
lib/Figments/library.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "Figments",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"description": "An embedded graphics rendering engine",
|
||||||
|
"keywords": ["FastLED", "esp32", "esp8266"],
|
||||||
|
"frameworks": ["arduino"],
|
||||||
|
"platforms": ["espressif32", "espressif8266"]
|
||||||
|
}
|
193
platformio.ini
193
platformio.ini
@ -9,47 +9,194 @@
|
|||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[common_env_data]
|
[common_env_data]
|
||||||
src_filter = "+<*> -<.git/> -<.svn/> -<platform/>"
|
src_filter = "+<*> -<.git/> -<.svn/> -<platform/> -<inputs/>"
|
||||||
|
lib_ldf_mode = chain+
|
||||||
|
src_build_flags =
|
||||||
|
-DRENDERBUG_VERSION=3
|
||||||
|
-DRENDERBUG_LED_PIN=14
|
||||||
|
-DRENDERBUG_LED_PACKING=RGB
|
||||||
|
-DDEFAULT_PATTERN_INDEX=0
|
||||||
|
lib_deps_external =
|
||||||
|
fastled/FastLED@^3.4.0
|
||||||
|
thijse/ArduinoLog@1.0.3
|
||||||
|
bblanchon/ArduinoJson@^6.17.3
|
||||||
|
|
||||||
|
[config_u8display]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_U8DISPLAY
|
||||||
|
lib_deps =
|
||||||
|
olikraus/U8g2@2.28.8
|
||||||
|
src_filter = "+<platform/arduino/U8Display.cpp>"
|
||||||
|
|
||||||
|
[config_mqtt]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_MQTT
|
||||||
|
lib_deps =
|
||||||
|
knolleary/PubSubClient@^2.8.0
|
||||||
|
src_filter = "+<platform/arduino/MQTTTelemetry.cpp>"
|
||||||
|
|
||||||
|
[config_wifi]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_WIFI
|
||||||
|
src_filter = "+<platform/arduino/WiFiTask.cpp>"
|
||||||
|
|
||||||
|
[config_bluetooth]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_BLUETOOTH
|
||||||
|
src_filter = "+<platform/arduino/BluetoothSerialTelemetry.cpp>"
|
||||||
|
lib_deps =
|
||||||
|
BluetoothSerial
|
||||||
|
|
||||||
|
[config_ota]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_OTA
|
||||||
|
src_filter = "+<platform/arduino/OTA.cpp>"
|
||||||
|
lib_deps =
|
||||||
|
ArduinoOTA
|
||||||
|
ESP8266mDNS
|
||||||
|
|
||||||
|
[config_nocolor]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_NO_COLORDATA
|
||||||
|
|
||||||
|
[config_buttons]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_BUTTONS
|
||||||
|
src_filter = "+<inputs/Buttons.cpp>"
|
||||||
|
|
||||||
|
[config_mpu5060]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_MPU5060
|
||||||
|
src_filter = "+<inputs/MPU6050.cpp>"
|
||||||
|
|
||||||
|
[env:teensy]
|
||||||
|
extends = config_nocolor
|
||||||
|
platform = teensy
|
||||||
|
board = teensy31
|
||||||
|
framework = arduino
|
||||||
|
src_build_flags =
|
||||||
|
${common_env_data.src_build_flags}
|
||||||
|
${config_nocolor.src_build_flags}
|
||||||
|
-DPLATFORM_ARDUINO
|
||||||
|
-DBOARD_TEENSY
|
||||||
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_external}
|
||||||
|
src_filter = "${common_env_data.src_filter}"
|
||||||
|
|
||||||
|
[env:bike_teensy]
|
||||||
|
extends = env:teensy
|
||||||
|
src_build_flags=
|
||||||
|
${env:teensy.src_build_flags}
|
||||||
|
-DRENDERBUG_LED_PIN=11
|
||||||
|
-DRENDERBUG_LED_PACKING=GRB
|
||||||
|
-DDEFAULT_PATTERN_INDEX=1
|
||||||
|
|
||||||
|
[env:bike]
|
||||||
|
extends = env:esp32, config_u8display
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_u8display.src_filter}"
|
||||||
|
lib_deps =
|
||||||
|
${env:esp32.lib_deps}
|
||||||
|
${config_u8display.lib_deps}
|
||||||
|
src_build_flags =
|
||||||
|
${env:esp32.src_build_flags}
|
||||||
|
${config_u8display.src_build_flags}
|
||||||
|
build_type = debug
|
||||||
|
|
||||||
|
[env:bike_ble]
|
||||||
|
extends = env:bike
|
||||||
|
lib_deps =
|
||||||
|
${env:bike.lib_deps}
|
||||||
|
nkolban/ESP32 BLE Arduino@1.0.1
|
||||||
|
src_build_flags =
|
||||||
|
${env:bike.src_build_flags}
|
||||||
|
|
||||||
[env:esp32]
|
[env:esp32]
|
||||||
|
extends = config_nocolor
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = featheresp32
|
board = featheresp32
|
||||||
framework = arduino
|
framework = arduino
|
||||||
build_flags =
|
board_build.filesystem = littlefs
|
||||||
|
src_build_flags =
|
||||||
|
${common_env_data.src_build_flags}
|
||||||
|
${config_nocolor.src_build_flags}
|
||||||
-DPLATFORM_ARDUINO
|
-DPLATFORM_ARDUINO
|
||||||
-DBOARD_ESP32
|
-DBOARD_ESP32
|
||||||
-DCONFIG_NO_COLORDATA
|
; -DCONFIG_THREADED_INPUTS
|
||||||
; -DCORE_DEBUG_LEVEL=5
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
fastled/FastLED@^3.4.0
|
${common_env_data.lib_deps_external}
|
||||||
thijse/ArduinoLog@^1.0.3
|
src_filter = "${common_env_data.src_filter}"
|
||||||
knolleary/PubSubClient@^2.8.0
|
|
||||||
bblanchon/ArduinoJson@^6.17.3
|
|
||||||
sstaub/NTP@^1.4.0
|
|
||||||
arduino-libraries/NTPClient@^3.1.0
|
|
||||||
src_filter = "${common_env_data.src_filter} +<platform/arduino/>"
|
|
||||||
board_build.partitions = no_ota.csv
|
board_build.partitions = no_ota.csv
|
||||||
;build_type = debug
|
|
||||||
|
|
||||||
[env:cyberplague]
|
[env:esp8266-12f]
|
||||||
extends = env:esp32
|
extends = env:esp8266
|
||||||
board_build.partitions = no_ota.csv
|
board = esp12e
|
||||||
|
|
||||||
[env:esp8266]
|
[env:esp8266]
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
board = huzzah
|
board = huzzah
|
||||||
framework = arduino
|
framework = arduino
|
||||||
build_flags =
|
booard_build.filesystem = littlefs
|
||||||
|
src_build_flags =
|
||||||
|
${common_env_data.src_build_flags}
|
||||||
-DPLATFORM_ARDUINO
|
-DPLATFORM_ARDUINO
|
||||||
-DBOARD_ESP8266
|
-DBOARD_ESP8266
|
||||||
lib_deps =
|
-DCORE_DEBUG_LEVEL=5
|
||||||
fastled/FastLED@^3.4.0
|
-fstack-protector
|
||||||
thijse/ArduinoLog@^1.0.3
|
lib_deps =
|
||||||
knolleary/PubSubClient@^2.8.0
|
${common_env_data.lib_deps_external}
|
||||||
bblanchon/ArduinoJson@^6.17.3
|
|
||||||
sstaub/NTP@^1.4.0
|
|
||||||
arduino-libraries/NTPClient@^3.1.0
|
arduino-libraries/NTPClient@^3.1.0
|
||||||
src_filter = "${common_env_data.src_filter} +<platform/arduino/>"
|
src_filter = "${common_env_data.src_filter}"
|
||||||
|
|
||||||
|
[env:cyberplague]
|
||||||
|
extends = env:esp32, config_bluetooth
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_bluetooth.src_filter}"
|
||||||
|
lib_deps =
|
||||||
|
${env:esp32.lib_deps}
|
||||||
|
${config_bluetooth.lib_deps}
|
||||||
|
src_build_flags =
|
||||||
|
${env:esp32.src_build_flags}
|
||||||
|
${config_bluetooth.src_build_flags}
|
||||||
|
-DRENDERBUG_LED_PIN=13
|
||||||
|
-DDEFAULT_PATTERN_INDEX=1
|
||||||
|
|
||||||
|
[env:cyberplague_wifi]
|
||||||
|
extends = env:esp32, config_wifi, config_mqtt
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_wifi.src_filter} ${config_mqtt.src_filter}"
|
||||||
|
lib_deps =
|
||||||
|
${env:esp32.lib_deps}
|
||||||
|
${config_mqtt.lib_deps}
|
||||||
|
src_build_flags =
|
||||||
|
${env:esp32.src_build_flags}
|
||||||
|
${config_mqtt.src_build_flags}
|
||||||
|
${config_wifi.src_build_flags}
|
||||||
|
|
||||||
|
[env:prototype]
|
||||||
|
extends = env:esp32, config_buttons, config_mpu5060
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_buttons.src_filter} ${config_mpu5060.src_filter}"
|
||||||
|
|
||||||
|
[env:home_lighting]
|
||||||
|
extends = env:esp8266, config_wifi, config_mqtt, config_ota
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_ota.src_filter} ${config_wifi.src_filter} ${config_mqtt.src_filter}"
|
||||||
|
src_build_flags =
|
||||||
|
${env:esp8266.src_build_flags}
|
||||||
|
${config_mqtt.src_build_flags}
|
||||||
|
${config_wifi.src_build_flags}
|
||||||
|
${config_ota.src_build_flags}
|
||||||
|
lib_deps =
|
||||||
|
${env:esp8266.lib_deps}
|
||||||
|
${config_mqtt.lib_deps}
|
||||||
|
ESP8266WiFi
|
||||||
|
${config_ota.lib_deps}
|
||||||
|
|
||||||
|
[env:home_lighting_grb]
|
||||||
|
extends = env:home_lighting
|
||||||
|
src_build_flags =
|
||||||
|
${env:home_lighting.src_build_flags}
|
||||||
|
-DRENDERBUG_LED_PACKING=GRB
|
||||||
|
|
||||||
|
[env:home_lighting-12f]
|
||||||
|
extends = env:home_lighting
|
||||||
|
board = esp12e
|
||||||
|
|
||||||
;[env:photon]
|
;[env:photon]
|
||||||
;platform = particlephoton
|
;platform = particlephoton
|
||||||
|
@ -8,9 +8,13 @@ constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
|||||||
HardwareConfig
|
HardwareConfig
|
||||||
HardwareConfig::load() {
|
HardwareConfig::load() {
|
||||||
HardwareConfig ret;
|
HardwareConfig ret;
|
||||||
|
#ifndef BOARD_TEENSY
|
||||||
EEPROM.begin(sizeof(ret));
|
EEPROM.begin(sizeof(ret));
|
||||||
|
#endif
|
||||||
EEPROM.get(0, ret);
|
EEPROM.get(0, ret);
|
||||||
|
#ifndef BOARD_TEENSY
|
||||||
EEPROM.end();
|
EEPROM.end();
|
||||||
|
#endif
|
||||||
Log.notice("Loaded config version %d, CRC %d", ret.version, ret.checksum);
|
Log.notice("Loaded config version %d, CRC %d", ret.version, ret.checksum);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -19,10 +23,14 @@ void
|
|||||||
HardwareConfig::save() {
|
HardwareConfig::save() {
|
||||||
HardwareConfig dataCopy{*this};
|
HardwareConfig dataCopy{*this};
|
||||||
dataCopy.checksum = getCRC();
|
dataCopy.checksum = getCRC();
|
||||||
|
#ifndef BOARD_TEENSY
|
||||||
EEPROM.begin(sizeof(dataCopy));
|
EEPROM.begin(sizeof(dataCopy));
|
||||||
|
#endif
|
||||||
EEPROM.put(0, dataCopy);
|
EEPROM.put(0, dataCopy);
|
||||||
|
#ifndef BOARD_TEENSY
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
EEPROM.end();
|
EEPROM.end();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
LinearCoordinateMapping
|
LinearCoordinateMapping
|
||||||
@ -72,13 +80,6 @@ ConfigService::onStart()
|
|||||||
m_coordMap = m_config.toCoordMap();
|
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("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
|
void
|
||||||
@ -112,3 +113,4 @@ ConfigService::handleEvent(const InputEvent &evt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(ConfigService);
|
STATIC_ALLOC(ConfigService);
|
||||||
|
STATIC_TASK(ConfigService);
|
||||||
|
84
src/Config.h
84
src/Config.h
@ -1,17 +1,75 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Figments.h>
|
#include <Figments.h>
|
||||||
|
|
||||||
//#define PLATFORM_PHOTON
|
struct MaskCoordinateMapping : CoordinateMapping {
|
||||||
//#define PLATFORM_ARDUINO
|
struct Span {
|
||||||
#define RENDERBUG_VERSION 2
|
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 {
|
struct HardwareConfig {
|
||||||
uint8_t version = 2;
|
uint8_t version = 3;
|
||||||
uint8_t checksum = 0;
|
uint8_t checksum = 0;
|
||||||
struct TaskState {
|
|
||||||
char name[16] = {0};
|
|
||||||
bool isDisabled = false;
|
|
||||||
};
|
|
||||||
struct Data {
|
struct Data {
|
||||||
uint16_t pixelCount = 255;
|
uint16_t pixelCount = 255;
|
||||||
uint16_t startPixel = 0;
|
uint16_t startPixel = 0;
|
||||||
@ -19,7 +77,6 @@ struct HardwareConfig {
|
|||||||
uint8_t lastGreen = 255;
|
uint8_t lastGreen = 255;
|
||||||
uint8_t lastBlue = 255;
|
uint8_t lastBlue = 255;
|
||||||
char lastScene[16] = {0};
|
char lastScene[16] = {0};
|
||||||
TaskState serviceStates[32];
|
|
||||||
};
|
};
|
||||||
Data data;
|
Data data;
|
||||||
|
|
||||||
@ -28,13 +85,7 @@ struct HardwareConfig {
|
|||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
|
|
||||||
LinearCoordinateMapping toCoordMap() const;
|
LinearCoordinateMapping toCoordMap() const;
|
||||||
|
|
||||||
static constexpr uint16_t MAX_LED_NUM = 255;
|
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:
|
private:
|
||||||
uint8_t getCRC() const;
|
uint8_t getCRC() const;
|
||||||
@ -50,9 +101,10 @@ struct ConfigService: public Task {
|
|||||||
void onStart();
|
void onStart();
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void handleEvent(const InputEvent &evt) override;
|
void handleEvent(const InputEvent &evt) override;
|
||||||
const LinearCoordinateMapping* coordMap() const { return &m_coordMap; }
|
const CoordinateMapping* coordMap() const { return /*&m_maskMap;*/ &m_coordMap; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HardwareConfig m_config;
|
HardwareConfig m_config;
|
||||||
|
MaskCoordinateMapping m_maskMap;
|
||||||
LinearCoordinateMapping m_coordMap;
|
LinearCoordinateMapping m_coordMap;
|
||||||
};
|
};
|
||||||
|
@ -2,99 +2,96 @@
|
|||||||
#include "Static.h"
|
#include "Static.h"
|
||||||
#include <ArduinoLog.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
|
void
|
||||||
LogService::handleEvent(const InputEvent& evt) {
|
LogService::handleEvent(const InputEvent& evt) {
|
||||||
if (evt.intent != InputEvent::None) {
|
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 (evt.intent != m_lastEvent.intent) {
|
||||||
if (m_duplicateEvents > 0) {
|
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_duplicateEvents = 0;
|
||||||
m_lastEvent = evt;
|
m_lastEvent = evt;
|
||||||
//Particle.publish("renderbug/event", buf, PRIVATE);
|
|
||||||
} else {
|
} else {
|
||||||
m_duplicateEvents++;
|
m_duplicateEvents++;
|
||||||
}
|
}
|
||||||
/*if (m_online) {
|
|
||||||
} else {
|
|
||||||
Log.info("[offline] Event: %s", buf);
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(LogService);
|
STATIC_ALLOC(LogService);
|
||||||
|
STATIC_TASK(LogService);
|
||||||
|
@ -6,7 +6,11 @@ class LogService : public Task {
|
|||||||
void handleEvent(const InputEvent& event) override;
|
void handleEvent(const InputEvent& event) override;
|
||||||
void loop() override {}
|
void loop() override {}
|
||||||
|
|
||||||
|
static const char* intentName(InputEvent::Intent intent);
|
||||||
|
static const char* eventValue(const InputEvent& evt);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static char s_valueBuf[255];
|
||||||
uint16_t m_duplicateEvents = 0;
|
uint16_t m_duplicateEvents = 0;
|
||||||
InputEvent m_lastEvent;
|
InputEvent m_lastEvent;
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
#include <ArduinoLog.h>
|
#include <ArduinoLog.h>
|
||||||
#include "Static.h"
|
#include "Static.h"
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
#ifdef BOARD_ESP32
|
#ifdef BOARD_ESP32
|
||||||
|
#ifdef CONFIG_WIFI
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
#endif
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
#include <time.h>
|
|
||||||
#elif defined(BOARD_ESP8266)
|
#elif defined(BOARD_ESP8266)
|
||||||
|
#ifdef CONFIG_WIFI
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
#include <NTPClient.h>
|
#include <NTPClient.h>
|
||||||
@ -16,11 +19,14 @@ WiFiUDP wifiUdp;
|
|||||||
//NTPClient timeClient(wifiUdp, "pool.ntp.org", 3600 * -7);
|
//NTPClient timeClient(wifiUdp, "pool.ntp.org", 3600 * -7);
|
||||||
NTPClient timeClient(wifiUdp, "10.0.0.1", 3600 * -7);
|
NTPClient timeClient(wifiUdp, "10.0.0.1", 3600 * -7);
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef PLATFORM_PHOTON
|
#ifdef PLATFORM_PHOTON
|
||||||
STARTUP(BootOptions::initPins());
|
STARTUP(BootOptions::initPins());
|
||||||
#else
|
#else
|
||||||
|
#ifdef CONFIG_MQTT
|
||||||
#include "platform/arduino/MQTTTelemetry.h"
|
#include "platform/arduino/MQTTTelemetry.h"
|
||||||
|
#endif
|
||||||
void printNewline(Print* logOutput)
|
void printNewline(Print* logOutput)
|
||||||
{
|
{
|
||||||
logOutput->print("\r\n");
|
logOutput->print("\r\n");
|
||||||
@ -33,6 +39,8 @@ int printEspLog(const char* fmt, va_list args)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
int Platform::s_timezone = 0;
|
int Platform::s_timezone = 0;
|
||||||
|
Platform::TaskRegistration* Platform::firstTask = NULL;
|
||||||
|
Platform::TaskRegistration* Platform::lastTask = NULL;
|
||||||
|
|
||||||
const char*
|
const char*
|
||||||
Platform::name()
|
Platform::name()
|
||||||
@ -78,7 +86,11 @@ Platform::preSetup()
|
|||||||
Log.notice("\xf0\x9f\x94\x8c Serial connected");
|
Log.notice("\xf0\x9f\x94\x8c Serial connected");
|
||||||
}
|
}
|
||||||
#else
|
#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);
|
Log.setSuffix(printNewline);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -101,8 +113,10 @@ Platform::setup()
|
|||||||
constexpr int dst = 1;
|
constexpr int dst = 1;
|
||||||
configTime(s_timezone* 3600, 3600 * dst, "pool.ntp.org");
|
configTime(s_timezone* 3600, 3600 * dst, "pool.ntp.org");
|
||||||
#elif defined(BOARD_ESP8266)
|
#elif defined(BOARD_ESP8266)
|
||||||
|
#ifdef CONFIG_WIFI
|
||||||
timeClient.begin();
|
timeClient.begin();
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -114,15 +128,24 @@ Platform::bootSplash()
|
|||||||
Log.notice(u8" 3: Serial - %d", bootopts.isSerial);
|
Log.notice(u8" 3: Serial - %d", bootopts.isSerial);
|
||||||
Log.notice(u8" 4: Flash - %d", bootopts.isFlash);
|
Log.notice(u8" 4: Flash - %d", bootopts.isFlash);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Log.trace("Registered tasks:");
|
||||||
|
auto it = beginTasks();
|
||||||
|
while (it != endTasks()) {
|
||||||
|
Log.trace((*it)->name);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Platform::loop()
|
Platform::loop()
|
||||||
{
|
{
|
||||||
#ifdef BOARD_ESP8266
|
#ifdef BOARD_ESP8266
|
||||||
|
#ifdef CONFIG_WIFI
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
timeClient.update();
|
timeClient.update();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
ESP.wdtFeed();
|
ESP.wdtFeed();
|
||||||
#elif defined(BOARD_ESP32)
|
#elif defined(BOARD_ESP32)
|
||||||
esp_task_wdt_reset();
|
esp_task_wdt_reset();
|
||||||
@ -141,13 +164,18 @@ Platform::getLocalTime(struct tm* timedata)
|
|||||||
return false;
|
return false;
|
||||||
#elif defined(BOARD_ESP32)
|
#elif defined(BOARD_ESP32)
|
||||||
time_t rawtime;
|
time_t rawtime;
|
||||||
|
memset(&rawtime, 0, sizeof(rawtime));
|
||||||
time(&rawtime);
|
time(&rawtime);
|
||||||
(*timedata) = (*localtime(&rawtime));
|
(*timedata) = (*localtime(&rawtime));
|
||||||
return (timedata->tm_year > (2016-1990));
|
return (timedata->tm_year > (2016-1990));
|
||||||
//return getLocalTime(timedata);
|
|
||||||
#else
|
#else
|
||||||
|
#ifdef CONFIG_WIFI
|
||||||
timedata->tm_hour = timeClient.getHours();
|
timedata->tm_hour = timeClient.getHours();
|
||||||
timedata->tm_min = timeClient.getMinutes();
|
timedata->tm_min = timeClient.getMinutes();
|
||||||
|
#else
|
||||||
|
memset(timedata, sizeof(struct tm), 0);
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -172,3 +200,4 @@ char
|
|||||||
Platform::s_deviceID[15];
|
Platform::s_deviceID[15];
|
||||||
|
|
||||||
STATIC_ALLOC(Platform);
|
STATIC_ALLOC(Platform);
|
||||||
|
STATIC_TASK(Platform);
|
||||||
|
@ -13,14 +13,7 @@ class Platform : public Task {
|
|||||||
static int getTimezone() { return s_timezone; }
|
static int getTimezone() { return s_timezone; }
|
||||||
|
|
||||||
static void addLEDs(CRGB* leds, unsigned int ledCount) {
|
static void addLEDs(CRGB* leds, unsigned int ledCount) {
|
||||||
#ifdef PLATFORM_PHOTON
|
FastLED.addLeds<WS2812B, RENDERBUG_LED_PIN, RENDERBUG_LED_PACKING>(leds, ledCount);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* name();
|
static const char* name();
|
||||||
@ -39,5 +32,58 @@ class Platform : public Task {
|
|||||||
void loop() override;
|
void loop() override;
|
||||||
static bool getLocalTime(struct tm* timedata);
|
static bool getLocalTime(struct tm* timedata);
|
||||||
static const char* deviceID();
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
|
|
||||||
Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes) :
|
Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes) :
|
||||||
Task("SceneSequencer"),
|
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))
|
m_scenes(std::move(scenes))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -22,16 +30,28 @@ Sequencer::scenes() const
|
|||||||
return m_scenes;
|
return m_scenes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Sequencer::onStart()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Sequencer::handleEvent(const InputEvent& evt)
|
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) {
|
if (evt.intent == InputEvent::SetPattern && evt.asString() == m_scenes[m_idx].name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) {
|
if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) {
|
||||||
Log.notice("Switching pattern!");
|
Log.notice("Switching pattern!");
|
||||||
for(const char* pattern : m_scenes[m_idx].patterns) {
|
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});
|
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) {
|
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});
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,14 +13,16 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Sequencer(std::vector<Scene> &&scenes);
|
Sequencer(std::vector<Scene> &&scenes);
|
||||||
|
Sequencer(std::vector<Scene> &&scenes, int startingIndex);
|
||||||
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
void onStart() override;
|
||||||
void handleEvent(const InputEvent& evt) override;
|
void handleEvent(const InputEvent& evt) override;
|
||||||
|
|
||||||
const char* currentSceneName();
|
const char* currentSceneName();
|
||||||
const std::vector<Scene> scenes() const;
|
const std::vector<Scene> scenes() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_idx = 0;
|
int m_idx;
|
||||||
std::vector<Scene> m_scenes;
|
std::vector<Scene> m_scenes;
|
||||||
};
|
};
|
||||||
|
18
src/Static.h
18
src/Static.h
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "Platform.h"
|
||||||
|
|
||||||
// Utility class mostly for when certain inputs need singleton callback handlers
|
// Utility class mostly for when certain inputs need singleton callback handlers
|
||||||
template<typename T> class Static {
|
template<typename T> class Static {
|
||||||
@ -10,7 +11,24 @@ private:
|
|||||||
static T* s_instance;
|
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;\
|
#define NAMED_STATIC_ALLOC(Cls, StaticName) static Cls _staticAlloc__ ## StaticName;\
|
||||||
template<> Cls* Static<Cls>::s_instance=&_staticAlloc__ ## StaticName;
|
template<> Cls* Static<Cls>::s_instance=&_staticAlloc__ ## StaticName;
|
||||||
|
|
||||||
#define STATIC_ALLOC(Cls) NAMED_STATIC_ALLOC(Cls, Cls)
|
#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);
|
||||||
|
@ -1,75 +1,74 @@
|
|||||||
#include "../Figments/Figments.h"
|
#include "Chimes.h"
|
||||||
#include "../sprites/Chime.h"
|
#include "../Static.h"
|
||||||
#include "../sprites/Blob.h"
|
|
||||||
|
|
||||||
#define CHIME_LENGTH 23
|
ChimesAnimation::ChimesAnimation() : Figment("Chimes", Task::Stopped) {
|
||||||
#define CHIME_COUNT 4
|
}
|
||||||
#define BLOB_COUNT 10
|
|
||||||
|
|
||||||
class ChimesAnimation: public Figment {
|
void ChimesAnimation::randomize() {
|
||||||
public:
|
m_isRandom = true;
|
||||||
ChimesAnimation(Task::State initialState) : Figment("Chimes", initialState) {
|
m_chimes.forEach([](Chime<CHIME_LENGTH> &chime) {
|
||||||
m_chimes.forEach([](Chime<CHIME_LENGTH> &chime) {
|
chime.setPos(random(Chime<CHIME_LENGTH>::Length * 5));
|
||||||
chime.setPos(random(Chime<CHIME_LENGTH>::Length * 5));
|
chime.setHue(random(255));
|
||||||
chime.setHue(random(255));
|
chime.setSpeed(random(90) + 138);
|
||||||
chime.setSpeed(random(90) + 138);
|
chime.setBrightness(200);
|
||||||
chime.setBrightness(200);
|
chime.setOffset(random(1024));
|
||||||
chime.setOffset(random(1024));
|
});
|
||||||
});
|
m_blobs.forEach([](Blob &blob) {
|
||||||
m_blobs.forEach([](Blob &blob) {
|
blob.setPos(random(255));
|
||||||
blob.setPos(random(255));
|
blob.setHue(random(255));
|
||||||
blob.setHue(random(255));
|
blob.setBrightness(random(255));
|
||||||
blob.setBrightness(random(255));
|
if (random(255) % 2) {
|
||||||
if (random(255) % 2) {
|
blob.setVelocity(-1);
|
||||||
blob.setVelocity(-1);
|
} else {
|
||||||
} else {
|
blob.setVelocity(1);
|
||||||
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 {
|
void ChimesAnimation::loop() {
|
||||||
if (evt.intent == InputEvent::UserInput) {
|
if (!m_isRandom) {
|
||||||
if (strcmp(evt.asString(), "blobs") == 0) {
|
randomize();
|
||||||
m_blobs.toggle();
|
}
|
||||||
} else if (strcmp(evt.asString(), "chimes") == 0) {
|
m_chimes.update();
|
||||||
m_chimes.toggle();
|
m_blobs.update();
|
||||||
}
|
m_flashColor.update();
|
||||||
} else if (evt.intent == InputEvent::SetColor) {
|
EVERY_N_MILLISECONDS(5) {
|
||||||
m_flashBrightness.set(255, 0);
|
m_flashBrightness.update();
|
||||||
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 loop() override {
|
void ChimesAnimation::render(Display* dpy) const {
|
||||||
m_chimes.update();
|
m_chimes.render(dpy);
|
||||||
m_blobs.update();
|
m_blobs.render(dpy);
|
||||||
m_flashColor.update();
|
Surface fullSurface(dpy, {0, 0}, {255, 0});
|
||||||
EVERY_N_MILLISECONDS(5) {
|
CRGB scaledColor = CRGB(m_flashColor).nscale8_video(std::max((uint8_t)10, ease8InOutCubic(m_flashBrightness)));
|
||||||
m_flashBrightness.update();
|
fullSurface.paintWith([&](CRGB& pixel) {
|
||||||
}
|
pixel = blend(scaledColor, pixel, 200);
|
||||||
}
|
//pixel = scaledColor;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void render(Display* dpy) const override {
|
STATIC_ALLOC(ChimesAnimation);
|
||||||
m_chimes.render(dpy);
|
STATIC_TASK(ChimesAnimation);
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
24
src/animations/Chimes.h
Normal file
24
src/animations/Chimes.h
Normal 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;
|
||||||
|
};
|
@ -1,64 +1,58 @@
|
|||||||
#include <Figments.h>
|
#include "Drain.h"
|
||||||
#include <ArduinoLog.h>
|
#include "../Static.h"
|
||||||
|
|
||||||
class DrainAnimation: public Figment {
|
DrainAnimation::DrainAnimation() : Figment("Drain", Task::Stopped) {}
|
||||||
public:
|
|
||||||
|
|
||||||
DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {}
|
void DrainAnimation::loop() {
|
||||||
|
EVERY_N_MILLISECONDS(8) {
|
||||||
void loop() override {
|
m_pos++;
|
||||||
EVERY_N_MILLISECONDS(8) {
|
m_fillColor.update();
|
||||||
m_pos++;
|
}
|
||||||
m_fillColor.update();
|
EVERY_N_MILLISECONDS(50) {
|
||||||
}
|
if (random(255) >= 10) {
|
||||||
EVERY_N_MILLISECONDS(50) {
|
m_burst -= m_burst / 10;
|
||||||
if (random(255) >= 10) {
|
|
||||||
m_burst -= m_burst / 10;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void handleEvent(const InputEvent& event) override {
|
void DrainAnimation::handleEvent(const InputEvent& event) {
|
||||||
if (event.intent == InputEvent::SetColor) {
|
if (event.intent == InputEvent::SetColor) {
|
||||||
m_fillColor = event.asRGB();
|
m_fillColor = event.asRGB();
|
||||||
} else if (event.intent == InputEvent::Acceleration) {
|
} else if (event.intent == InputEvent::Acceleration) {
|
||||||
m_pos += log10(event.asInt());
|
m_pos += log10(event.asInt());
|
||||||
uint16_t burstInc = event.asInt() / 6;
|
uint16_t burstInc = event.asInt() / 6;
|
||||||
m_burst = (m_burst > 0xFFFF - burstInc) ? 0xFFFF : m_burst + burstInc;
|
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 {
|
const uint8_t localScale = inoise8(i * 80, m_pos * 3);
|
||||||
dpy->clear();
|
const uint8_t dimPosition = lerp8by8(50, 190, scale8(sin8((frac * i) / 2), localScale));
|
||||||
Surface leftPanel{dpy, {0, 0}, {128, 0}};
|
const uint8_t withBurst = ease8InOutCubic(lerp16by16(dimPosition, 255, m_burst));
|
||||||
Surface rightPanel{dpy, {128, 0}, {255, 0}};
|
auto scaledColor = CHSV(baseColor.hue, lerp8by8(100, 255, localScale), withBurst);
|
||||||
fillRange(dpy, leftPanel.start, leftPanel.end, rgb2hsv_approximate(m_fillColor));
|
|
||||||
fillRange(dpy, rightPanel.end, rightPanel.start, rgb2hsv_approximate(m_fillColor));
|
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 {
|
STATIC_ALLOC(DrainAnimation);
|
||||||
int length = end.x - start.x;
|
STATIC_TASK(DrainAnimation);
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
@ -1,63 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Figments.h>
|
||||||
|
#include <ArduinoLog.h>
|
||||||
|
|
||||||
class DrainAnimation: public Figment {
|
class DrainAnimation: public Figment {
|
||||||
public:
|
public:
|
||||||
|
DrainAnimation();
|
||||||
DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {}
|
void loop() override;
|
||||||
|
void handleEvent(const InputEvent& event) override;
|
||||||
void loop() override {
|
void render(Display* dpy) const override;
|
||||||
EVERY_N_MILLISECONDS(8) {
|
private:
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedRGB m_fillColor;
|
AnimatedRGB m_fillColor;
|
||||||
|
void fillRange(Display* dpy, const PhysicalCoordinates &start, const PhysicalCoordinates& end, const CHSV &baseColor) const;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t m_pos;
|
uint16_t m_pos;
|
||||||
uint16_t m_burst;
|
uint16_t m_burst;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,47 +1,33 @@
|
|||||||
#pragma once
|
#include "Flashlight.h"
|
||||||
|
#include "../Static.h"
|
||||||
|
|
||||||
#include <Figments.h>
|
Flashlight::Flashlight() : Figment("Flashlight", Task::Stopped) {
|
||||||
#include "../sprites/Blob.h"
|
m_blobs.forEach([](Blob &blob) {
|
||||||
|
blob.setHue(random(255));
|
||||||
class Flashlight: public Figment {
|
blob.setSaturation(10);
|
||||||
public:
|
blob.setPos(random(255));
|
||||||
Flashlight(Task::State initialState) : Figment("Flashlight", initialState) {
|
if (random(255) >= 128) {
|
||||||
m_blobs.forEach([](Blob &blob) {
|
blob.setVelocity(-1);
|
||||||
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();});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/*if (evt.intent() == InputEvent::UserInput) {
|
void Flashlight::handleEvent(const InputEvent& evt) {
|
||||||
if (evt.asInt() == 1) {
|
if (evt.intent == InputEvent::Acceleration) {
|
||||||
m_blobs.forEach([](Blob& blob) {blob.setPos(random(255));});
|
if (evt.asInt() > 10) {
|
||||||
}
|
m_blobs.forEach([](Blob& blob) {blob.update();});
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
void Flashlight::loop() {
|
||||||
static constexpr int blobCount = 30;
|
m_blobs.update();
|
||||||
SpriteList<Blob, blobCount> m_blobs;
|
}
|
||||||
};
|
|
||||||
|
void Flashlight::render(Display* dpy) const {
|
||||||
|
m_blobs.render(dpy);
|
||||||
|
Surface(dpy, {0, 0}, {255, 0}) |= 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC_ALLOC(Flashlight);
|
||||||
|
STATIC_TASK(Flashlight);
|
||||||
|
15
src/animations/Flashlight.h
Normal file
15
src/animations/Flashlight.h
Normal 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;
|
||||||
|
};
|
@ -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
54
src/animations/Power.h
Normal 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;
|
||||||
|
};
|
@ -1,50 +1,72 @@
|
|||||||
#include <Figments.h>
|
#include "SolidAnimation.h"
|
||||||
#include "../sprites/Blob.h"
|
#include "../Static.h"
|
||||||
|
|
||||||
class SolidAnimation: public Figment {
|
SolidAnimation::SolidAnimation() : Figment("Solid", Task::Stopped) {
|
||||||
private:
|
}
|
||||||
AnimatedNumber m_red, m_green, m_blue;
|
|
||||||
static constexpr int blobCount = 20;
|
|
||||||
SpriteList<Blob, blobCount> m_blobs;
|
|
||||||
|
|
||||||
public:
|
void SolidAnimation::randomize() {
|
||||||
SolidAnimation(Task::State initialState) : Figment("Solid", initialState) {
|
m_isRandom = true;
|
||||||
m_blobs.forEach([](Blob& blob) {
|
m_blobs.forEach([](Blob& blob) {
|
||||||
blob.setPos(random(140));
|
blob.setPos(random(140));
|
||||||
blob.setBrightness(random(255));
|
blob.setBrightness(random(255));
|
||||||
if (random(255) % 2) {
|
if (random(255) % 2) {
|
||||||
blob.setVelocity(-1);
|
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 {
|
#include <Perfcounter.h>
|
||||||
if (evt.intent == InputEvent::SetColor) {
|
|
||||||
CRGB nextColor = evt.asRGB();
|
|
||||||
m_red.set(nextColor.red);
|
|
||||||
m_green.set(nextColor.green);
|
|
||||||
m_blue.set(nextColor.blue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() override {
|
void SolidAnimation::render(Display* dpy) const {
|
||||||
m_red.update();
|
PerfCounter _("solidRender");
|
||||||
m_green.update();
|
CRGB color(m_red.value(), m_green.value(), m_blue.value());
|
||||||
m_blue.update();
|
uint8_t frame = ease8InOutApprox(m_changePct);
|
||||||
EVERY_N_MILLIS(6) {
|
if (frame == 255) {
|
||||||
CRGB rgb{m_red, m_green, m_blue};
|
Surface(dpy, {0, 0}, {255, 255}) = color;
|
||||||
CHSV hsv = rgb2hsv_approximate(rgb);
|
} else {
|
||||||
m_blobs.forEach([=](Blob& blob) {
|
uint8_t cutoff = (frame / 2);
|
||||||
blob.setHue(hsv.hue);
|
uint8_t rotation = m_horizontal ? 0 : 128;
|
||||||
blob.setSaturation(hsv.saturation);
|
Surface(dpy, {0, 0}, {128 - cutoff, 255}, rotation) = m_prevColor;
|
||||||
});
|
Surface(dpy, {128 - cutoff, 0}, {128 + cutoff, 255}, rotation) = color;
|
||||||
m_blobs.update();
|
Surface(dpy, {128 + cutoff, 0}, {255, 255}, rotation) = m_prevColor;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
m_blobs.render(dpy);
|
||||||
void render(Display* dpy) const override {
|
}
|
||||||
CRGB color(m_red.value(), m_green.value(), m_blue.value());
|
STATIC_ALLOC(SolidAnimation);
|
||||||
Surface(dpy, {0, 0}, {255, 0}) = color;
|
STATIC_TASK(SolidAnimation);
|
||||||
m_blobs.render(dpy);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
21
src/animations/SolidAnimation.h
Normal file
21
src/animations/SolidAnimation.h
Normal 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;
|
||||||
|
};
|
@ -32,3 +32,4 @@ UpdateStatus::render(Display* dpy) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(UpdateStatus);
|
STATIC_ALLOC(UpdateStatus);
|
||||||
|
STATIC_TASK(UpdateStatus);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
#include <Figments.h>
|
#include <Figments.h>
|
||||||
|
|
||||||
class UpdateStatus: public Figment {
|
class UpdateStatus: public Figment {
|
||||||
|
@ -39,3 +39,4 @@ Buttons::read()
|
|||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(Buttons);
|
STATIC_ALLOC(Buttons);
|
||||||
|
STATIC_TASK(Buttons);
|
||||||
|
@ -21,6 +21,14 @@ public:
|
|||||||
return InputEvent{};
|
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 {
|
void onStart() override {
|
||||||
m_reset = true;
|
m_reset = true;
|
||||||
}
|
}
|
||||||
|
@ -63,3 +63,4 @@ MPU5060::read()
|
|||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(MPU5060);
|
STATIC_ALLOC(MPU5060);
|
||||||
|
STATIC_TASK(MPU5060);
|
||||||
|
333
src/main.cpp
333
src/main.cpp
@ -5,8 +5,7 @@
|
|||||||
|
|
||||||
#ifndef PLATFORM_PHOTON
|
#ifndef PLATFORM_PHOTON
|
||||||
#include <ArduinoLog.h>
|
#include <ArduinoLog.h>
|
||||||
#include <NTP.h>
|
#endif // !PLATFORM_PHOTON
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
|
|
||||||
@ -16,29 +15,22 @@
|
|||||||
#include "Sequencer.h"
|
#include "Sequencer.h"
|
||||||
#include "LogService.h"
|
#include "LogService.h"
|
||||||
|
|
||||||
#include "animations/Power.cpp"
|
#include <time.h>
|
||||||
#include "animations/SolidAnimation.cpp"
|
|
||||||
#include "animations/Chimes.cpp"
|
#include "animations/Power.h"
|
||||||
#include "animations/Flashlight.cpp"
|
#include "animations/SolidAnimation.h"
|
||||||
#include "animations/Drain.cpp"
|
#include "animations/Chimes.h"
|
||||||
|
#include "animations/Flashlight.h"
|
||||||
|
#include "animations/Drain.h"
|
||||||
#include "animations/UpdateStatus.h"
|
#include "animations/UpdateStatus.h"
|
||||||
|
|
||||||
#include "inputs/ColorCycle.h"
|
#include "inputs/ColorCycle.h"
|
||||||
#include "inputs/Buttons.h"
|
#include "inputs/Buttons.h"
|
||||||
#include "inputs/MPU6050.h"
|
|
||||||
|
|
||||||
#ifdef PLATFORM_PHOTON
|
#ifdef PLATFORM_PHOTON
|
||||||
#include "platform/particle/inputs/Photon.h"
|
#include "platform/particle/inputs/Photon.h"
|
||||||
#include "platform/particle/inputs/CloudStatus.h"
|
#include "platform/particle/inputs/CloudStatus.h"
|
||||||
#include "platform/particle/PhotonTelemetry.h"
|
#endif // PLATFORM_PHOTON
|
||||||
#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
|
|
||||||
|
|
||||||
//SerialLogHandler logHandler;
|
//SerialLogHandler logHandler;
|
||||||
|
|
||||||
@ -51,6 +43,7 @@
|
|||||||
// Enable system thread, so rendering happens while booting
|
// Enable system thread, so rendering happens while booting
|
||||||
//SYSTEM_THREAD(ENABLED);
|
//SYSTEM_THREAD(ENABLED);
|
||||||
|
|
||||||
|
|
||||||
// Setup FastLED and the display
|
// Setup FastLED and the display
|
||||||
CRGB leds[HardwareConfig::MAX_LED_NUM];
|
CRGB leds[HardwareConfig::MAX_LED_NUM];
|
||||||
Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()->coordMap());
|
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
|
// Setup power management
|
||||||
Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power;
|
Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power;
|
||||||
|
|
||||||
FigmentFunc configDisplay([](Display* dpy) {
|
REGISTER_TASK(power);
|
||||||
|
|
||||||
|
/*FigmentFunc configDisplay([](Display* dpy) {
|
||||||
uint8_t brightness = brighten8_video(beatsin8(60));
|
uint8_t brightness = brighten8_video(beatsin8(60));
|
||||||
auto coords = Static<ConfigService>::instance()->coordMap();
|
auto coords = Static<ConfigService>::instance()->coordMap();
|
||||||
for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) {
|
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);
|
dpy->pixelAt(i) += CRGB(255 - brightness, 255 - brightness, 255 - brightness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
|
|
||||||
class InputBlip: public Figment {
|
class InputBlip: public Figment {
|
||||||
public:
|
public:
|
||||||
@ -94,42 +89,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
InputBlip inputBlip;
|
InputBlip inputBlip;
|
||||||
|
REGISTER_TASK(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);
|
|
||||||
|
|
||||||
InputFunc randomPulse([]() {
|
InputFunc randomPulse([]() {
|
||||||
static unsigned int pulse = 0;
|
static unsigned int pulse = 0;
|
||||||
@ -145,7 +105,9 @@ InputFunc randomPulse([]() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return InputEvent{};
|
return InputEvent{};
|
||||||
}, "Pulse", Task::Running);
|
}, "Pulse", Task::Stopped);
|
||||||
|
|
||||||
|
REGISTER_TASK(randomPulse);
|
||||||
|
|
||||||
InputMapper keyMap([](const InputEvent& evt) {
|
InputMapper keyMap([](const InputEvent& evt) {
|
||||||
if (evt.intent == InputEvent::UserInput) {
|
if (evt.intent == InputEvent::UserInput) {
|
||||||
@ -167,38 +129,96 @@ InputMapper keyMap([](const InputEvent& evt) {
|
|||||||
return InputEvent::None;
|
return InputEvent::None;
|
||||||
}, "Keymap");
|
}, "Keymap");
|
||||||
|
|
||||||
ChimesAnimation chimes{Task::Stopped};
|
REGISTER_TASK(keyMap);
|
||||||
SolidAnimation solid{Task::Running};
|
|
||||||
DrainAnimation drain{Task::Stopped};
|
|
||||||
Flashlight flashlight{Task::Stopped};
|
#ifndef DEFAULT_PATTERN_INDEX
|
||||||
|
#define DEFAULT_PATTERN_INDEX 0
|
||||||
|
#endif
|
||||||
|
|
||||||
Sequencer sequencer{{
|
Sequencer sequencer{{
|
||||||
{"Idle", {"Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"}},
|
{"Idle", {"Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"}},
|
||||||
|
{"Acid", {"Chimes", "Pulse", "MPU5060", "IdleColors", "Rainbow"}},
|
||||||
{"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}},
|
{"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}},
|
||||||
{"Interactive", {"Drain", "MPU5060", "CircadianRhythm"}},
|
{"Interactive", {"Drain", "MPU5060", "CircadianRhythm"}},
|
||||||
{"Flashlight", {"Flashlight"}},
|
{"Flashlight", {"Flashlight"}},
|
||||||
{"Gay", {"Solid", "Pulse", "Rainbow", "Rainbow"}},
|
{"Gay", {"Solid", "Pulse", "Rainbow"}},
|
||||||
{"Acid", {"Chimes", "Pulse", "MPU5060", "IdleColors", "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
|
// Render all layers to the displays
|
||||||
Renderer renderer{
|
Renderer renderer{
|
||||||
{&dpy},
|
{&dpy},
|
||||||
{
|
{
|
||||||
&chimes,
|
Static<ChimesAnimation>::instance(),
|
||||||
&drain,
|
Static<DrainAnimation>::instance(),
|
||||||
&solid,
|
Static<SolidAnimation>::instance(),
|
||||||
&flashlight,
|
Static<Flashlight>::instance(),
|
||||||
Static<UpdateStatus>::instance(),
|
Static<UpdateStatus>::instance(),
|
||||||
&inputBlip,
|
&inputBlip,
|
||||||
&power,
|
&power,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
REGISTER_TASK(renderer);
|
||||||
|
|
||||||
Renderer configRenderer{
|
Renderer configRenderer{
|
||||||
{&dpy},
|
{&dpy},
|
||||||
{&drain, &configDisplay, &inputBlip, &power}
|
{Static<DrainAnimation>::instance(), /*&configDisplay,*/ &inputBlip, &power}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cycle some random colors
|
// Cycle some random colors
|
||||||
@ -209,7 +229,9 @@ ColorSequenceInput<9> idleCycle{{
|
|||||||
CRGB(128, 0, 128), // Purple
|
CRGB(128, 0, 128), // Purple
|
||||||
CRGB(255, 255, 255), // White
|
CRGB(255, 255, 255), // White
|
||||||
CRGB(0, 255, 255), // Cyan
|
CRGB(0, 255, 255), // Cyan
|
||||||
}, "IdleColors", Task::Running};
|
}, "IdleColors", Task::Stopped};
|
||||||
|
|
||||||
|
REGISTER_TASK(idleCycle);
|
||||||
|
|
||||||
ColorSequenceInput<7> rainbowCycle{{
|
ColorSequenceInput<7> rainbowCycle{{
|
||||||
CRGB(255, 0, 0), // Red
|
CRGB(255, 0, 0), // Red
|
||||||
@ -219,7 +241,9 @@ ColorSequenceInput<7> rainbowCycle{{
|
|||||||
CRGB(128, 0, 128), // Purple
|
CRGB(128, 0, 128), // Purple
|
||||||
}, "Rainbow", Task::Stopped};
|
}, "Rainbow", Task::Stopped};
|
||||||
|
|
||||||
struct ConfigInputTask: public BufferedInputSource {
|
REGISTER_TASK(rainbowCycle);
|
||||||
|
|
||||||
|
/*struct ConfigInputTask: public BufferedInputSource {
|
||||||
public:
|
public:
|
||||||
ConfigInputTask() : BufferedInputSource("ConfigInput") {}
|
ConfigInputTask() : BufferedInputSource("ConfigInput") {}
|
||||||
|
|
||||||
@ -292,7 +316,7 @@ private:
|
|||||||
return InputEvent::None;
|
return InputEvent::None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};*/
|
||||||
|
|
||||||
struct ScheduleEntry {
|
struct ScheduleEntry {
|
||||||
uint8_t hour;
|
uint8_t hour;
|
||||||
@ -368,9 +392,13 @@ class CircadianRhythm : public InputSource {
|
|||||||
uint8_t minute = 0;
|
uint8_t minute = 0;
|
||||||
needsUpdate = false;
|
needsUpdate = false;
|
||||||
struct tm timeinfo;
|
struct tm timeinfo;
|
||||||
Platform::getLocalTime(&timeinfo);
|
if (Platform::getLocalTime(&timeinfo)) {
|
||||||
hour = timeinfo.tm_hour;
|
hour = timeinfo.tm_hour;
|
||||||
minute = timeinfo.tm_min;
|
minute = timeinfo.tm_min;
|
||||||
|
} else {
|
||||||
|
hour = 0;
|
||||||
|
minute = 0;
|
||||||
|
}
|
||||||
Log.notice("Current time: %d:%d", hour, minute);
|
Log.notice("Current time: %d:%d", hour, minute);
|
||||||
return InputEvent{InputEvent::SetBrightness, brightnessForTime(hour, minute)};
|
return InputEvent{InputEvent::SetBrightness, brightnessForTime(hour, minute)};
|
||||||
}
|
}
|
||||||
@ -379,20 +407,16 @@ class CircadianRhythm : public InputSource {
|
|||||||
};
|
};
|
||||||
|
|
||||||
STATIC_ALLOC(CircadianRhythm);
|
STATIC_ALLOC(CircadianRhythm);
|
||||||
|
STATIC_TASK(CircadianRhythm);
|
||||||
|
|
||||||
// A special mainloop app for configuring hardware settings that reboots the
|
// A special mainloop app for configuring hardware settings that reboots the
|
||||||
// device when the user is finished.
|
// device when the user is finished.
|
||||||
MainLoop configApp{{
|
/*MainLoop configApp{{
|
||||||
Static<Platform>::instance(),
|
Static<Platform>::instance(),
|
||||||
|
|
||||||
// Manage read/write of configuration data
|
// Manage read/write of configuration data
|
||||||
Static<ConfigService>::instance(),
|
Static<ConfigService>::instance(),
|
||||||
|
|
||||||
#ifdef PLATFORM_PHOTON
|
|
||||||
// Update photon telemetry
|
|
||||||
Static<PhotonTelemetry>::instance(),
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Read hardware inputs
|
// Read hardware inputs
|
||||||
Static<Buttons>::instance(),
|
Static<Buttons>::instance(),
|
||||||
|
|
||||||
@ -408,12 +432,14 @@ MainLoop configApp{{
|
|||||||
&inputBlip,
|
&inputBlip,
|
||||||
// Render it all
|
// Render it all
|
||||||
&configRenderer,
|
&configRenderer,
|
||||||
}};
|
}};*/
|
||||||
|
|
||||||
|
MainLoop configApp{std::vector<Task*>()};
|
||||||
|
|
||||||
TaskFunc safeModeNag([]{
|
TaskFunc safeModeNag([]{
|
||||||
static uint8_t frame = 0;
|
static uint8_t frame = 0;
|
||||||
EVERY_N_SECONDS(30) {
|
EVERY_N_SECONDS(30) {
|
||||||
Log.notice("I am running in safe mode!");
|
Log.fatal("I am running in safe mode!");
|
||||||
}
|
}
|
||||||
EVERY_N_MILLISECONDS(16) {
|
EVERY_N_MILLISECONDS(16) {
|
||||||
frame++;
|
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(),
|
Static<Platform>::instance(),
|
||||||
|
// System logging
|
||||||
|
Static<LogService>::instance(),
|
||||||
|
&safeModeNag,
|
||||||
|
#ifdef CONFIG_WIFI
|
||||||
// ESP Wifi
|
// ESP Wifi
|
||||||
Static<WiFiTask>::instance(),
|
Static<WiFiTask>::instance(),
|
||||||
// System logging
|
#endif // CONFIG_WIFI
|
||||||
Static<LogService>::instance(),
|
#ifdef CONFIG_MQTT
|
||||||
// MQTT
|
// MQTT
|
||||||
Static<MQTTTelemetry>::instance(),
|
Static<MQTTTelemetry>::instance(),
|
||||||
|
#endif // CONFIG_MQTT
|
||||||
|
#ifdef CONFIG_OTA
|
||||||
// OTA Updates
|
// OTA Updates
|
||||||
Static<ArduinoOTAUpdater>::instance(),
|
Static<ArduinoOTAUpdater>::instance(),
|
||||||
|
#endif // CONFIG_OTA
|
||||||
&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
|
|
||||||
}};
|
}};
|
||||||
|
|
||||||
MainLoop &runner = renderbugApp;
|
MainLoop* runner = &safeModeApp;
|
||||||
|
|
||||||
// Tune in,
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
// Turn on,
|
||||||
Platform::preSetup();
|
Platform::preSetup();
|
||||||
|
#ifdef CONFIG_MQTT
|
||||||
Static<MQTTTelemetry>::instance()->setSequencer(&sequencer);
|
Static<MQTTTelemetry>::instance()->setSequencer(&sequencer);
|
||||||
|
#endif // CONFIG_MQTT
|
||||||
Log.notice(u8"🐛 Booting Renderbug!");
|
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"🐞 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());
|
Log.notice(u8"📡 Platform %s version %s", Platform::name(), Platform::version());
|
||||||
Platform::bootSplash();
|
|
||||||
|
|
||||||
Log.notice(u8"Setting timezone to -7 (PST)");
|
Log.notice(u8"Setting timezone to -7 (PST)");
|
||||||
Platform::setTimezone(-7);
|
Platform::setTimezone(-7);
|
||||||
|
|
||||||
Log.notice(u8" Setting up platform...");
|
Log.notice(u8" Setting up platform...");
|
||||||
Platform::setup();
|
Platform::setup();
|
||||||
|
Platform::bootSplash();
|
||||||
|
|
||||||
Log.notice(u8"💡 Starting FastLED...");
|
Log.notice(u8"💡 Starting FastLED...");
|
||||||
Platform::addLEDs(leds, HardwareConfig::MAX_LED_NUM);
|
Platform::addLEDs(leds, HardwareConfig::MAX_LED_NUM);
|
||||||
|
runner = new MainLoop{std::vector<Task*>{Platform::beginTasks(), Platform::endTasks()}};
|
||||||
|
|
||||||
|
// Tune in,
|
||||||
if (Platform::bootopts.isSafeMode) {
|
if (Platform::bootopts.isSafeMode) {
|
||||||
Log.notice(u8"⚠️ Starting Figment in safe mode!!!");
|
Log.notice(u8"⚠️ Starting Figment in safe mode!!!");
|
||||||
runner = safeModeApp;
|
runner = &safeModeApp;
|
||||||
FastLED.showColor(CRGB(5, 0, 0));
|
FastLED.showColor(CRGB(5, 0, 0));
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
} else if (Platform::bootopts.isSetup) {
|
} else if (Platform::bootopts.isSetup) {
|
||||||
Log.notice(u8"🔧 Starting Figment in configuration mode...");
|
Log.notice(u8"🔧 Starting Figment in configuration mode...");
|
||||||
runner = configApp;
|
//runner = &configApp;
|
||||||
} else {
|
} else {
|
||||||
Log.notice(u8"🌌 Starting Figment...");
|
Log.notice(u8"🌌 Starting Figment...");
|
||||||
}
|
}
|
||||||
Serial.flush();
|
Serial.flush();
|
||||||
runner.start();
|
runner->start();
|
||||||
|
|
||||||
//Log.info(u8"💽 %lu bytes of free RAM", System.freeMemory());
|
//Log.info(u8"💽 %lu bytes of free RAM", System.freeMemory());
|
||||||
Log.notice(u8"🚀 Setup complete! Ready to rock and roll.");
|
Log.notice(u8"🚀 Setup complete! Ready to rock and roll.");
|
||||||
@ -581,6 +530,8 @@ void setup() {
|
|||||||
|
|
||||||
// Drop out.
|
// Drop out.
|
||||||
void loop() {
|
void loop() {
|
||||||
//Platform::loop();
|
EVERY_N_SECONDS(5) {
|
||||||
runner.loop();
|
Log.notice("FPS: %d", FastLED.getFPS());
|
||||||
|
}
|
||||||
|
runner->loop();
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ BluetoothSerialTelemetry::read()
|
|||||||
// Overwrite the '*' character, to leave us with a complete command
|
// Overwrite the '*' character, to leave us with a complete command
|
||||||
commandBuf[cmdSize-1] = 0;
|
commandBuf[cmdSize-1] = 0;
|
||||||
|
|
||||||
//Log.notice("Bluetooth read %s", commandBuf);
|
Log.verbose("Bluetooth read %s", commandBuf);
|
||||||
|
|
||||||
if (commandBuf[0] == 'R') {
|
if (commandBuf[0] == 'R') {
|
||||||
m_color = CRGB(std::atoi(&commandBuf[1]), m_color.g, m_color.b);
|
m_color = CRGB(std::atoi(&commandBuf[1]), m_color.g, m_color.b);
|
||||||
@ -49,6 +49,12 @@ BluetoothSerialTelemetry::read()
|
|||||||
return InputEvent{InputEvent::SetPower, 0};
|
return InputEvent{InputEvent::SetPower, 0};
|
||||||
} else if (commandBuf[0] == 'p') {
|
} else if (commandBuf[0] == 'p') {
|
||||||
return InputEvent{InputEvent::SetPattern, &commandBuf[1]};
|
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') {
|
} else if (commandBuf[0] == 'A') {
|
||||||
char* axisVal = strtok(&commandBuf[1], ",");
|
char* axisVal = strtok(&commandBuf[1], ",");
|
||||||
const uint8_t accelX = std::atof(axisVal) * 10;
|
const uint8_t accelX = std::atof(axisVal) * 10;
|
||||||
@ -75,12 +81,13 @@ BluetoothSerialTelemetry::read()
|
|||||||
void
|
void
|
||||||
BluetoothSerialTelemetry::onStart()
|
BluetoothSerialTelemetry::onStart()
|
||||||
{
|
{
|
||||||
Log.notice("Starting up Bluetooth...");
|
Log.trace("Starting up Bluetooth...");
|
||||||
if (m_serial.begin(Platform::deviceName())) {
|
if (m_serial.begin(Platform::deviceName())) {
|
||||||
Log.notice("Bluetooth started!");
|
Log.notice("Bluetooth started! Device name is %s", Platform::deviceName());
|
||||||
} else {
|
} else {
|
||||||
Log.warning("Bluetooth could not be started!");
|
Log.warning("Bluetooth could not be started!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(BluetoothSerialTelemetry);
|
STATIC_ALLOC(BluetoothSerialTelemetry);
|
||||||
|
STATIC_TASK(BluetoothSerialTelemetry);
|
||||||
|
@ -117,39 +117,37 @@ MQTTTelemetry::handleEventOnline(const InputEvent& evt)
|
|||||||
Log.notice("Connected to MQTT");
|
Log.notice("Connected to MQTT");
|
||||||
m_needHeartbeat = true;
|
m_needHeartbeat = true;
|
||||||
|
|
||||||
StaticJsonDocument<1024> configJson;
|
m_json.clear();
|
||||||
|
Lightswitch.toJson(m_json);
|
||||||
Lightswitch.toJson(configJson);
|
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(const Sequencer::Scene& scene : m_sequencer->scenes()) {
|
for(const Sequencer::Scene& scene : m_sequencer->scenes()) {
|
||||||
configJson["fx_list"][i++] = scene.name;
|
m_json["fx_list"][i++] = scene.name;
|
||||||
}
|
}
|
||||||
configJson["brightness"] = true;
|
m_json["brightness"] = true;
|
||||||
configJson["rgb"] = true;
|
m_json["rgb"] = true;
|
||||||
|
|
||||||
char buf[1024];
|
publishDoc(Lightswitch.configTopic().c_str(), true);
|
||||||
serializeJson(configJson, buf, sizeof(buf));
|
|
||||||
|
|
||||||
Log.verbose("Publish %s %s", Lightswitch.configTopic().c_str(), buf);
|
//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.publish(Lightswitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
|
||||||
m_mqtt.subscribe(Lightswitch.commandTopic().c_str());
|
m_mqtt.subscribe(Lightswitch.commandTopic().c_str());
|
||||||
|
|
||||||
configJson.clear();
|
m_json.clear();
|
||||||
flashlightSwitch.toJson(configJson, false);
|
flashlightSwitch.toJson(m_json, false);
|
||||||
configJson["cmd_t"] = "~/set";
|
m_json["cmd_t"] = "~/set";
|
||||||
configJson["ret"] = true;
|
m_json["ret"] = true;
|
||||||
serializeJson(configJson, buf, sizeof(buf));
|
publishDoc(flashlightSwitch.configTopic().c_str(), true);
|
||||||
m_mqtt.publish(flashlightSwitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
|
//m_mqtt.publish(flashlightSwitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
|
||||||
m_mqtt.subscribe(flashlightSwitch.commandTopic().c_str());
|
m_mqtt.subscribe(flashlightSwitch.commandTopic().c_str());
|
||||||
|
|
||||||
configJson.clear();
|
m_json.clear();
|
||||||
FPSSensor.toJson(configJson, false);
|
FPSSensor.toJson(m_json, false);
|
||||||
configJson["unit_of_meas"] = "Frames/s";
|
m_json["unit_of_meas"] = "Frames/s";
|
||||||
serializeJson(configJson, buf, sizeof(buf));
|
publishDoc(FPSSensor.configTopic().c_str(), true);
|
||||||
|
|
||||||
Log.verbose("Publish %s %s", FPSSensor.configTopic().c_str(), buf);
|
//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.publish(FPSSensor.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
|
||||||
m_mqtt.subscribe(FPSSensor.commandTopic().c_str());
|
m_mqtt.subscribe(FPSSensor.commandTopic().c_str());
|
||||||
|
|
||||||
#ifdef BOARD_ESP8266
|
#ifdef BOARD_ESP8266
|
||||||
@ -172,39 +170,28 @@ MQTTTelemetry::handleEventOnline(const InputEvent& evt)
|
|||||||
String flashlightStatTopic = flashlightSwitch.statTopic();
|
String flashlightStatTopic = flashlightSwitch.statTopic();
|
||||||
m_mqtt.publish(flashlightStatTopic.c_str(), "ON");
|
m_mqtt.publish(flashlightStatTopic.c_str(), "ON");
|
||||||
} else if (evt.intent == InputEvent::SetPower) {
|
} else if (evt.intent == InputEvent::SetPower) {
|
||||||
StaticJsonDocument<256> doc;
|
m_json.clear();
|
||||||
char buf[256];
|
|
||||||
m_isOn = evt.asInt() ? true : false;
|
m_isOn = evt.asInt() ? true : false;
|
||||||
doc["state"] = m_isOn ? "ON" : "OFF";
|
m_json["state"] = m_isOn ? "ON" : "OFF";
|
||||||
serializeJson(doc, buf, sizeof(buf));
|
publishDoc(statTopic.c_str());
|
||||||
m_mqtt.publish(statTopic.c_str(), buf);
|
|
||||||
} else if (evt.intent == InputEvent::SetBrightness) {
|
} else if (evt.intent == InputEvent::SetBrightness) {
|
||||||
StaticJsonDocument<256> doc;
|
m_json.clear();
|
||||||
char buf[256];
|
m_json["brightness"] = evt.asInt();
|
||||||
doc["brightness"] = evt.asInt();
|
m_json["state"] = m_isOn ? "ON" : "OFF";
|
||||||
doc["state"] = m_isOn ? "ON" : "OFF";
|
publishDoc(statTopic.c_str());
|
||||||
serializeJson(doc, buf, sizeof(buf));
|
|
||||||
m_mqtt.publish(statTopic.c_str(), buf);
|
|
||||||
} else if (evt.intent == InputEvent::SetColor) {
|
} else if (evt.intent == InputEvent::SetColor) {
|
||||||
StaticJsonDocument<256> doc;
|
|
||||||
char buf[256];
|
|
||||||
CRGB color = evt.asRGB();
|
CRGB color = evt.asRGB();
|
||||||
doc["color"]["r"] = color.r;
|
m_json.clear();
|
||||||
doc["color"]["g"] = color.g;
|
m_json["color"]["r"] = color.r;
|
||||||
doc["color"]["b"] = color.b;
|
m_json["color"]["g"] = color.g;
|
||||||
doc["state"] = m_isOn ? "ON" : "OFF";
|
m_json["color"]["b"] = color.b;
|
||||||
serializeJson(doc, buf, sizeof(buf));
|
m_json["state"] = m_isOn ? "ON" : "OFF";
|
||||||
m_mqtt.publish(statTopic.c_str(), buf);
|
publishDoc(statTopic.c_str());
|
||||||
} else if (evt.intent == InputEvent::SetPattern) {
|
} else if (evt.intent == InputEvent::SetPattern) {
|
||||||
StaticJsonDocument<256> doc;
|
m_json.clear();
|
||||||
char buf[256];
|
m_json["effect"] = evt.asString();
|
||||||
doc["effect"] = evt.asString();
|
m_json["state"] = m_isOn ? "ON" : "OFF";
|
||||||
doc["state"] = m_isOn ? "ON" : "OFF";
|
publishDoc(statTopic.c_str());
|
||||||
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!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,22 +228,20 @@ MQTTTelemetry::loopOnline()
|
|||||||
m_needHeartbeat = true;
|
m_needHeartbeat = true;
|
||||||
}
|
}
|
||||||
if (m_needHeartbeat) {
|
if (m_needHeartbeat) {
|
||||||
char buf[512];
|
m_json.clear();
|
||||||
StaticJsonDocument<512> response;
|
m_json["device_id"] = Platform::deviceID();
|
||||||
response["device_id"] = Platform::deviceID();
|
m_json["sketch_version"] = ESP.getSketchMD5();
|
||||||
response["sketch_version"] = ESP.getSketchMD5();
|
m_json["os_version"] = ESP.getSdkVersion();
|
||||||
response["os_version"] = ESP.getSdkVersion();
|
m_json["localip"] = WiFi.localIP().toString();
|
||||||
response["localip"] = WiFi.localIP().toString();
|
m_json["pixelCount"] = Static<ConfigService>::instance()->coordMap()->physicalPixelCount();
|
||||||
response["pixelCount"] = Static<ConfigService>::instance()->coordMap()->pixelCount;
|
//m_json["startPixel"] = Static<ConfigService>::instance()->coordMap()->startPixel;
|
||||||
response["startPixel"] = Static<ConfigService>::instance()->coordMap()->startPixel;
|
m_json["RSSI"] = WiFi.RSSI();
|
||||||
response["RSSI"] = WiFi.RSSI();
|
m_json["free_ram"] = ESP.getFreeHeap();
|
||||||
response["free_ram"] = ESP.getFreeHeap();
|
m_json["fps"] = FastLED.getFPS();
|
||||||
response["fps"] = FastLED.getFPS();
|
|
||||||
serializeJson(response, buf, sizeof(buf));
|
|
||||||
String availTopic = m_rootTopic + "/available";
|
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");
|
m_mqtt.publish(Device.availabilityTopic.c_str(), "online");
|
||||||
//Log.notice("Heartbeat: %s", buf);
|
//Log.trace("Heartbeat: %s", buf);
|
||||||
|
|
||||||
String fpsCounter = String(FastLED.getFPS());
|
String fpsCounter = String(FastLED.getFPS());
|
||||||
m_mqtt.publish(FPSSensor.statTopic().c_str(), fpsCounter.c_str());
|
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"});
|
setEvent(InputEvent{InputEvent::SetPattern, "Idle"});
|
||||||
}
|
}
|
||||||
} else if (Lightswitch.isCommandTopic(topic)) {
|
} else if (Lightswitch.isCommandTopic(topic)) {
|
||||||
StaticJsonDocument<512> doc;
|
deserializeJson(m_json, payload);
|
||||||
deserializeJson(doc, payload);
|
|
||||||
|
|
||||||
if (doc.containsKey("state")) {
|
if (m_json.containsKey("state")) {
|
||||||
if (doc["state"] == "ON") {
|
if (m_json["state"] == "ON") {
|
||||||
Log.notice("Turning on power");
|
Log.notice("Turning on power");
|
||||||
setEvent(InputEvent{InputEvent::SetPower, true});
|
setEvent(InputEvent{InputEvent::SetPower, true});
|
||||||
} else if (doc["state"] == "OFF") {
|
} else if (m_json["state"] == "OFF") {
|
||||||
Log.notice("Turning off power");
|
Log.notice("Turning off power");
|
||||||
setEvent(InputEvent{InputEvent::SetPattern, "Idle"});
|
setEvent(InputEvent{InputEvent::SetPattern, "Idle"});
|
||||||
setEvent(InputEvent{InputEvent::SetPower, false});
|
setEvent(InputEvent{InputEvent::SetPower, false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("start")) {
|
if (m_json.containsKey("start")) {
|
||||||
strcpy(m_patternBuf, doc["start"].as<const char*>());
|
strcpy(m_patternBuf, m_json["start"].as<const char*>());
|
||||||
setEvent(InputEvent{InputEvent::StartThing, m_patternBuf});
|
setEvent(InputEvent{InputEvent::StartThing, m_patternBuf});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("stop")) {
|
if (m_json.containsKey("stop")) {
|
||||||
if (doc["stop"] == name) {
|
if (m_json["stop"] == name) {
|
||||||
Log.notice("You can't kill an idea, or stop the MQTT Task via MQTT.");
|
Log.warning("You can't kill an idea, or stop the MQTT Task via MQTT.");
|
||||||
} else {
|
} else {
|
||||||
strcpy(m_patternBuf, doc["stop"].as<const char*>());
|
strcpy(m_patternBuf, m_json["stop"].as<const char*>());
|
||||||
setEvent(InputEvent{InputEvent::StopThing, m_patternBuf});
|
setEvent(InputEvent{InputEvent::StopThing, m_patternBuf});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("pixelCount")) {
|
if (m_json.containsKey("pixelCount")) {
|
||||||
setEvent(InputEvent{InputEvent::SetDisplayLength, (int)doc["pixelCount"]});
|
Log.notice("Pixel count changed");
|
||||||
|
setEvent(InputEvent{InputEvent::SetDisplayLength, m_json["pixelCount"].as<int>()});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("startPixel")) {
|
if (m_json.containsKey("startPixel")) {
|
||||||
setEvent(InputEvent{InputEvent::SetDisplayOffset, (int)doc["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});
|
setEvent(InputEvent{InputEvent::SaveConfigurationRequest});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("restart")) {
|
if (m_json.containsKey("restart")) {
|
||||||
#ifdef BOARD_ESP8266
|
Platform::restart();
|
||||||
ESP.wdtDisable();
|
|
||||||
ESP.restart();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("reconnect")) {
|
if (m_json.containsKey("reconnect")) {
|
||||||
m_mqtt.disconnect();
|
m_mqtt.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("ping")) {
|
if (m_json.containsKey("ping")) {
|
||||||
m_needHeartbeat = true;
|
m_needHeartbeat = true;
|
||||||
Log.notice("Queuing up heartbeat");
|
Log.notice("Queuing up heartbeat");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("effect")) {
|
if (m_json.containsKey("effect")) {
|
||||||
strcpy(m_patternBuf, doc["effect"].as<const char*>());
|
strcpy(m_patternBuf, m_json["effect"].as<const char*>());
|
||||||
setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf});
|
setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("color")) {
|
if (m_json.containsKey("color")) {
|
||||||
uint8_t r = doc["color"]["r"];
|
uint8_t r = m_json["color"]["r"];
|
||||||
uint8_t g = doc["color"]["g"];
|
uint8_t g = m_json["color"]["g"];
|
||||||
uint8_t b = doc["color"]["b"];
|
uint8_t b = m_json["color"]["b"];
|
||||||
setEvent(InputEvent{InputEvent::SetColor, CRGB(r, g, b)});
|
setEvent(InputEvent{InputEvent::SetColor, CRGB(r, g, b)});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc.containsKey("brightness")) {
|
if (m_json.containsKey("brightness")) {
|
||||||
setEvent(InputEvent{InputEvent::SetBrightness, (int)doc["brightness"]});
|
setEvent(InputEvent{InputEvent::SetBrightness, m_json["brightness"].as<int>()});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.notice("Event done.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MQTTTelemetry::s_callback(char* topic, byte* payload, unsigned int length)
|
MQTTTelemetry::s_callback(char* topic, byte* payload, unsigned int length)
|
||||||
{
|
{
|
||||||
char topicBuf[128];
|
strcpy(s_topicBuf, topic);
|
||||||
char payloadBuf[512];
|
memcpy(s_payloadBuf, payload, length);
|
||||||
strcpy(topicBuf, topic);
|
s_payloadBuf[std::min(sizeof(s_payloadBuf) - 1, length)] = 0;
|
||||||
memcpy(payloadBuf, payload, length);
|
Static<MQTTTelemetry>::instance()->callback(s_topicBuf, s_payloadBuf);
|
||||||
payloadBuf[std::min(sizeof(payloadBuf) - 1, length)] = 0;
|
|
||||||
Static<MQTTTelemetry>::instance()->callback(topicBuf, 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_ALLOC(MQTTTelemetry);
|
||||||
|
STATIC_TASK(MQTTTelemetry);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
|
class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
|
||||||
public:
|
public:
|
||||||
@ -25,13 +26,16 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
|
|||||||
public:
|
public:
|
||||||
LogPrinter(MQTTTelemetry* telemetry) : telemetry(telemetry) {};
|
LogPrinter(MQTTTelemetry* telemetry) : telemetry(telemetry) {};
|
||||||
size_t write(uint8_t byte) {
|
size_t write(uint8_t byte) {
|
||||||
char outBuf[512];
|
|
||||||
if (byte == '\n') {
|
if (byte == '\n') {
|
||||||
size_t bufSize = buf.write(outBuf);
|
char c;
|
||||||
outBuf[std::min(sizeof(outBuf), bufSize)] = 0;
|
|
||||||
Serial.println(outBuf);
|
|
||||||
String logTopic = telemetry->m_debugTopic + "/log";
|
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 {
|
} else {
|
||||||
buf.insert(byte);
|
buf.insert(byte);
|
||||||
}
|
}
|
||||||
@ -61,9 +65,15 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
|
|||||||
char m_patternBuf[48];
|
char m_patternBuf[48];
|
||||||
bool m_needHeartbeat = false;
|
bool m_needHeartbeat = false;
|
||||||
bool m_isOn = true;
|
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;
|
Sequencer *m_sequencer = 0;
|
||||||
WiFiClient m_wifi;
|
WiFiClient m_wifi;
|
||||||
PubSubClient m_mqtt;
|
PubSubClient m_mqtt;
|
||||||
LogPrinter m_logPrinter;
|
LogPrinter m_logPrinter;
|
||||||
|
StaticJsonDocument<1024> m_json;
|
||||||
};
|
};
|
||||||
|
34
src/platform/arduino/OTA.cpp
Normal file
34
src/platform/arduino/OTA.cpp
Normal 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);
|
14
src/platform/arduino/OTA.h
Normal file
14
src/platform/arduino/OTA.h
Normal 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);
|
||||||
|
};
|
232
src/platform/arduino/U8Display.cpp
Normal file
232
src/platform/arduino/U8Display.cpp
Normal 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);
|
@ -39,3 +39,4 @@ WiFiTask::read()
|
|||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(WiFiTask);
|
STATIC_ALLOC(WiFiTask);
|
||||||
|
STATIC_TASK(WiFiTask);
|
@ -33,3 +33,4 @@ class MDNSService : public Task {
|
|||||||
};
|
};
|
||||||
|
|
||||||
STATIC_ALLOC(MDNSService);
|
STATIC_ALLOC(MDNSService);
|
||||||
|
STATIC_TASK(MDNSService);
|
||||||
|
@ -202,4 +202,4 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
|
|||||||
};
|
};
|
||||||
|
|
||||||
STATIC_ALLOC(MQTTTelemetry);
|
STATIC_ALLOC(MQTTTelemetry);
|
||||||
|
STATIC_TASK(MQTTTelemetry);
|
||||||
|
@ -67,3 +67,4 @@ PhotonTelemetry::handleEvent(const InputEvent &evt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(PhotonTelemetry);
|
STATIC_ALLOC(PhotonTelemetry);
|
||||||
|
STATIC_TASK(PhotonTelemetry);
|
||||||
|
@ -28,3 +28,4 @@ class Watchdog : public Task {
|
|||||||
};
|
};
|
||||||
|
|
||||||
STATIC_ALLOC(Watchdog);
|
STATIC_ALLOC(Watchdog);
|
||||||
|
STATIC_TASK(Watchdog);
|
||||||
|
@ -154,4 +154,5 @@ class WebTelemetry : public Task {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
STATIC_ALLOC(WebTelemetry);
|
||||||
|
STATIC_TASK(WebTelemetry);
|
||||||
|
@ -26,3 +26,4 @@ CloudStatus::initNetwork(system_event_t event, int param) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(CloudStatus);
|
STATIC_ALLOC(CloudStatus);
|
||||||
|
STATIC_TASK(CloudStatus);
|
||||||
|
@ -182,6 +182,5 @@ PhotonInput::setStartPixel(String command)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
STATIC_ALLOC(PhotonInput);
|
STATIC_ALLOC(PhotonInput);
|
||||||
|
STATIC_TASK(PhotonInput);
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Figments.h>
|
||||||
#include <ArduinoLog.h>
|
#include <ArduinoLog.h>
|
||||||
|
#include <Perfcounter.h>
|
||||||
|
|
||||||
class Blob {
|
class Blob {
|
||||||
uint16_t m_pos;
|
uint8_t m_pos;
|
||||||
int8_t m_velocity;
|
int8_t m_velocity;
|
||||||
uint8_t m_hue;
|
uint8_t m_hue;
|
||||||
int16_t m_brightness;
|
int16_t m_brightness;
|
||||||
@ -49,13 +51,26 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void render(Display* display) const {
|
void render(Display* display) const {
|
||||||
|
PerfCounter _("blobRender");
|
||||||
const uint8_t width = 25;
|
const uint8_t width = 25;
|
||||||
//Log.notice("get coords");
|
|
||||||
auto map = display->coordinateMapping();
|
auto map = display->coordinateMapping();
|
||||||
// Grab the physical pixel we'll start with
|
// Grab the physical pixel we'll start with
|
||||||
PhysicalCoordinates startPos = map->virtualToPhysicalCoords({m_pos, 0});
|
//PhysicalCoordinates startPos = map->virtualToPhysicalCoords({m_pos, m_pos});
|
||||||
PhysicalCoordinates endPos = map->virtualToPhysicalCoords({m_pos + width, 0});
|
//PhysicalCoordinates endPos = map->virtualToPhysicalCoords({m_pos + width, m_pos});
|
||||||
uint8_t scaledWidth = std::abs(endPos.x - startPos.x);
|
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);
|
//Log.notice("blob w=%d x=%d", scaledWidth, startPos.x);
|
||||||
for(uint8_t i = 0;i < scaledWidth; i++) {
|
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((i / (double)scaledWidth) * m_brightness));
|
||||||
CHSV blobColor(m_hue, m_saturation, quadwave8(val));
|
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));
|
CRGB src(display->pixelAt(pos));
|
||||||
display->pixelAt(pos) = blend(CRGB(blobColor), src, 200);
|
display->pixelAt(pos) = blend(CRGB(blobColor), src, 140);
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
11
test/README
11
test/README
@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
This directory is intended for PlatformIO Unit Testing and project tests.
|
|
||||||
|
|
||||||
Unit Testing is a software testing method by which individual units of
|
|
||||||
source code, sets of one or more MCU program modules together with associated
|
|
||||||
control data, usage procedures, and operating procedures, are tested to
|
|
||||||
determine whether they are fit for use. Unit testing finds problems early
|
|
||||||
in the development cycle.
|
|
||||||
|
|
||||||
More information about PlatformIO Unit Testing:
|
|
||||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
|
Loading…
Reference in New Issue
Block a user