port to platformio

This commit is contained in:
2021-03-29 01:10:55 -07:00
parent 9a3bf84214
commit a6534bcb20
131 changed files with 1537 additions and 1148 deletions

View File

@ -0,0 +1,10 @@
#include "./Animation.h"
#include "./Input.h"
#include "./Display.h"
#include <algorithm>
uint8_t
AnimatedNumber::value() const
{
return lerp8by8(m_start, m_end, m_idx);
}

147
lib/Figments/Animation.h Normal file
View File

@ -0,0 +1,147 @@
#pragma once
#include <FastLED.h>
#include "./Figment.h"
#include <vector>
class Display;
struct AnimatedNumber {
void set(uint8_t v) {
set(value(), v);
}
void set(uint8_t start, uint8_t end) {
m_start = start;
m_end = end;
m_idx = 0;
}
uint8_t value() const;
void update() {
if (m_idx < 255) {
m_idx += 1;
}
}
AnimatedNumber() {}
AnimatedNumber(uint8_t v) : m_end(v) {}
AnimatedNumber& operator=(uint8_t v) {
set(v);
return *this;
}
AnimatedNumber& operator+=(uint8_t v) {
set(value()+v);
return *this;
}
operator uint8_t() const {
return value();
}
operator int() const {
return value();
}
operator unsigned int() const {
return value();
}
bool operator==(int other) const {
return value() == other;
}
uint8_t operator+(uint8_t other) const {
return value() + other;
}
int operator+(int other) const {
return value() + other;
}
private:
uint8_t m_idx = 255;
uint8_t m_start = 0;
uint8_t m_end = 0;
};
struct AnimatedRGB {
CRGB start;
CRGB end;
AnimatedNumber pos;
AnimatedRGB(const CRGB& color)
: start(color), end(color) {}
AnimatedRGB() {}
AnimatedRGB& operator=(const CRGB& rgb) {
start = *this;
end = rgb;
pos.set(0, 255);
return *this;
}
void update() {
pos.update();
}
operator CRGB() const {
uint8_t red = lerp8by8(start.red, end.red, pos);
uint8_t green = lerp8by8(start.green, end.green, pos);
uint8_t blue = lerp8by8(start.blue, end.blue, pos);
return CRGB(red, green, blue);
}
};
template<typename T, int Size>
struct SpriteList {
void update() {
if (!m_enabled) return;
for(int i = 0; i < size; i++) {
animations[i].update();
}
}
void render(Display* dpy) const {
if (!m_enabled) return;
for(int i = 0; i < size; i++) {
animations[i].render(dpy);
}
}
void forEach(std::function<void(T&)> func) {
for(int i = 0; i < size; i++) {
func(animations[i]);
}
}
void forEach(std::function<void(const T&)> func) const {
for(int i = 0; i < size; i++) {
func(animations[i]);
}
}
void disable() {
m_enabled = false;
}
void enable() {
m_enabled = true;
}
void toggle() {
m_enabled = !m_enabled;
}
T& operator[](int idx) {
return animations[idx];
}
T animations[Size];
bool m_enabled = true;
static constexpr int size = Size;
using Type = T;
};

57
lib/Figments/Display.cpp Normal file
View File

@ -0,0 +1,57 @@
#include "Display.h"
#include <math.h>
int
Display::pixelCount() const
{
return m_pixelCount;
}
CRGB*
Display::pixelBacking() const
{
return m_pixels;
}
void
Display::clear()
{
clear(CRGB(0, 0, 0));
}
void
Display::clear(const CRGB& color)
{
for(int i = 0; i < m_pixelCount;i++) {
m_pixels[i] = color;
}
}
CRGB&
Display::pixelAt(const PhysicalCoordinates coords)
{
return pixelAt(m_coordMap->physicalCoordsToIndex(coords));
}
CRGB&
Display::pixelAt(const VirtualCoordinates coords)
{
return pixelAt(m_coordMap->virtualToPhysicalCoords(coords));
}
CRGB&
Display::pixelAt(int idx)
{
const int kx = idx % pixelCount();
if (kx < 0) {
return m_pixels[pixelCount() + 1 + kx];
} else {
return m_pixels[kx];
}
}
const CoordinateMapping*
Display::coordinateMapping() const
{
return m_coordMap;
}

56
lib/Figments/Display.h Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include <FastLED.h>
#include "Geometry.h"
struct CoordinateMapping {
virtual VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const = 0;
virtual PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const = 0;
virtual int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const = 0;
virtual unsigned int physicalPixelCount() const = 0;
};
struct LinearCoordinateMapping: CoordinateMapping {
unsigned int pixelCount = 1;
unsigned int startPixel = 0;
LinearCoordinateMapping() {}
LinearCoordinateMapping(unsigned int count, unsigned int start) : pixelCount(count), startPixel(start) {}
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const {
return VirtualCoordinates{(uint8_t)((localCoords.x) / pixelCount), 0};
}
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const {
return PhysicalCoordinates{scale8(pixelCount, virtualCoords.x), 0};
}
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override {
return localCoords.x + startPixel;
}
unsigned int physicalPixelCount() const {
return pixelCount;
}
};
class Display {
public:
Display(CRGB* pixels, int pixelCount, const CoordinateMapping* map)
: m_pixels(pixels), m_pixelCount(pixelCount), m_coordMap(map) {}
CRGB& pixelAt(const PhysicalCoordinates coords);
CRGB& pixelAt(const VirtualCoordinates coords);
CRGB& pixelAt(int idx);
int pixelCount() const;
CRGB* pixelBacking() const;
const CoordinateMapping* coordinateMapping() const;
void clear();
void clear(const CRGB& color);
void clear(VirtualCoordinates& start, VirtualCoordinates& end, const CRGB& color);
private:
CRGB* m_pixels;
int m_pixelCount;
const CoordinateMapping* m_coordMap;
};

53
lib/Figments/Figment.h Normal file
View File

@ -0,0 +1,53 @@
#pragma once
#include <Arduino.h>
#include <functional>
#include <ArduinoLog.h>
class Display;
class InputEvent;
class InputSource;
struct Loopable {
virtual void handleEvent(const InputEvent& event) {}
virtual void loop() = 0;
};
struct Task : public virtual Loopable {
virtual void onStart() {};
virtual void onStop() {};
enum State {
Running,
Stopped,
};
Task() {}
Task(State initialState) : Task(0, initialState) {}
Task(const char* name) : Task(name, Running) {}
Task(const char* name, State initialState) : name(name), state(initialState) {}
void start() { state = Running; onStart(); }
void stop() { onStop(); state = Stopped; }
virtual bool isFigment() const { return false; }
const char* name = 0;
State state = Running;
};
struct Figment: public Task {
Figment() : Task() {}
Figment(State initialState) : Task(initialState) {}
Figment(const char* name) : Task(name) {}
Figment(const char* name, State initialState) : Task(name, initialState) {}
virtual void render(Display* dpy) const = 0;
bool isFigment() const override { return true; }
};
struct FigmentFunc: public Figment {
FigmentFunc(std::function<void(Display*)> func) : Figment("lambda"), func(func) {}
void loop() override {}
void render(Display* dpy) const override {
func(dpy);
}
std::function<void(Display*)> func;
};

8
lib/Figments/Figments.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include "./Display.h"
#include "./Input.h"
#include "./Figment.h"
#include "./Animation.h"
#include "./MainLoop.h"
#include "./Renderer.h"
#include "./Surface.h"

39
lib/Figments/Geometry.h Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include <stdint.h>
template<typename T> struct Coordinates {
Coordinates(T _x, T _y) : x(_x), y(_y) {}
T x;
T y;
};
struct VirtualCoordinates: Coordinates<uint16_t> {
VirtualCoordinates(uint16_t _x, uint16_t _y) : Coordinates(_x, _y) {}
};
struct PhysicalCoordinates: Coordinates<uint16_t> {
PhysicalCoordinates(uint16_t _x, uint16_t _y) : Coordinates(_x, _y) {}
};
template<typename T> struct Vector3d {
Vector3d(T _x, T _y, T _z) : x(_x), y(_y), z(_z) {}
Vector3d() : Vector3d(0, 0, 0) {}
T x;
T y;
T z;
T magnitude() const {
return abs(max(x, max(y, z)));
}
Vector3d<T> operator-(const Vector3d<T>& other) const {
return Vector3d<T>(x - other.x, y - other.y, z - other.z);
}
Vector3d<T> absolute() const {
return Vector3d<T>(abs(x), abs(y), abs(z));
}
};
typedef Vector3d<uint8_t> Vec3;

47
lib/Figments/Input.cpp Normal file
View File

@ -0,0 +1,47 @@
#include <Arduino.h>
#include "./Input.h"
#include "./MainLoop.h"
CRGB
Variant::asRGB() const
{
return CRGB(m_value.asRGB[0], m_value.asRGB[1], m_value.asRGB[2]);
}
const char*
Variant::asString() const
{
return m_value.asString;
}
int
Variant::asInt() const
{
return m_value.asInt;
}
void
InputSource::loop()
{
MainLoop::instance()->dispatch(read());
}
InputEvent
BufferedInputSource::read()
{
InputEvent ret;
m_eventQueue.take(ret);
return ret;
}
void
BufferedInputSource::setEvent(InputEvent &&evt)
{
m_eventQueue.insert(std::move(evt));
}
void
BufferedInputSource::setEvent(InputEvent::Intent intent, Variant &&v)
{
m_eventQueue.insert(InputEvent{intent, std::move(v)});
}

178
lib/Figments/Input.h Normal file
View File

@ -0,0 +1,178 @@
#pragma once
#include <Arduino.h>
#include "./Geometry.h"
#include "./Figment.h"
#include "./Ringbuf.h"
#include <FastLED.h>
typedef Vector3d<int> MotionVec;
struct Variant {
enum Type {
Null,
Integer,
String,
Color,
};
Variant(int v)
: type(Integer), m_value{.asInt=v} {}
Variant(const char* v)
: type(String), m_value{.asString=v} {}
Variant(const CRGB &v)
: type(Color), m_value{.asRGB={v.r, v.g, v.b}} {}
Variant()
: type(Null) {}
Type type;
const char* asString() const;
CRGB asRGB() const;
int asInt() const;
private:
union {
int asInt;
const char* asString;
uint8_t asRGB[3];
} m_value;
};
struct InputEvent: public Variant {
enum Intent {
// An empty non-event
None,
// An input from the user, for other tasks to translate into canonical
// types. Makes for easy button re-mapping on the fly.
UserInput,
//
// The canonical types
//
// Hardware inputs
ButtonPress,
Acceleration,
NetworkStatus,
NetworkActivity,
// Power management
PowerToggle,
SetPower,
SetBrightness,
// Animation sequencing
PreviousPattern,
NextPattern,
SetPattern,
PreviousScene,
NextScene,
SetScene,
// Timekeeping
ScheduleChange,
// Task management
StartThing,
StopThing,
// Configuration
SetDisplayOffset,
SetDisplayLength,
SetColor,
SaveConfigurationRequest,
// Firmware events
FirmwareUpdate,
};
template<typename Value>
InputEvent(Intent s, Value v)
: Variant(v), intent(s) {}
InputEvent(Intent s)
: Variant(), intent(s) {}
InputEvent()
: Variant(), intent(None) {}
Intent intent;
};
class InputSource: public Task {
public:
InputSource() : Task() {}
InputSource(const char* name) : Task(name) {}
InputSource(Task::State initialState) : Task(initialState) {}
InputSource(const char* name, Task::State initialState) : Task(name, initialState) {}
void loop() override;
virtual InputEvent read() = 0;
};
class InputFunc : public InputSource {
public:
InputFunc(std::function<InputEvent(void)> f) : InputSource(), m_func(f) {}
InputFunc(std::function<InputEvent(void)> f, const char* name) : InputSource(name), m_func(f) {}
InputFunc(std::function<InputEvent(void)> f, const char* name, Task::State initialState) : InputSource(name, initialState), m_func(f) {}
InputEvent read() override {
return m_func();
}
private:
std::function<InputEvent(void)> m_func;
};
class BufferedInputSource: public InputSource {
public:
BufferedInputSource() : InputSource() {}
BufferedInputSource(const char* name) : InputSource(name) {}
InputEvent read() override;
protected:
void setEvent(InputEvent &&evt);
void setEvent(InputEvent::Intent intent, Variant &&v);
private:
Ringbuf<InputEvent, 5> m_eventQueue;
};
class InputMapper: public BufferedInputSource {
public:
InputMapper(std::function<InputEvent(const InputEvent)> f, const char* name) : BufferedInputSource(name), m_func(f) {}
void handleEvent(const InputEvent& evt) override {
setEvent(m_func(evt));
}
private:
std::function<InputEvent(const InputEvent)> m_func;
};
class OnlineTaskMixin : public virtual Loopable {
public:
void handleEvent(const InputEvent &evt) override {
if (evt.intent == InputEvent::NetworkStatus) {
m_online = evt.asInt();
}
if (m_online) {
handleEventOnline(evt);
}
}
virtual void handleEventOnline(const InputEvent &evt) = 0;
void loop() override {
if (m_online) {
loopOnline();
}
}
virtual void loopOnline() = 0;
private:
bool m_online = false;
};

58
lib/Figments/MainLoop.cpp Normal file
View File

@ -0,0 +1,58 @@
#include "./MainLoop.h"
#include "./Input.h"
#include "./Figment.h"
#include <ArduinoLog.h>
void
MainLoop::dispatch(const InputEvent& evt)
{
if (evt.intent == InputEvent::None) {
return;
}
m_eventBuf.insert(evt);
}
void
MainLoop::loop()
{
InputEvent evt;
while (m_eventBuf.take(evt)) {
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
const bool jobState = (evt.intent == InputEvent::StartThing);
for(auto figmentJob: scheduler.tasks) {
if (!strcmp(figmentJob->name, evt.asString())) {
if (jobState) {
//Log.notice("Starting %s", figmentJob->name);
figmentJob->start();
} else {
//Log.notice("Stopping %s", figmentJob->name);
figmentJob->stop();
}
}
}
}
for(Task* task : scheduler) {
task->handleEvent(evt);
}
}
for(Task* task : scheduler) {
//Log.notice("Running %s", task->name);
task->loop();
//Log.notice("next");
}
}
void
MainLoop::start()
{
Log.notice("*** Starting %d tasks...", scheduler.tasks.size());
Serial.flush();
for(auto task: scheduler) {
Log.notice("** Starting %s", task->name);
task->start();
}
}
MainLoop* MainLoop::s_instance;

69
lib/Figments/MainLoop.h Normal file
View File

@ -0,0 +1,69 @@
#pragma once
#include <vector>
#include <algorithm>
#include "./Input.h"
#include "./Ringbuf.h"
class Task;
class InputSource;
struct Scheduler {
std::vector<Task*> tasks;
bool operator==(const Scheduler& other) const {
return tasks == other.tasks;
}
Scheduler(std::vector<Task*> &&tasks)
: tasks(std::move(tasks))
{}
struct iterator: public std::iterator<std::input_iterator_tag, Task*> {
Scheduler& queue;
int idx = 0;
explicit iterator(Scheduler& queue) : queue(queue), idx(nextEnabled(0)) {}
iterator(Scheduler& queue, int start) : queue(queue), idx(nextEnabled(start)) {}
iterator& operator++() {
while (idx < queue.tasks.size() && !queue.tasks[idx]->state == Task::Running) {
idx++;
}
idx = nextEnabled(idx+1);
return *this;
}
int nextEnabled(int start) const {
for(int pos = start; pos < queue.tasks.size();pos++) {
if (queue.tasks[pos]->state == Task::Running) {
return pos;
}
}
return queue.tasks.size();
}
iterator operator++(int) {iterator ret = *this; ++(*this); return ret;}
bool operator==(iterator other) const { return idx == other.idx && queue == other.queue; }
bool operator!=(iterator other) const { return !(*this == other); }
Task* operator*() const { return queue.tasks[idx]; }
};
iterator begin() { return iterator(*this); }
iterator end() { return iterator(*this, tasks.size()); }
};
struct MainLoop {
Scheduler scheduler;
MainLoop(std::vector<Task*> &&tasks)
: scheduler(std::move(tasks)) {s_instance = this;}
void start();
void loop();
void dispatch(const InputEvent& event);
static MainLoop* instance() { return s_instance; }
private:
Ringbuf<InputEvent, 10> m_eventBuf;
static MainLoop* s_instance;
};

31
lib/Figments/Renderer.cpp Normal file
View File

@ -0,0 +1,31 @@
#include "./Renderer.h"
#include "./Display.h"
#include <ArduinoLog.h>
void
Renderer::loop()
{
for(Display* dpy : m_displays) {
for(Figment* figment : m_figments) {
if (figment->state == Task::Running) {
//Log.notice("Rendering %s", figment->name);
figment->render(dpy);
//Log.notice("next");
} else {
//Log.notice("Not rendering %s", figment->name);
}
};
}
FastLED.show();
FastLED.countFPS();
}
void
Renderer::onStart()
{
for(Display* dpy : m_displays) {
dpy->clear();
}
FastLED.show();
}

16
lib/Figments/Renderer.h Normal file
View File

@ -0,0 +1,16 @@
#include "./Figment.h"
#include <vector>
class Display;
struct Renderer: public Task {
public:
Renderer(std::vector<Display*> displays, const std::vector<Figment*> &figments) : Task("Renderer"), m_figments(figments), m_displays(displays) {}
void loop() override;
void onStart() override;
private:
const std::vector<Figment*> m_figments;
const std::vector<Display*> m_displays;
};

39
lib/Figments/Ringbuf.h Normal file
View File

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

0
lib/Figments/Service.h Normal file
View File

40
lib/Figments/Surface.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "./Surface.h"
#include "./Display.h"
#include <ArduinoLog.h>
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end)
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)),
m_display(dpy)
{
}
Surface&
Surface::operator=(const CRGB& color)
{
paintWith([&](CRGB& pixel) {
pixel = color;
});
return *this;
}
Surface&
Surface::operator+=(const CRGB& color)
{
paintWith([&](CRGB& pixel) {
pixel += color;
});
return *this;
}
void
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);
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);
func(m_display->pixelAt(PhysicalCoordinates{x, y}));
}
}
}

21
lib/Figments/Surface.h Normal file
View File

@ -0,0 +1,21 @@
#include <FastLED.h>
#include "./Geometry.h"
#include <functional>
class Display;
class Surface {
public:
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end);
Surface& operator=(const CRGB& color);
Surface& operator+=(const CRGB& color);
void paintWith(std::function<void(CRGB&)> func);
const PhysicalCoordinates start;
const PhysicalCoordinates end;
private:
Display* m_display;
};

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html