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

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

View File

@ -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

View File

@ -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) {}

View File

@ -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;
} }
}; };

View File

@ -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; }

View File

@ -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> {

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;
}; };

View 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;
};

View File

@ -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);
} }
} }
}; };

View File

@ -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;

View File

@ -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;

View File

@ -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);
} }
} }
} }

View File

@ -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;
}; };

View 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"]
}

View File

@ -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
-DCORE_DEBUG_LEVEL=5
-fstack-protector
lib_deps = lib_deps =
fastled/FastLED@^3.4.0 ${common_env_data.lib_deps_external}
thijse/ArduinoLog@^1.0.3
knolleary/PubSubClient@^2.8.0
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

View File

@ -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);

View File

@ -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;
}; };

View File

@ -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);

View File

@ -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;
}; };

View File

@ -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);

View File

@ -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
}
};

View File

@ -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});
} }
} }

View File

@ -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;
}; };

View File

@ -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);

View File

@ -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
View File

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

View File

@ -1,64 +1,58 @@
#include <Figments.h> #include "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;
};

View File

@ -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;
}; };

View File

@ -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);

View File

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

View File

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

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

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

View File

@ -1,50 +1,72 @@
#include <Figments.h> #include "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);
}
};

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
} }

View File

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

View File

@ -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();
} }

View File

@ -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);

View File

@ -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);

View File

@ -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;
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
} }*/
} }
}; };

View File

@ -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