port to platformio
This commit is contained in:
42
src/BootOptions.cpp
Normal file
42
src/BootOptions.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "BootOptions.h"
|
||||
|
||||
#ifdef PLATFORM_PHOTON
|
||||
LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND);
|
||||
LEDStatus configStatus = LEDStatus(RGB_COLOR_YELLOW, LED_PATTERN_FADE, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT);
|
||||
retained bool LAST_BOOT_WAS_FLASH;
|
||||
retained bool LAST_BOOT_WAS_SERIAL;
|
||||
#endif
|
||||
|
||||
void
|
||||
BootOptions::initPins()
|
||||
{
|
||||
#ifdef PLATFORM_PHOTON
|
||||
pinMode(2, INPUT_PULLDOWN);
|
||||
pinMode(3, INPUT_PULLDOWN);
|
||||
pinMode(4, INPUT_PULLDOWN);
|
||||
#endif
|
||||
}
|
||||
|
||||
BootOptions::BootOptions()
|
||||
{
|
||||
#ifdef PLATFORM_PHOTON
|
||||
isSetup = digitalRead(2) == HIGH;
|
||||
isSerial = digitalRead(3) == HIGH || LAST_BOOT_WAS_SERIAL;
|
||||
isFlash = digitalRead(4) == HIGH;
|
||||
|
||||
LAST_BOOT_WAS_FLASH = isFlash;
|
||||
LAST_BOOT_WAS_SERIAL |= isSerial;
|
||||
lastBootWasFlash = LAST_BOOT_WAS_FLASH;
|
||||
|
||||
configStatus.setActive(isSetup);
|
||||
serialStatus.setActive(isSerial);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
BootOptions::waitForRelease()
|
||||
{
|
||||
#ifdef PLATFORM_PHOTON
|
||||
while(digitalRead(2) == HIGH || digitalRead(3) == HIGH) {};
|
||||
#endif
|
||||
}
|
14
src/BootOptions.h
Normal file
14
src/BootOptions.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
struct BootOptions {
|
||||
static void initPins();
|
||||
|
||||
BootOptions();
|
||||
|
||||
void waitForRelease();
|
||||
|
||||
bool isSetup = false;
|
||||
bool isSerial = false;
|
||||
bool isFlash = false;
|
||||
bool lastBootWasFlash = false;
|
||||
};
|
109
src/Config.cpp
Normal file
109
src/Config.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
#include "./Config.h"
|
||||
#include "./Static.h"
|
||||
#include <ArduinoLog.h>
|
||||
|
||||
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
||||
|
||||
HardwareConfig
|
||||
HardwareConfig::load() {
|
||||
HardwareConfig ret;
|
||||
#ifdef PLATFORM_PHOTON
|
||||
EEPROM.get(0, ret);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
HardwareConfig::save() {
|
||||
HardwareConfig dataCopy{*this};
|
||||
dataCopy.checksum = getCRC();
|
||||
#ifdef PLATFORM_PHOTON
|
||||
EEPROM.put(0, dataCopy);
|
||||
#endif
|
||||
}
|
||||
|
||||
LinearCoordinateMapping
|
||||
HardwareConfig::toCoordMap() const
|
||||
{
|
||||
auto pixelCount = min(HardwareConfig::MAX_LED_NUM, std::max((uint16_t)1, data.pixelCount));
|
||||
auto startPixel = min(pixelCount, std::max((uint16_t)1, data.startPixel));
|
||||
return LinearCoordinateMapping{pixelCount, startPixel};
|
||||
}
|
||||
|
||||
bool
|
||||
HardwareConfig::isValid() const
|
||||
{
|
||||
return version == 2 && checksum == getCRC() && data.pixelCount <= MAX_LED_NUM;
|
||||
}
|
||||
|
||||
uint8_t
|
||||
HardwareConfig::getCRC() const
|
||||
{
|
||||
const unsigned char* message = reinterpret_cast<const unsigned char*>(&data);
|
||||
constexpr uint8_t length = sizeof(data);
|
||||
unsigned char i, j, crc = 0;
|
||||
for(i = 0; i < length; i++) {
|
||||
crc ^= message[i];
|
||||
for(j = 0; j < 8; j++) {
|
||||
if (crc & 1) {
|
||||
crc ^= CRC7_POLY;
|
||||
}
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigService::onStart()
|
||||
{
|
||||
Log.notice("Starting configuration service...");
|
||||
m_config = HardwareConfig::load();
|
||||
if (m_config.isValid()) {
|
||||
Log.notice("Configuration found!");
|
||||
} else {
|
||||
Log.notice("No configuration found. Writing defaults...");
|
||||
m_config = HardwareConfig{};
|
||||
m_config.save();
|
||||
}
|
||||
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("Loading task states...");
|
||||
for(int i = 0; i < 32; i++) {
|
||||
auto svc = m_config.data.serviceStates[i];
|
||||
if (strlen(svc.name) > 0) {
|
||||
Log.notice("* %s: %s", svc.name, svc.isDisabled? "DISABLED" : "ENABLED");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ConfigService::loop()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ConfigService::handleEvent(const InputEvent &evt)
|
||||
{
|
||||
switch(evt.intent) {
|
||||
case InputEvent::SetDisplayLength:
|
||||
//Log.info("Updating pixel count from %d to %d", m_coordMap.pixelCount, evt.asInt());
|
||||
m_config.data.pixelCount = evt.asInt();
|
||||
m_coordMap = m_config.toCoordMap();
|
||||
//Log.info("Count is now %d", m_coordMap.pixelCount);
|
||||
break;
|
||||
case InputEvent::SetDisplayOffset:
|
||||
//Log.info("Updating pixel offset from %d to %d", m_coordMap.startPixel, evt.asInt());
|
||||
m_config.data.startPixel = evt.asInt();
|
||||
m_coordMap = m_config.toCoordMap();
|
||||
//Log.info("Offset is now %d", m_coordMap.startPixel);
|
||||
break;
|
||||
case InputEvent::SaveConfigurationRequest:
|
||||
//Log.info("Saving configuration");
|
||||
m_config.save();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
STATIC_ALLOC(ConfigService);
|
53
src/Config.h
Normal file
53
src/Config.h
Normal file
@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include <Figments.h>
|
||||
|
||||
//#define PLATFORM_PHOTON
|
||||
#define PLATFORM_ARDUINO
|
||||
|
||||
struct HardwareConfig {
|
||||
uint8_t version = 2;
|
||||
uint8_t checksum = 0;
|
||||
struct TaskState {
|
||||
char name[16] = {0};
|
||||
bool isDisabled = false;
|
||||
};
|
||||
struct Data {
|
||||
uint16_t pixelCount = 255;
|
||||
uint16_t startPixel = 0;
|
||||
uint8_t lastRed = 255;
|
||||
uint8_t lastGreen = 255;
|
||||
uint8_t lastBlue = 255;
|
||||
char lastScene[16] = {0};
|
||||
TaskState serviceStates[32];
|
||||
};
|
||||
Data data;
|
||||
|
||||
static HardwareConfig load();
|
||||
void save();
|
||||
bool isValid() const;
|
||||
|
||||
LinearCoordinateMapping toCoordMap() const;
|
||||
|
||||
static constexpr uint16_t MAX_LED_NUM = 255;
|
||||
static constexpr bool HAS_MPU_6050 = false;
|
||||
|
||||
private:
|
||||
uint8_t getCRC() const;
|
||||
|
||||
static constexpr uint8_t CRC7_POLY = 0x91;
|
||||
};
|
||||
|
||||
// A task that manages the EEPROM settings and coord mapping when modified via
|
||||
// Particle. This allows for multiple devices with wildly different displays to
|
||||
// run the same code
|
||||
struct ConfigService: public Task {
|
||||
ConfigService() : Task("Configuration") {}
|
||||
void onStart();
|
||||
void loop() override;
|
||||
void handleEvent(const InputEvent &evt) override;
|
||||
const LinearCoordinateMapping* coordMap() const { return &m_coordMap; }
|
||||
|
||||
private:
|
||||
HardwareConfig m_config;
|
||||
LinearCoordinateMapping m_coordMap;
|
||||
};
|
61
src/Sequencer.cpp
Normal file
61
src/Sequencer.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include "Sequencer.h"
|
||||
#include <MainLoop.h>
|
||||
|
||||
Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes) :
|
||||
Task("SceneSequencer"),
|
||||
m_scenes(std::move(scenes))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Sequencer::loop() {}
|
||||
|
||||
const char*
|
||||
Sequencer::currentSceneName()
|
||||
{
|
||||
return m_scenes[m_idx].name;
|
||||
}
|
||||
|
||||
const std::vector<Sequencer::Scene>
|
||||
Sequencer::scenes() const
|
||||
{
|
||||
return m_scenes;
|
||||
}
|
||||
|
||||
void
|
||||
Sequencer::handleEvent(const InputEvent& evt)
|
||||
{
|
||||
if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) {
|
||||
Log.notice("Switching pattern!");
|
||||
for(const char* pattern : m_scenes[m_idx].patterns) {
|
||||
Log.notice("Stopping %s", pattern);
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern});
|
||||
}
|
||||
|
||||
if (evt.intent == InputEvent::NextPattern) {
|
||||
m_idx++;
|
||||
} else if (evt.intent == InputEvent::PreviousPattern) {
|
||||
m_idx--;
|
||||
} else {
|
||||
//m_idx = evt.asInt();
|
||||
for(m_idx = 0; m_idx < m_scenes.size(); m_idx++) {
|
||||
if (!strcmp(evt.asString(), m_scenes[m_idx].name)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_idx < 0) {
|
||||
m_idx = m_scenes.size() - 1;
|
||||
}
|
||||
|
||||
if (m_idx >= m_scenes.size()) {
|
||||
m_idx = 0;
|
||||
}
|
||||
|
||||
for(const char* pattern : m_scenes[m_idx].patterns) {
|
||||
Log.notice("Starting %s", pattern);
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
|
||||
}
|
||||
}
|
||||
}
|
26
src/Sequencer.h
Normal file
26
src/Sequencer.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <Figment.h>
|
||||
#include <vector>
|
||||
|
||||
class Sequencer: public Task {
|
||||
public:
|
||||
|
||||
class Scene {
|
||||
public:
|
||||
const char* name;
|
||||
std::vector<const char*> patterns;
|
||||
};
|
||||
|
||||
Sequencer(std::vector<Scene> &&scenes);
|
||||
|
||||
void loop() override;
|
||||
void handleEvent(const InputEvent& evt) override;
|
||||
|
||||
const char* currentSceneName();
|
||||
const std::vector<Scene> scenes() const;
|
||||
|
||||
private:
|
||||
int m_idx = 0;
|
||||
std::vector<Scene> m_scenes;
|
||||
};
|
16
src/Static.h
Normal file
16
src/Static.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
// Utility class mostly for when certain inputs need singleton callback handlers
|
||||
template<typename T> class Static {
|
||||
public:
|
||||
static T* instance() {
|
||||
return s_instance;
|
||||
}
|
||||
private:
|
||||
static T* s_instance;
|
||||
};
|
||||
|
||||
#define NAMED_STATIC_ALLOC(Cls, StaticName) static Cls _staticAlloc__ ## StaticName;\
|
||||
template<> Cls* Static<Cls>::s_instance=&_staticAlloc__ ## StaticName;
|
||||
|
||||
#define STATIC_ALLOC(Cls) NAMED_STATIC_ALLOC(Cls, Cls)
|
49
src/WiFiTask.cpp
Normal file
49
src/WiFiTask.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include <Input.h>
|
||||
#include <ArduinoLog.h>
|
||||
#include <WiFi.h>
|
||||
#include "Static.h"
|
||||
#include "WiFiTask.h"
|
||||
|
||||
WiFiTask::WiFiTask() : InputSource("WiFi") {}
|
||||
|
||||
void
|
||||
WiFiTask::onStart()
|
||||
{
|
||||
Log.notice("Starting wifi...");
|
||||
WiFi.mode(WIFI_STA);
|
||||
int n = WiFi.scanNetworks();
|
||||
if (n == 0) {
|
||||
Log.notice("No wifi found");
|
||||
} else {
|
||||
for(int i = 0; i < n; ++i) {
|
||||
Serial.print("WiFi: ");
|
||||
Serial.println(WiFi.SSID(i));
|
||||
}
|
||||
}
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin("The Frequency", "thepasswordkenneth");
|
||||
while(WiFi.status() != WL_CONNECTED) {
|
||||
Serial.print('.');
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
InputEvent
|
||||
WiFiTask::read()
|
||||
{
|
||||
uint8_t curStatus = WiFi.status();
|
||||
if (m_lastStatus != curStatus) {
|
||||
m_lastStatus = curStatus;
|
||||
Log.verbose("WiFi Status: %d", curStatus);
|
||||
if (curStatus == WL_CONNECTED) {
|
||||
Log.notice("Connected!");
|
||||
return InputEvent{InputEvent::NetworkStatus, true};
|
||||
} else if (curStatus == WL_CONNECTION_LOST || curStatus == WL_DISCONNECTED) {
|
||||
Log.notice("Lost wifi connection!");
|
||||
return InputEvent{InputEvent::NetworkStatus, false};
|
||||
}
|
||||
}
|
||||
return InputEvent{};
|
||||
}
|
||||
|
||||
STATIC_ALLOC(WiFiTask);
|
12
src/WiFiTask.h
Normal file
12
src/WiFiTask.h
Normal file
@ -0,0 +1,12 @@
|
||||
#include <Input.h>
|
||||
#include <WiFi.h>
|
||||
#include "Static.h"
|
||||
|
||||
class WiFiTask : public InputSource {
|
||||
public:
|
||||
WiFiTask();
|
||||
void onStart() override;
|
||||
InputEvent read() override;
|
||||
private:
|
||||
uint8_t m_lastStatus = WL_IDLE_STATUS;
|
||||
};
|
75
src/animations/Chimes.cpp
Normal file
75
src/animations/Chimes.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#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(Task::State initialState) : Figment("Chimes", initialState) {
|
||||
m_chimes.forEach([](Chime<CHIME_LENGTH> &chime) {
|
||||
chime.setPos(random(Chime<CHIME_LENGTH>::Length * 5));
|
||||
chime.setHue(random(255));
|
||||
chime.setSpeed(random(90) + 138);
|
||||
chime.setBrightness(200);
|
||||
chime.setOffset(random(1024));
|
||||
});
|
||||
m_blobs.forEach([](Blob &blob) {
|
||||
blob.setPos(random(255));
|
||||
blob.setHue(random(255));
|
||||
blob.setBrightness(random(255));
|
||||
if (random(255) % 2) {
|
||||
blob.setVelocity(-1);
|
||||
} else {
|
||||
blob.setVelocity(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void handleEvent(const InputEvent& evt) override {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
m_chimes.update();
|
||||
m_blobs.update();
|
||||
m_flashColor.update();
|
||||
EVERY_N_MILLISECONDS(5) {
|
||||
m_flashBrightness.update();
|
||||
}
|
||||
}
|
||||
|
||||
void render(Display* dpy) const override {
|
||||
m_chimes.render(dpy);
|
||||
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;
|
||||
};
|
64
src/animations/Drain.cpp
Normal file
64
src/animations/Drain.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include <Figments.h>
|
||||
#include <ArduinoLog.h>
|
||||
|
||||
class DrainAnimation: public Figment {
|
||||
public:
|
||||
|
||||
DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {}
|
||||
|
||||
void loop() override {
|
||||
EVERY_N_MILLISECONDS(8) {
|
||||
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;
|
||||
|
||||
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_burst;
|
||||
};
|
63
src/animations/Drain.h
Normal file
63
src/animations/Drain.h
Normal file
@ -0,0 +1,63 @@
|
||||
class DrainAnimation: public Figment {
|
||||
public:
|
||||
|
||||
DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {}
|
||||
|
||||
void loop() override {
|
||||
EVERY_N_MILLISECONDS(8) {
|
||||
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;
|
||||
|
||||
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_burst;
|
||||
};
|
||||
|
||||
|
47
src/animations/Flashlight.cpp
Normal file
47
src/animations/Flashlight.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <Figments.h>
|
||||
#include "../sprites/Blob.h"
|
||||
|
||||
class Flashlight: public Figment {
|
||||
public:
|
||||
Flashlight(Task::State initialState) : Figment("Flashlight", initialState) {
|
||||
m_blobs.forEach([](Blob &blob) {
|
||||
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) {
|
||||
if (evt.asInt() == 1) {
|
||||
m_blobs.forEach([](Blob& blob) {blob.setPos(random(255));});
|
||||
}
|
||||
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:
|
||||
static constexpr int blobCount = 30;
|
||||
SpriteList<Blob, blobCount> m_blobs;
|
||||
};
|
40
src/animations/Power.cpp
Normal file
40
src/animations/Power.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
50
src/animations/SolidAnimation.cpp
Normal file
50
src/animations/SolidAnimation.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include <Figments.h>
|
||||
#include "../sprites/Blob.h"
|
||||
|
||||
class SolidAnimation: public Figment {
|
||||
private:
|
||||
AnimatedNumber m_red, m_green, m_blue;
|
||||
static constexpr int blobCount = 20;
|
||||
SpriteList<Blob, blobCount> m_blobs;
|
||||
|
||||
public:
|
||||
SolidAnimation(Task::State initialState) : Figment("Solid", initialState) {
|
||||
m_blobs.forEach([](Blob& blob) {
|
||||
blob.setPos(random(140));
|
||||
blob.setBrightness(random(255));
|
||||
if (random(255) % 2) {
|
||||
blob.setVelocity(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void handleEvent(const InputEvent& evt) override {
|
||||
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 {
|
||||
m_red.update();
|
||||
m_green.update();
|
||||
m_blue.update();
|
||||
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 render(Display* dpy) const override {
|
||||
CRGB color(m_red.value(), m_green.value(), m_blue.value());
|
||||
Surface(dpy, {0, 0}, {255, 0}) = color;
|
||||
m_blobs.render(dpy);
|
||||
}
|
||||
};
|
53
src/animations/TestAnimation.cpp
Normal file
53
src/animations/TestAnimation.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "./TestAnimation.h"
|
||||
#include <FastLED.h>
|
||||
|
||||
const char*
|
||||
TestAnimation::name() const
|
||||
{
|
||||
return "Test";
|
||||
}
|
||||
|
||||
void
|
||||
TestAnimation::handleEvent(const InputEvent& evt)
|
||||
{
|
||||
if (evt.intent == InputEvent::Acceleration) {
|
||||
if (evt.asInt() > 5) {
|
||||
m_brightness += 15;
|
||||
}
|
||||
m_hue += scale8(evt.asInt(), 128);
|
||||
}
|
||||
|
||||
if (evt.intent == InputEvent::UserInput) {
|
||||
switch(evt.asInt()) {
|
||||
case 1:
|
||||
m_brightness.set(255, 0);break;
|
||||
case 2:
|
||||
m_saturation.set(255, 128);break;
|
||||
default:
|
||||
m_brightness.set(255, 0);
|
||||
m_saturation.set(255, 128);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TestAnimation::loop()
|
||||
{
|
||||
m_x += 4;
|
||||
if (m_x % 12 == 0) {
|
||||
m_y += 28;
|
||||
}
|
||||
m_hue.update();
|
||||
m_saturation.update();
|
||||
m_brightness.update();
|
||||
}
|
||||
|
||||
void
|
||||
TestAnimation::render(Display* dpy) const
|
||||
{
|
||||
for(uint8_t col = 0; col < 3; col++) {
|
||||
for (uint8_t i = 0; i < 254; i+=10) {
|
||||
dpy->pixelAt(VirtualCoordinates{(uint8_t)(m_x + (col * (254 / 3))), (uint8_t)(i + m_y)}) = CHSV(m_hue, m_saturation + 100, scale8(i, m_brightness));
|
||||
}
|
||||
}
|
||||
}
|
16
src/animations/TestAnimation.h
Normal file
16
src/animations/TestAnimation.h
Normal file
@ -0,0 +1,16 @@
|
||||
#include <Figments.h>
|
||||
|
||||
class TestAnimation: public Figment {
|
||||
public:
|
||||
const char* name() const;
|
||||
void handleEvent(const InputEvent& evt) override;
|
||||
void loop() override;
|
||||
void render(Display* dpy) const override;
|
||||
|
||||
private:
|
||||
AnimatedNumber m_hue;
|
||||
AnimatedNumber m_saturation;
|
||||
AnimatedNumber m_brightness;
|
||||
uint8_t m_x;
|
||||
uint8_t m_y;
|
||||
};
|
34
src/animations/UpdateStatus.cpp
Normal file
34
src/animations/UpdateStatus.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include "./UpdateStatus.h"
|
||||
#include <FastLED.h>
|
||||
#include "../Static.h"
|
||||
|
||||
void
|
||||
UpdateStatus::handleEvent(const InputEvent& evt)
|
||||
{
|
||||
if (evt.intent == InputEvent::FirmwareUpdate) {
|
||||
static int updateCount = 0;
|
||||
updateCount++;
|
||||
//Log.info("Update count %d", updateCount);
|
||||
m_updateReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
UpdateStatus::loop()
|
||||
{
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
void
|
||||
UpdateStatus::render(Display* dpy) const
|
||||
{
|
||||
if (m_updateReady) {
|
||||
for(int i = 0; i < 12; i+=3) {
|
||||
dpy->pixelAt(m_pos + i) = CRGB(255, 0, 0);
|
||||
dpy->pixelAt(m_pos + i + 1) = CRGB(0, 255, 0);
|
||||
dpy->pixelAt(m_pos + i + 2) = CRGB(0, 0, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
STATIC_ALLOC(UpdateStatus);
|
13
src/animations/UpdateStatus.h
Normal file
13
src/animations/UpdateStatus.h
Normal file
@ -0,0 +1,13 @@
|
||||
#include <Figments.h>
|
||||
|
||||
class UpdateStatus: public Figment {
|
||||
public:
|
||||
UpdateStatus() : Figment("UpdateStatusAnimation") {}
|
||||
void handleEvent(const InputEvent& evt) override;
|
||||
void loop() override;
|
||||
void render(Display* dpy) const override;
|
||||
|
||||
private:
|
||||
bool m_updateReady = false;
|
||||
uint8_t m_pos = 0;
|
||||
};
|
813
src/colors.cpp
Normal file
813
src/colors.cpp
Normal file
@ -0,0 +1,813 @@
|
||||
#include "colors.h"
|
||||
|
||||
const ColorInfo color_data[] = {
|
||||
{ "Air Superiority Blue", { 114, 160, 193 } },
|
||||
{ "Alabama Crimson", { 163, 38, 56 } },
|
||||
{ "Alice Blue", { 240, 248, 255 } },
|
||||
{ "Alizarin Crimson", { 227, 38, 54 } },
|
||||
{ "Alloy Orange", { 196, 98, 16 } },
|
||||
{ "Almond", { 239, 222, 205 } },
|
||||
{ "Amaranth", { 229, 43, 80 } },
|
||||
{ "Amber", { 255, 191, 0 } },
|
||||
{ "American Rose", { 255, 3, 62 } },
|
||||
{ "Amethyst", { 153, 102, 204 } },
|
||||
{ "Android Green", { 164, 198, 57 } },
|
||||
{ "Anti-Flash White", { 242, 243, 244 } },
|
||||
{ "Antique Brass", { 205, 149, 117 } },
|
||||
{ "Antique Fuchsia", { 145, 92, 131 } },
|
||||
{ "Antique Ruby", { 132, 27, 45 } },
|
||||
{ "Antique White", { 250, 235, 215 } },
|
||||
{ "Apple Green", { 141, 182, 0 } },
|
||||
{ "Apricot", { 251, 206, 177 } },
|
||||
{ "Aqua", { 0, 255, 255 } },
|
||||
{ "Aquamarine", { 127, 255, 212 } },
|
||||
{ "Army Green", { 75, 83, 32 } },
|
||||
{ "Arsenic", { 59, 68, 75 } },
|
||||
{ "Arylide Yellow", { 233, 214, 107 } },
|
||||
{ "Ash Grey", { 178, 190, 181 } },
|
||||
{ "Asparagus", { 135, 169, 107 } },
|
||||
{ "Atomic Tangerine", { 255, 153, 102 } },
|
||||
{ "Auburn", { 165, 42, 42 } },
|
||||
{ "Aureolin", { 253, 238, 0 } },
|
||||
{ "Aurometalsaurus", { 110, 127, 128 } },
|
||||
{ "Avocado", { 86, 130, 3 } },
|
||||
{ "Azure", { 0, 127, 255 } },
|
||||
{ "Baby Blue", { 137, 207, 240 } },
|
||||
{ "Baby Blue Eyes", { 161, 202, 241 } },
|
||||
{ "Baby Pink", { 244, 194, 194 } },
|
||||
{ "Ball Blue", { 33, 171, 205 } },
|
||||
{ "Banana Mania", { 250, 231, 181 } },
|
||||
{ "Banana Yellow", { 255, 225, 53 } },
|
||||
{ "Barn Red", { 124, 10, 2 } },
|
||||
{ "Battleship Grey", { 132, 132, 130 } },
|
||||
{ "Bazaar", { 152, 119, 123 } },
|
||||
{ "Beau Blue", { 188, 212, 230 } },
|
||||
{ "Beaver", { 159, 129, 112 } },
|
||||
{ "Beige", { 245, 245, 220 } },
|
||||
{ "Big Dip O’Ruby", { 156, 37, 66 } },
|
||||
{ "Bisque", { 255, 228, 196 } },
|
||||
{ "Bistre", { 61, 43, 31 } },
|
||||
{ "Bittersweet", { 254, 111, 94 } },
|
||||
{ "Bittersweet Shimmer", { 191, 79, 81 } },
|
||||
{ "Black", { 0, 0, 0 } },
|
||||
{ "Black Bean", { 61, 12, 2 } },
|
||||
{ "Black Leather Jacket", { 37, 53, 41 } },
|
||||
{ "Black Olive", { 59, 60, 54 } },
|
||||
{ "Blanched Almond", { 255, 235, 205 } },
|
||||
{ "Blast-Off Bronze", { 165, 113, 100 } },
|
||||
{ "Bleu De France", { 49, 140, 231 } },
|
||||
{ "Blizzard Blue", { 172, 229, 238 } },
|
||||
{ "Blond", { 250, 240, 190 } },
|
||||
{ "Blue", { 0, 0, 255 } },
|
||||
{ "Blue Gray", { 102, 153, 204 } },
|
||||
{ "Blue-Green", { 13, 152, 186 } },
|
||||
{ "Blue Sapphire", { 18, 97, 128 } },
|
||||
{ "Blue-Violet", { 138, 43, 226 } },
|
||||
{ "Blush", { 222, 93, 131 } },
|
||||
{ "Bole", { 121, 68, 59 } },
|
||||
{ "Bondi Blue", { 0, 149, 182 } },
|
||||
{ "Bone", { 227, 218, 201 } },
|
||||
{ "Boston University Red", { 204, 0, 0 } },
|
||||
{ "Bottle Green", { 0, 106, 78 } },
|
||||
{ "Boysenberry", { 135, 50, 96 } },
|
||||
{ "Brandeis Blue", { 0, 112, 255 } },
|
||||
{ "Brass", { 181, 166, 66 } },
|
||||
{ "Brick Red", { 203, 65, 84 } },
|
||||
{ "Bright Cerulean", { 29, 172, 214 } },
|
||||
{ "Bright Green", { 102, 255, 0 } },
|
||||
{ "Bright Lavender", { 191, 148, 228 } },
|
||||
{ "Bright Maroon", { 195, 33, 72 } },
|
||||
{ "Bright Pink", { 255, 0, 127 } },
|
||||
{ "Bright Turquoise", { 8, 232, 222 } },
|
||||
{ "Bright Ube", { 209, 159, 232 } },
|
||||
{ "Brilliant Lavender", { 244, 187, 255 } },
|
||||
{ "Brilliant Rose", { 255, 85, 163 } },
|
||||
{ "Brink Pink", { 251, 96, 127 } },
|
||||
{ "British Racing Green", { 0, 66, 37 } },
|
||||
{ "Bronze", { 205, 127, 50 } },
|
||||
{ "Brown", { 150, 75, 0 } },
|
||||
{ "Bubble Gum", { 255, 193, 204 } },
|
||||
{ "Bubbles", { 231, 254, 255 } },
|
||||
{ "Buff", { 240, 220, 130 } },
|
||||
{ "Bulgarian Rose", { 72, 6, 7 } },
|
||||
{ "Burgundy", { 128, 0, 32 } },
|
||||
{ "Burlywood", { 222, 184, 135 } },
|
||||
{ "Burnt Orange", { 204, 85, 0 } },
|
||||
{ "Burnt Sienna", { 233, 116, 81 } },
|
||||
{ "Burnt Umber", { 138, 51, 36 } },
|
||||
{ "Byzantine", { 189, 51, 164 } },
|
||||
{ "Byzantium", { 112, 41, 99 } },
|
||||
{ "Cadet", { 83, 104, 114 } },
|
||||
{ "Cadet Blue", { 95, 158, 160 } },
|
||||
{ "Cadet Grey", { 145, 163, 176 } },
|
||||
{ "Cadmium Green", { 0, 107, 60 } },
|
||||
{ "Cadmium Orange", { 237, 135, 45 } },
|
||||
{ "Cadmium Red", { 227, 0, 34 } },
|
||||
{ "Cadmium Yellow", { 255, 246, 0 } },
|
||||
{ "Cal Poly Green", { 30, 77, 43 } },
|
||||
{ "Cambridge Blue", { 163, 193, 173 } },
|
||||
{ "Camel", { 193, 154, 107 } },
|
||||
{ "Cameo Pink", { 239, 187, 204 } },
|
||||
{ "Camouflage Green", { 120, 134, 107 } },
|
||||
{ "Canary Yellow", { 255, 239, 0 } },
|
||||
{ "Candy Apple Red", { 255, 8, 0 } },
|
||||
{ "Candy Pink", { 228, 113, 122 } },
|
||||
{ "Capri", { 0, 191, 255 } },
|
||||
{ "Caput Mortuum", { 89, 39, 32 } },
|
||||
{ "Cardinal", { 196, 30, 58 } },
|
||||
{ "Caribbean Green", { 0, 204, 153 } },
|
||||
{ "Carmine", { 150, 0, 24 } },
|
||||
{ "Carmine Pink", { 235, 76, 66 } },
|
||||
{ "Carmine Red", { 255, 0, 56 } },
|
||||
{ "Carnation Pink", { 255, 166, 201 } },
|
||||
{ "Carnelian", { 179, 27, 27 } },
|
||||
{ "Carolina Blue", { 153, 186, 221 } },
|
||||
{ "Carrot Orange", { 237, 145, 33 } },
|
||||
{ "Catalina Blue", { 6, 42, 120 } },
|
||||
{ "Ceil", { 146, 161, 207 } },
|
||||
{ "Celadon", { 172, 225, 175 } },
|
||||
{ "Celadon Blue", { 0, 123, 167 } },
|
||||
{ "Celadon Green", { 47, 132, 124 } },
|
||||
{ "Celeste", { 178, 255, 255 } },
|
||||
{ "Celestial Blue", { 73, 151, 208 } },
|
||||
{ "Cerise", { 222, 49, 99 } },
|
||||
{ "Cerise Pink", { 236, 59, 131 } },
|
||||
{ "Cerulean", { 0, 123, 167 } },
|
||||
{ "Cerulean Blue", { 42, 82, 190 } },
|
||||
{ "Cerulean Frost", { 109, 155, 195 } },
|
||||
{ "Cg Blue", { 0, 122, 165 } },
|
||||
{ "Cg Red", { 224, 60, 49 } },
|
||||
{ "Chamoisee", { 160, 120, 90 } },
|
||||
{ "Champagne", { 250, 214, 165 } },
|
||||
{ "Charcoal", { 54, 69, 79 } },
|
||||
{ "Charm Pink", { 230, 143, 172 } },
|
||||
{ "Chartreuse", { 223, 255, 0 } },
|
||||
{ "Cherry", { 222, 49, 99 } },
|
||||
{ "Cherry Blossom Pink", { 255, 183, 197 } },
|
||||
{ "Chestnut", { 205, 92, 92 } },
|
||||
{ "China Pink", { 222, 111, 161 } },
|
||||
{ "China Rose", { 168, 81, 110 } },
|
||||
{ "Chinese Red", { 170, 56, 30 } },
|
||||
{ "Chocolate", { 123, 63, 0 } },
|
||||
{ "Chrome Yellow", { 255, 167, 0 } },
|
||||
{ "Cinereous", { 152, 129, 123 } },
|
||||
{ "Cinnabar", { 227, 66, 52 } },
|
||||
{ "Cinnamon", { 210, 105, 30 } },
|
||||
{ "Citrine", { 228, 208, 10 } },
|
||||
{ "Classic Rose", { 251, 204, 231 } },
|
||||
{ "Cobalt", { 0, 71, 171 } },
|
||||
{ "Cocoa Brown", { 210, 105, 30 } },
|
||||
{ "Coffee", { 111, 78, 55 } },
|
||||
{ "Columbia Blue", { 155, 221, 255 } },
|
||||
{ "Congo Pink", { 248, 131, 121 } },
|
||||
{ "Cool Black", { 0, 46, 99 } },
|
||||
{ "Cool Grey", { 140, 146, 172 } },
|
||||
{ "Copper", { 184, 115, 51 } },
|
||||
{ "Copper Penny", { 173, 111, 105 } },
|
||||
{ "Copper Red", { 203, 109, 81 } },
|
||||
{ "Copper Rose", { 153, 102, 102 } },
|
||||
{ "Coquelicot", { 255, 56, 0 } },
|
||||
{ "Coral", { 255, 127, 80 } },
|
||||
{ "Coral Pink", { 248, 131, 121 } },
|
||||
{ "Coral Red", { 255, 64, 64 } },
|
||||
{ "Cordovan", { 137, 63, 69 } },
|
||||
{ "Corn", { 251, 236, 93 } },
|
||||
{ "Cornell Red", { 179, 27, 27 } },
|
||||
{ "Cornflower Blue", { 100, 149, 237 } },
|
||||
{ "Cornsilk", { 255, 248, 220 } },
|
||||
{ "Cosmic Latte", { 255, 248, 231 } },
|
||||
{ "Cotton Candy", { 255, 188, 217 } },
|
||||
{ "Cream", { 255, 253, 208 } },
|
||||
{ "Crimson", { 220, 20, 60 } },
|
||||
{ "Crimson Glory", { 190, 0, 50 } },
|
||||
{ "Cyan", { 0, 255, 255 } },
|
||||
{ "Daffodil", { 255, 255, 49 } },
|
||||
{ "Dandelion", { 240, 225, 48 } },
|
||||
{ "Dark Blue", { 0, 0, 139 } },
|
||||
{ "Dark Brown", { 101, 67, 33 } },
|
||||
{ "Dark Byzantium", { 93, 57, 84 } },
|
||||
{ "Dark Candy Apple Red", { 164, 0, 0 } },
|
||||
{ "Dark Cerulean", { 8, 69, 126 } },
|
||||
{ "Dark Chestnut", { 152, 105, 96 } },
|
||||
{ "Dark Coral", { 205, 91, 69 } },
|
||||
{ "Dark Cyan", { 0, 139, 139 } },
|
||||
{ "Dark Electric Blue", { 83, 104, 120 } },
|
||||
{ "Dark Goldenrod", { 184, 134, 11 } },
|
||||
{ "Dark Gray", { 169, 169, 169 } },
|
||||
{ "Dark Green", { 1, 50, 32 } },
|
||||
{ "Dark Imperial Blue", { 0, 65, 106 } },
|
||||
{ "Dark Jungle Green", { 26, 36, 33 } },
|
||||
{ "Dark Khaki", { 189, 183, 107 } },
|
||||
{ "Dark Lava", { 72, 60, 50 } },
|
||||
{ "Dark Lavender", { 115, 79, 150 } },
|
||||
{ "Dark Magenta", { 139, 0, 139 } },
|
||||
{ "Dark Midnight Blue", { 0, 51, 102 } },
|
||||
{ "Dark Olive Green", { 85, 107, 47 } },
|
||||
{ "Dark Orange", { 255, 140, 0 } },
|
||||
{ "Dark Orchid", { 153, 50, 204 } },
|
||||
{ "Dark Pastel Blue", { 119, 158, 203 } },
|
||||
{ "Dark Pastel Green", { 3, 192, 60 } },
|
||||
{ "Dark Pastel Purple", { 150, 111, 214 } },
|
||||
{ "Dark Pastel Red", { 194, 59, 34 } },
|
||||
{ "Dark Pink", { 231, 84, 128 } },
|
||||
{ "Dark Powder Blue", { 0, 51, 153 } },
|
||||
{ "Dark Raspberry", { 135, 38, 87 } },
|
||||
{ "Dark Red", { 139, 0, 0 } },
|
||||
{ "Dark Salmon", { 233, 150, 122 } },
|
||||
{ "Dark Scarlet", { 86, 3, 25 } },
|
||||
{ "Dark Sea Green", { 143, 188, 143 } },
|
||||
{ "Dark Sienna", { 60, 20, 20 } },
|
||||
{ "Dark Slate Blue", { 72, 61, 139 } },
|
||||
{ "Dark Slate Gray", { 47, 79, 79 } },
|
||||
{ "Dark Spring Green", { 23, 114, 69 } },
|
||||
{ "Dark Tan", { 145, 129, 81 } },
|
||||
{ "Dark Tangerine", { 255, 168, 18 } },
|
||||
{ "Dark Taupe", { 72, 60, 50 } },
|
||||
{ "Dark Terra Cotta", { 204, 78, 92 } },
|
||||
{ "Dark Turquoise", { 0, 206, 209 } },
|
||||
{ "Dark Violet", { 148, 0, 211 } },
|
||||
{ "Dark Yellow", { 155, 135, 12 } },
|
||||
{ "Dartmouth Green", { 0, 112, 60 } },
|
||||
{ "Deep Carmine", { 169, 32, 62 } },
|
||||
{ "Deep Carmine Pink", { 239, 48, 56 } },
|
||||
{ "Deep Carrot Orange", { 233, 105, 44 } },
|
||||
{ "Deep Cerise", { 218, 50, 135 } },
|
||||
{ "Deep Champagne", { 250, 214, 165 } },
|
||||
{ "Deep Chestnut", { 185, 78, 72 } },
|
||||
{ "Deep Coffee", { 112, 66, 65 } },
|
||||
{ "Deep Fuchsia", { 193, 84, 193 } },
|
||||
{ "Deep Jungle Green", { 0, 75, 73 } },
|
||||
{ "Deep Lilac", { 153, 85, 187 } },
|
||||
{ "Deep Magenta", { 204, 0, 204 } },
|
||||
{ "Deep Peach", { 255, 203, 164 } },
|
||||
{ "Deep Pink", { 255, 20, 147 } },
|
||||
{ "Deep Ruby", { 132, 63, 91 } },
|
||||
{ "Deep Saffron", { 255, 153, 51 } },
|
||||
{ "Deep Sky Blue", { 0, 191, 255 } },
|
||||
{ "Deep Tuscan Red", { 102, 66, 77 } },
|
||||
{ "Denim", { 21, 96, 189 } },
|
||||
{ "Desert", { 193, 154, 107 } },
|
||||
{ "Desert Sand", { 237, 201, 175 } },
|
||||
{ "Dim Gray", { 105, 105, 105 } },
|
||||
{ "Dodger Blue", { 30, 144, 255 } },
|
||||
{ "Dogwood Rose", { 215, 24, 104 } },
|
||||
{ "Dollar Bill", { 133, 187, 101 } },
|
||||
{ "Drab", { 150, 113, 23 } },
|
||||
{ "Duke Blue", { 0, 0, 156 } },
|
||||
{ "Earth Yellow", { 225, 169, 95 } },
|
||||
{ "Ebony", { 85, 93, 80 } },
|
||||
{ "Ecru", { 194, 178, 128 } },
|
||||
{ "Eggplant", { 97, 64, 81 } },
|
||||
{ "Eggshell", { 240, 234, 214 } },
|
||||
{ "Egyptian Blue", { 16, 52, 166 } },
|
||||
{ "Electric Blue", { 125, 249, 255 } },
|
||||
{ "Electric Crimson", { 255, 0, 63 } },
|
||||
{ "Electric Cyan", { 0, 255, 255 } },
|
||||
{ "Electric Green", { 0, 255, 0 } },
|
||||
{ "Electric Indigo", { 111, 0, 255 } },
|
||||
{ "Electric Lavender", { 244, 187, 255 } },
|
||||
{ "Electric Lime", { 204, 255, 0 } },
|
||||
{ "Electric Purple", { 191, 0, 255 } },
|
||||
{ "Electric Ultramarine", { 63, 0, 255 } },
|
||||
{ "Electric Violet", { 143, 0, 255 } },
|
||||
{ "Electric Yellow", { 255, 255, 0 } },
|
||||
{ "Emerald", { 80, 200, 120 } },
|
||||
{ "English Lavender", { 180, 131, 149 } },
|
||||
{ "Eton Blue", { 150, 200, 162 } },
|
||||
{ "Fallow", { 193, 154, 107 } },
|
||||
{ "Falu Red", { 128, 24, 24 } },
|
||||
{ "Fandango", { 181, 51, 137 } },
|
||||
{ "Fashion Fuchsia", { 244, 0, 161 } },
|
||||
{ "Fawn", { 229, 170, 112 } },
|
||||
{ "Feldgrau", { 77, 93, 83 } },
|
||||
{ "Fern Green", { 79, 121, 66 } },
|
||||
{ "Ferrari Red", { 255, 40, 0 } },
|
||||
{ "Field Drab", { 108, 84, 30 } },
|
||||
{ "Fire Engine Red", { 206, 32, 41 } },
|
||||
{ "Firebrick", { 178, 34, 34 } },
|
||||
{ "Flame", { 226, 88, 34 } },
|
||||
{ "Flamingo Pink", { 252, 142, 172 } },
|
||||
{ "Flavescent", { 247, 233, 142 } },
|
||||
{ "Flax", { 238, 220, 130 } },
|
||||
{ "Floral White", { 255, 250, 240 } },
|
||||
{ "Fluorescent Orange", { 255, 191, 0 } },
|
||||
{ "Fluorescent Pink", { 255, 20, 147 } },
|
||||
{ "Fluorescent Yellow", { 204, 255, 0 } },
|
||||
{ "Folly", { 255, 0, 79 } },
|
||||
{ "Forest Green", { 1, 68, 33 } },
|
||||
{ "French Beige", { 166, 123, 91 } },
|
||||
{ "French Blue", { 0, 114, 187 } },
|
||||
{ "French Lilac", { 134, 96, 142 } },
|
||||
{ "French Lime", { 204, 255, 0 } },
|
||||
{ "French Raspberry", { 199, 44, 72 } },
|
||||
{ "French Rose", { 246, 74, 138 } },
|
||||
{ "Fuchsia", { 255, 0, 255 } },
|
||||
{ "Fuchsia Pink", { 255, 119, 255 } },
|
||||
{ "Fuchsia Rose", { 199, 67, 117 } },
|
||||
{ "Fulvous", { 228, 132, 0 } },
|
||||
{ "Fuzzy Wuzzy", { 204, 102, 102 } },
|
||||
{ "Gainsboro", { 220, 220, 220 } },
|
||||
{ "Gamboge", { 228, 155, 15 } },
|
||||
{ "Ghost White", { 248, 248, 255 } },
|
||||
{ "Ginger", { 176, 101, 0 } },
|
||||
{ "Glaucous", { 96, 130, 182 } },
|
||||
{ "Glitter", { 230, 232, 250 } },
|
||||
{ "Gold", { 212, 175, 55 } },
|
||||
{ "Golden Brown", { 153, 101, 21 } },
|
||||
{ "Golden Poppy", { 252, 194, 0 } },
|
||||
{ "Golden Yellow", { 255, 223, 0 } },
|
||||
{ "Goldenrod", { 218, 165, 32 } },
|
||||
{ "Granny Smith Apple", { 168, 228, 160 } },
|
||||
{ "Gray", { 128, 128, 128 } },
|
||||
{ "Gray-Asparagus", { 70, 89, 69 } },
|
||||
{ "Green", { 0, 255, 0 } },
|
||||
{ "Green-Yellow", { 173, 255, 47 } },
|
||||
{ "Grullo", { 169, 154, 134 } },
|
||||
{ "Guppie Green", { 0, 255, 127 } },
|
||||
{ "Halayà úBe", { 102, 56, 84 } },
|
||||
{ "Han Blue", { 68, 108, 207 } },
|
||||
{ "Han Purple", { 82, 24, 250 } },
|
||||
{ "Hansa Yellow", { 233, 214, 107 } },
|
||||
{ "Harlequin", { 63, 255, 0 } },
|
||||
{ "Harvard Crimson", { 201, 0, 22 } },
|
||||
{ "Harvest Gold", { 218, 145, 0 } },
|
||||
{ "Heart Gold", { 128, 128, 0 } },
|
||||
{ "Heliotrope", { 223, 115, 255 } },
|
||||
{ "Hollywood Cerise", { 244, 0, 161 } },
|
||||
{ "Honeydew", { 240, 255, 240 } },
|
||||
{ "Honolulu Blue", { 0, 127, 191 } },
|
||||
{ "Hooker'S Green", { 73, 121, 107 } },
|
||||
{ "Hot Magenta", { 255, 29, 206 } },
|
||||
{ "Hot Pink", { 255, 105, 180 } },
|
||||
{ "Hunter Green", { 53, 94, 59 } },
|
||||
{ "Iceberg", { 113, 166, 210 } },
|
||||
{ "Icterine", { 252, 247, 94 } },
|
||||
{ "Imperial Blue", { 0, 35, 149 } },
|
||||
{ "Inchworm", { 178, 236, 93 } },
|
||||
{ "India Green", { 19, 136, 8 } },
|
||||
{ "Indian Red", { 205, 92, 92 } },
|
||||
{ "Indian Yellow", { 227, 168, 87 } },
|
||||
{ "Indigo", { 111, 0, 255 } },
|
||||
{ "International Klein Blue", { 0, 47, 167 } },
|
||||
{ "International Orange", { 186, 22, 12 } },
|
||||
{ "Iris", { 90, 79, 207 } },
|
||||
{ "Isabelline", { 244, 240, 236 } },
|
||||
{ "Islamic Green", { 0, 144, 0 } },
|
||||
{ "Ivory", { 255, 255, 240 } },
|
||||
{ "Jade", { 0, 168, 107 } },
|
||||
{ "Jasmine", { 248, 222, 126 } },
|
||||
{ "Jasper", { 215, 59, 62 } },
|
||||
{ "Jazzberry Jam", { 165, 11, 94 } },
|
||||
{ "Jet", { 52, 52, 52 } },
|
||||
{ "Jonquil", { 250, 218, 94 } },
|
||||
{ "June Bud", { 189, 218, 87 } },
|
||||
{ "Jungle Green", { 41, 171, 135 } },
|
||||
{ "Kelly Green", { 76, 187, 23 } },
|
||||
{ "Kenyan Copper", { 124, 28, 5 } },
|
||||
{ "Khaki", { 195, 176, 145 } },
|
||||
{ "Ku Crimson", { 232, 0, 13 } },
|
||||
{ "La Salle Green", { 8, 120, 48 } },
|
||||
{ "Languid Lavender", { 214, 202, 221 } },
|
||||
{ "Lapis Lazuli", { 38, 97, 156 } },
|
||||
{ "Laser Lemon", { 254, 254, 34 } },
|
||||
{ "Laurel Green", { 169, 186, 157 } },
|
||||
{ "Lava", { 207, 16, 32 } },
|
||||
{ "Lavender Blue", { 204, 204, 255 } },
|
||||
{ "Lavender Blush", { 255, 240, 245 } },
|
||||
{ "Lavender Gray", { 196, 195, 208 } },
|
||||
{ "Lavender Indigo", { 148, 87, 235 } },
|
||||
{ "Lavender Magenta", { 238, 130, 238 } },
|
||||
{ "Lavender Mist", { 230, 230, 250 } },
|
||||
{ "Lavender Pink", { 251, 174, 210 } },
|
||||
{ "Lavender Purple", { 150, 123, 182 } },
|
||||
{ "Lavender Rose", { 251, 160, 227 } },
|
||||
{ "Lawn Green", { 124, 252, 0 } },
|
||||
{ "Lemon", { 255, 247, 0 } },
|
||||
{ "Lemon Chiffon", { 255, 250, 205 } },
|
||||
{ "Lemon Lime", { 227, 255, 0 } },
|
||||
{ "Licorice", { 26, 17, 16 } },
|
||||
{ "Light Apricot", { 253, 213, 177 } },
|
||||
{ "Light Blue", { 173, 216, 230 } },
|
||||
{ "Light Brown", { 181, 101, 29 } },
|
||||
{ "Light Carmine Pink", { 230, 103, 113 } },
|
||||
{ "Light Coral", { 240, 128, 128 } },
|
||||
{ "Light Cornflower Blue", { 147, 204, 234 } },
|
||||
{ "Light Crimson", { 245, 105, 145 } },
|
||||
{ "Light Cyan", { 224, 255, 255 } },
|
||||
{ "Light Fuchsia Pink", { 249, 132, 239 } },
|
||||
{ "Light Goldenrod Yellow", { 250, 250, 210 } },
|
||||
{ "Light Gray", { 211, 211, 211 } },
|
||||
{ "Light Green", { 144, 238, 144 } },
|
||||
{ "Light Khaki", { 240, 230, 140 } },
|
||||
{ "Light Pastel Purple", { 177, 156, 217 } },
|
||||
{ "Light Pink", { 255, 182, 193 } },
|
||||
{ "Light Red Ochre", { 233, 116, 81 } },
|
||||
{ "Light Salmon", { 255, 160, 122 } },
|
||||
{ "Light Salmon Pink", { 255, 153, 153 } },
|
||||
{ "Light Sea Green", { 32, 178, 170 } },
|
||||
{ "Light Sky Blue", { 135, 206, 250 } },
|
||||
{ "Light Slate Gray", { 119, 136, 153 } },
|
||||
{ "Light Taupe", { 179, 139, 109 } },
|
||||
{ "Light Thulian Pink", { 230, 143, 172 } },
|
||||
{ "Light Yellow", { 255, 255, 224 } },
|
||||
{ "Lilac", { 200, 162, 200 } },
|
||||
{ "Lime Green", { 50, 205, 50 } },
|
||||
{ "Limerick", { 157, 194, 9 } },
|
||||
{ "Lincoln Green", { 25, 89, 5 } },
|
||||
{ "Linen", { 250, 240, 230 } },
|
||||
{ "Lion", { 193, 154, 107 } },
|
||||
{ "Little Boy Blue", { 108, 160, 220 } },
|
||||
{ "Liver", { 83, 75, 79 } },
|
||||
{ "Lust", { 230, 32, 32 } },
|
||||
{ "Magenta", { 255, 0, 255 } },
|
||||
{ "Magic Mint", { 170, 240, 209 } },
|
||||
{ "Magnolia", { 248, 244, 255 } },
|
||||
{ "Mahogany", { 192, 64, 0 } },
|
||||
{ "Maize", { 251, 236, 93 } },
|
||||
{ "Majorelle Blue", { 96, 80, 220 } },
|
||||
{ "Malachite", { 11, 218, 81 } },
|
||||
{ "Manatee", { 151, 154, 170 } },
|
||||
{ "Mango Tango", { 255, 130, 67 } },
|
||||
{ "Mantis", { 116, 195, 101 } },
|
||||
{ "Mardi Gras", { 136, 0, 133 } },
|
||||
{ "Maroon", { 128, 0, 0 } },
|
||||
{ "Mauve", { 224, 176, 255 } },
|
||||
{ "Mauve Taupe", { 145, 95, 109 } },
|
||||
{ "Mauvelous", { 239, 152, 170 } },
|
||||
{ "Maya Blue", { 115, 194, 251 } },
|
||||
{ "Meat Brown", { 229, 183, 59 } },
|
||||
{ "Medium Aquamarine", { 102, 221, 170 } },
|
||||
{ "Medium Blue", { 0, 0, 205 } },
|
||||
{ "Medium Candy Apple Red", { 226, 6, 44 } },
|
||||
{ "Medium Carmine", { 175, 64, 53 } },
|
||||
{ "Medium Champagne", { 243, 229, 171 } },
|
||||
{ "Medium Electric Blue", { 3, 80, 150 } },
|
||||
{ "Medium Jungle Green", { 28, 53, 45 } },
|
||||
{ "Medium Lavender Magenta", { 221, 160, 221 } },
|
||||
{ "Medium Orchid", { 186, 85, 211 } },
|
||||
{ "Medium Persian Blue", { 0, 103, 165 } },
|
||||
{ "Medium Purple", { 147, 112, 219 } },
|
||||
{ "Medium Red-Violet", { 187, 51, 133 } },
|
||||
{ "Medium Ruby", { 170, 64, 105 } },
|
||||
{ "Medium Sea Green", { 60, 179, 113 } },
|
||||
{ "Medium Slate Blue", { 123, 104, 238 } },
|
||||
{ "Medium Spring Bud", { 201, 220, 135 } },
|
||||
{ "Medium Spring Green", { 0, 250, 154 } },
|
||||
{ "Medium Taupe", { 103, 76, 71 } },
|
||||
{ "Medium Turquoise", { 72, 209, 204 } },
|
||||
{ "Medium Tuscan Red", { 121, 68, 59 } },
|
||||
{ "Medium Vermilion", { 217, 96, 59 } },
|
||||
{ "Medium Violet-Red", { 199, 21, 133 } },
|
||||
{ "Mellow Apricot", { 248, 184, 120 } },
|
||||
{ "Mellow Yellow", { 248, 222, 126 } },
|
||||
{ "Melon", { 253, 188, 180 } },
|
||||
{ "Midnight Blue", { 25, 25, 112 } },
|
||||
{ "Mikado Yellow", { 255, 196, 12 } },
|
||||
{ "Mint", { 62, 180, 137 } },
|
||||
{ "Mint Cream", { 245, 255, 250 } },
|
||||
{ "Mint Green", { 152, 255, 152 } },
|
||||
{ "Misty Rose", { 255, 228, 225 } },
|
||||
{ "Moccasin", { 250, 235, 215 } },
|
||||
{ "Mode Beige", { 150, 113, 23 } },
|
||||
{ "Moonstone Blue", { 115, 169, 194 } },
|
||||
{ "Mordant Red 19", { 174, 12, 0 } },
|
||||
{ "Moss Green", { 173, 223, 173 } },
|
||||
{ "Mountain Meadow", { 48, 186, 143 } },
|
||||
{ "Mountbatten Pink", { 153, 122, 141 } },
|
||||
{ "Msu Green", { 24, 69, 59 } },
|
||||
{ "Mulberry", { 197, 75, 140 } },
|
||||
{ "Mustard", { 255, 219, 88 } },
|
||||
{ "Myrtle", { 33, 66, 30 } },
|
||||
{ "Nadeshiko Pink", { 246, 173, 198 } },
|
||||
{ "Napier Green", { 42, 128, 0 } },
|
||||
{ "Naples Yellow", { 250, 218, 94 } },
|
||||
{ "Navajo White", { 255, 222, 173 } },
|
||||
{ "Navy Blue", { 0, 0, 128 } },
|
||||
{ "Neon Carrot", { 255, 163, 67 } },
|
||||
{ "Neon Fuchsia", { 254, 65, 100 } },
|
||||
{ "Neon Green", { 57, 255, 20 } },
|
||||
{ "New York Pink", { 215, 131, 127 } },
|
||||
{ "Non-Photo Blue", { 164, 221, 237 } },
|
||||
{ "North Texas Green", { 5, 144, 51 } },
|
||||
{ "Ocean Boat Blue", { 0, 119, 190 } },
|
||||
{ "Ochre", { 204, 119, 34 } },
|
||||
{ "Office Green", { 0, 128, 0 } },
|
||||
{ "Old Gold", { 207, 181, 59 } },
|
||||
{ "Old Lace", { 253, 245, 230 } },
|
||||
{ "Old Lavender", { 121, 104, 120 } },
|
||||
{ "Old Mauve", { 103, 49, 71 } },
|
||||
{ "Old Rose", { 192, 128, 129 } },
|
||||
{ "Olive", { 128, 128, 0 } },
|
||||
{ "Olivine", { 154, 185, 115 } },
|
||||
{ "Onyx", { 53, 56, 57 } },
|
||||
{ "Opera Mauve", { 183, 132, 167 } },
|
||||
{ "Orange", { 255, 127, 0 } },
|
||||
{ "Orchid", { 218, 112, 214 } },
|
||||
{ "Otter Brown", { 101, 67, 33 } },
|
||||
{ "Ou Crimson Red", { 153, 0, 0 } },
|
||||
{ "Outer Space", { 65, 74, 76 } },
|
||||
{ "Outrageous Orange", { 255, 110, 74 } },
|
||||
{ "Oxford Blue", { 0, 33, 71 } },
|
||||
{ "Pakistan Green", { 0, 102, 0 } },
|
||||
{ "Palatinate Blue", { 39, 59, 226 } },
|
||||
{ "Palatinate Purple", { 104, 40, 96 } },
|
||||
{ "Pale Aqua", { 188, 212, 230 } },
|
||||
{ "Pale Blue", { 175, 238, 238 } },
|
||||
{ "Pale Brown", { 152, 118, 84 } },
|
||||
{ "Pale Carmine", { 175, 64, 53 } },
|
||||
{ "Pale Cerulean", { 155, 196, 226 } },
|
||||
{ "Pale Chestnut", { 221, 173, 175 } },
|
||||
{ "Pale Copper", { 218, 138, 103 } },
|
||||
{ "Pale Cornflower Blue", { 171, 205, 239 } },
|
||||
{ "Pale Gold", { 230, 190, 138 } },
|
||||
{ "Pale Goldenrod", { 238, 232, 170 } },
|
||||
{ "Pale Green", { 152, 251, 152 } },
|
||||
{ "Pale Lavender", { 220, 208, 255 } },
|
||||
{ "Pale Magenta", { 249, 132, 229 } },
|
||||
{ "Pale Pink", { 250, 218, 221 } },
|
||||
{ "Pale Plum", { 221, 160, 221 } },
|
||||
{ "Pale Red-Violet", { 219, 112, 147 } },
|
||||
{ "Pale Robin Egg Blue", { 150, 222, 209 } },
|
||||
{ "Pale Silver", { 201, 192, 187 } },
|
||||
{ "Pale Spring Bud", { 236, 235, 189 } },
|
||||
{ "Pale Taupe", { 188, 152, 126 } },
|
||||
{ "Pale Violet-Red", { 219, 112, 147 } },
|
||||
{ "Pansy Purple", { 120, 24, 74 } },
|
||||
{ "Papaya Whip", { 255, 239, 213 } },
|
||||
{ "Paris Green", { 80, 200, 120 } },
|
||||
{ "Pastel Blue", { 174, 198, 207 } },
|
||||
{ "Pastel Brown", { 131, 105, 83 } },
|
||||
{ "Pastel Gray", { 207, 207, 196 } },
|
||||
{ "Pastel Green", { 119, 221, 119 } },
|
||||
{ "Pastel Magenta", { 244, 154, 194 } },
|
||||
{ "Pastel Orange", { 255, 179, 71 } },
|
||||
{ "Pastel Pink", { 222, 165, 164 } },
|
||||
{ "Pastel Purple", { 179, 158, 181 } },
|
||||
{ "Pastel Red", { 255, 105, 97 } },
|
||||
{ "Pastel Violet", { 203, 153, 201 } },
|
||||
{ "Pastel Yellow", { 253, 253, 150 } },
|
||||
{ "Patriarch", { 128, 0, 128 } },
|
||||
{ "Payne'S Grey", { 83, 104, 120 } },
|
||||
{ "Peach", { 255, 229, 180 } },
|
||||
{ "Peach-Orange", { 255, 204, 153 } },
|
||||
{ "Peach Puff", { 255, 218, 185 } },
|
||||
{ "Peach-Yellow", { 250, 223, 173 } },
|
||||
{ "Pear", { 209, 226, 49 } },
|
||||
{ "Pearl", { 234, 224, 200 } },
|
||||
{ "Pearl Aqua", { 136, 216, 192 } },
|
||||
{ "Pearly Purple", { 183, 104, 162 } },
|
||||
{ "Peridot", { 230, 226, 0 } },
|
||||
{ "Periwinkle", { 204, 204, 255 } },
|
||||
{ "Persian Blue", { 28, 57, 187 } },
|
||||
{ "Persian Green", { 0, 166, 147 } },
|
||||
{ "Persian Indigo", { 50, 18, 122 } },
|
||||
{ "Persian Orange", { 217, 144, 88 } },
|
||||
{ "Persian Pink", { 247, 127, 190 } },
|
||||
{ "Persian Plum", { 112, 28, 28 } },
|
||||
{ "Persian Red", { 204, 51, 51 } },
|
||||
{ "Persian Rose", { 254, 40, 162 } },
|
||||
{ "Persimmon", { 236, 88, 0 } },
|
||||
{ "Peru", { 205, 133, 63 } },
|
||||
{ "Phlox", { 223, 0, 255 } },
|
||||
{ "Phthalo Blue", { 0, 15, 137 } },
|
||||
{ "Phthalo Green", { 18, 53, 36 } },
|
||||
{ "Piggy Pink", { 253, 221, 230 } },
|
||||
{ "Pine Green", { 1, 121, 111 } },
|
||||
{ "Pink", { 255, 192, 203 } },
|
||||
{ "Pink Lace", { 255, 221, 244 } },
|
||||
{ "Pink-Orange", { 255, 153, 102 } },
|
||||
{ "Pink Pearl", { 231, 172, 207 } },
|
||||
{ "Pink Sherbet", { 247, 143, 167 } },
|
||||
{ "Pistachio", { 147, 197, 114 } },
|
||||
{ "Platinum", { 229, 228, 226 } },
|
||||
{ "Plum", { 142, 69, 133 } },
|
||||
{ "Portland Orange", { 255, 90, 54 } },
|
||||
{ "Powder Blue", { 176, 224, 230 } },
|
||||
{ "Princeton Orange", { 255, 143, 0 } },
|
||||
{ "Prune", { 112, 28, 28 } },
|
||||
{ "Prussian Blue", { 0, 49, 83 } },
|
||||
{ "Psychedelic Purple", { 223, 0, 255 } },
|
||||
{ "Puce", { 204, 136, 153 } },
|
||||
{ "Pumpkin", { 255, 117, 24 } },
|
||||
{ "Purple Heart", { 105, 53, 156 } },
|
||||
{ "Purple", { 128, 0, 128 } },
|
||||
{ "Purple Mountain Majesty", { 150, 120, 182 } },
|
||||
{ "Purple Pizzazz", { 254, 78, 218 } },
|
||||
{ "Purple Taupe", { 80, 64, 77 } },
|
||||
{ "Quartz", { 81, 72, 79 } },
|
||||
{ "Rackley", { 93, 138, 168 } },
|
||||
{ "Radical Red", { 255, 53, 94 } },
|
||||
{ "Rajah", { 251, 171, 96 } },
|
||||
{ "Raspberry", { 227, 11, 93 } },
|
||||
{ "Raspberry Glace", { 145, 95, 109 } },
|
||||
{ "Raspberry Pink", { 226, 80, 152 } },
|
||||
{ "Raspberry Rose", { 179, 68, 108 } },
|
||||
{ "Raw Umber", { 130, 102, 68 } },
|
||||
{ "Razzle Dazzle Rose", { 255, 51, 204 } },
|
||||
{ "Razzmatazz", { 227, 37, 107 } },
|
||||
{ "Red", { 255, 0, 0 } },
|
||||
{ "Red-Brown", { 165, 42, 42 } },
|
||||
{ "Red Devil", { 134, 1, 17 } },
|
||||
{ "Red-Orange", { 255, 83, 73 } },
|
||||
{ "Red-Violet", { 199, 21, 133 } },
|
||||
{ "Redwood", { 171, 78, 82 } },
|
||||
{ "Regalia", { 82, 45, 128 } },
|
||||
{ "Resolution Blue", { 0, 35, 135 } },
|
||||
{ "Rich Black", { 0, 64, 64 } },
|
||||
{ "Rich Brilliant Lavender", { 241, 167, 254 } },
|
||||
{ "Rich Carmine", { 215, 0, 64 } },
|
||||
{ "Rich Electric Blue", { 8, 146, 208 } },
|
||||
{ "Rich Lavender", { 167, 107, 207 } },
|
||||
{ "Rich Lilac", { 182, 102, 210 } },
|
||||
{ "Rich Maroon", { 176, 48, 96 } },
|
||||
{ "Rifle Green", { 65, 72, 51 } },
|
||||
{ "Robin Egg Blue", { 0, 204, 204 } },
|
||||
{ "Rose", { 255, 0, 127 } },
|
||||
{ "Rose Bonbon", { 249, 66, 158 } },
|
||||
{ "Rose Ebony", { 103, 72, 70 } },
|
||||
{ "Rose Gold", { 183, 110, 121 } },
|
||||
{ "Rose Madder", { 227, 38, 54 } },
|
||||
{ "Rose Pink", { 255, 102, 204 } },
|
||||
{ "Rose Quartz", { 170, 152, 169 } },
|
||||
{ "Rose Taupe", { 144, 93, 93 } },
|
||||
{ "Rose Vale", { 171, 78, 82 } },
|
||||
{ "Rosewood", { 101, 0, 11 } },
|
||||
{ "Rosso Corsa", { 212, 0, 0 } },
|
||||
{ "Rosy Brown", { 188, 143, 143 } },
|
||||
{ "Royal Azure", { 0, 56, 168 } },
|
||||
{ "Royal Blue", { 0, 35, 102 } },
|
||||
{ "Royal Fuchsia", { 202, 44, 146 } },
|
||||
{ "Royal Purple", { 120, 81, 169 } },
|
||||
{ "Royal Yellow", { 250, 218, 94 } },
|
||||
{ "Rubine Red", { 209, 0, 86 } },
|
||||
{ "Ruby", { 224, 17, 95 } },
|
||||
{ "Ruby Red", { 155, 17, 30 } },
|
||||
{ "Ruddy", { 255, 0, 40 } },
|
||||
{ "Ruddy Brown", { 187, 101, 40 } },
|
||||
{ "Ruddy Pink", { 225, 142, 150 } },
|
||||
{ "Rufous", { 168, 28, 7 } },
|
||||
{ "Russet", { 128, 70, 27 } },
|
||||
{ "Rust", { 183, 65, 14 } },
|
||||
{ "Rusty Red", { 218, 44, 67 } },
|
||||
{ "Sacramento State Green", { 0, 86, 63 } },
|
||||
{ "Saddle Brown", { 139, 69, 19 } },
|
||||
{ "Safety Orange", { 255, 103, 0 } },
|
||||
{ "Saffron", { 244, 196, 48 } },
|
||||
{ "Salmon", { 255, 140, 105 } },
|
||||
{ "Salmon Pink", { 255, 145, 164 } },
|
||||
{ "Sand", { 194, 178, 128 } },
|
||||
{ "Sand Dune", { 150, 113, 23 } },
|
||||
{ "Sandstorm", { 236, 213, 64 } },
|
||||
{ "Sandy Brown", { 244, 164, 96 } },
|
||||
{ "Sandy Taupe", { 150, 113, 23 } },
|
||||
{ "Sangria", { 146, 0, 10 } },
|
||||
{ "Sap Green", { 80, 125, 42 } },
|
||||
{ "Sapphire", { 15, 82, 186 } },
|
||||
{ "Sapphire Blue", { 0, 103, 165 } },
|
||||
{ "Satin Sheen Gold", { 203, 161, 53 } },
|
||||
{ "Scarlet", { 255, 36, 0 } },
|
||||
{ "School Bus Yellow", { 255, 216, 0 } },
|
||||
{ "Screamin' Green", { 118, 255, 122 } },
|
||||
{ "Sea Blue", { 0, 105, 148 } },
|
||||
{ "Sea Green", { 46, 139, 87 } },
|
||||
{ "Seal Brown", { 50, 20, 20 } },
|
||||
{ "Seashell", { 255, 245, 238 } },
|
||||
{ "Selective Yellow", { 255, 186, 0 } },
|
||||
{ "Sepia", { 112, 66, 20 } },
|
||||
{ "Shadow", { 138, 121, 93 } },
|
||||
{ "Shamrock Green", { 0, 158, 96 } },
|
||||
{ "Shocking Pink", { 252, 15, 192 } },
|
||||
{ "Sienna", { 136, 45, 23 } },
|
||||
{ "Silver", { 192, 192, 192 } },
|
||||
{ "Sinopia", { 203, 65, 11 } },
|
||||
{ "Skobeloff", { 0, 116, 116 } },
|
||||
{ "Sky Blue", { 135, 206, 235 } },
|
||||
{ "Sky Magenta", { 207, 113, 175 } },
|
||||
{ "Slate Blue", { 106, 90, 205 } },
|
||||
{ "Slate Gray", { 112, 128, 144 } },
|
||||
{ "Smokey Topaz", { 147, 61, 65 } },
|
||||
{ "Smoky Black", { 16, 12, 8 } },
|
||||
{ "Snow", { 255, 250, 250 } },
|
||||
{ "Spiro Disco Ball", { 15, 192, 252 } },
|
||||
{ "Spring Bud", { 167, 252, 0 } },
|
||||
{ "Spring Green", { 0, 255, 127 } },
|
||||
{ "St. Patrick'S Blue", { 35, 41, 122 } },
|
||||
{ "Steel Blue", { 70, 130, 180 } },
|
||||
{ "Stil De Grain Yellow", { 250, 218, 94 } },
|
||||
{ "Stizza", { 153, 0, 0 } },
|
||||
{ "Stormcloud", { 79, 102, 106 } },
|
||||
{ "Straw", { 228, 217, 111 } },
|
||||
{ "Sunglow", { 255, 204, 51 } },
|
||||
{ "Sunset", { 250, 214, 165 } },
|
||||
{ "Tan", { 210, 180, 140 } },
|
||||
{ "Tangelo", { 249, 77, 0 } },
|
||||
{ "Tangerine", { 242, 133, 0 } },
|
||||
{ "Tangerine Yellow", { 255, 204, 0 } },
|
||||
{ "Tango Pink", { 228, 113, 122 } },
|
||||
{ "Taupe", { 72, 60, 50 } },
|
||||
{ "Taupe Gray", { 139, 133, 137 } },
|
||||
{ "Tea Green", { 208, 240, 192 } },
|
||||
{ "Teal", { 0, 128, 128 } },
|
||||
{ "Teal Blue", { 54, 117, 136 } },
|
||||
{ "Teal Green", { 0, 130, 127 } },
|
||||
{ "Telemagenta", { 207, 52, 118 } },
|
||||
{ "Terra Cotta", { 226, 114, 91 } },
|
||||
{ "Thistle", { 216, 191, 216 } },
|
||||
{ "Thulian Pink", { 222, 111, 161 } },
|
||||
{ "Tickle Me Pink", { 252, 137, 172 } },
|
||||
{ "Tiffany Blue", { 10, 186, 181 } },
|
||||
{ "Tiger'S Eye", { 224, 141, 60 } },
|
||||
{ "Timberwolf", { 219, 215, 210 } },
|
||||
{ "Titanium Yellow", { 238, 230, 0 } },
|
||||
{ "Tomato", { 255, 99, 71 } },
|
||||
{ "Toolbox", { 116, 108, 192 } },
|
||||
{ "Topaz", { 255, 200, 124 } },
|
||||
{ "Tractor Red", { 253, 14, 53 } },
|
||||
{ "Trolley Grey", { 128, 128, 128 } },
|
||||
{ "Tropical Rain Forest", { 0, 117, 94 } },
|
||||
{ "True Blue", { 0, 115, 207 } },
|
||||
{ "Tufts Blue", { 65, 125, 193 } },
|
||||
{ "Tumbleweed", { 222, 170, 136 } },
|
||||
{ "Turkish Rose", { 181, 114, 129 } },
|
||||
{ "Turquoise", { 48, 213, 200 } },
|
||||
{ "Turquoise Blue", { 0, 255, 239 } },
|
||||
{ "Turquoise Green", { 160, 214, 180 } },
|
||||
{ "Tuscan Red", { 124, 72, 72 } },
|
||||
{ "Twilight Lavender", { 138, 73, 107 } },
|
||||
{ "Tyrian Purple", { 102, 2, 60 } },
|
||||
{ "Ua Blue", { 0, 51, 170 } },
|
||||
{ "Ua Red", { 217, 0, 76 } },
|
||||
{ "Ube", { 136, 120, 195 } },
|
||||
{ "Ucla Blue", { 83, 104, 149 } },
|
||||
{ "Ucla Gold", { 255, 179, 0 } },
|
||||
{ "Ufo Green", { 60, 208, 112 } },
|
||||
{ "Ultra Pink", { 255, 111, 255 } },
|
||||
{ "Ultramarine", { 18, 10, 143 } },
|
||||
{ "Ultramarine Blue", { 65, 102, 245 } },
|
||||
{ "Umber", { 99, 81, 71 } },
|
||||
{ "Unbleached Silk", { 255, 221, 202 } },
|
||||
{ "United Nations Blue", { 91, 146, 229 } },
|
||||
{ "University Of California Gold", { 183, 135, 39 } },
|
||||
{ "Unmellow Yellow", { 255, 255, 102 } },
|
||||
{ "Up Forest Green", { 1, 68, 33 } },
|
||||
{ "Up Maroon", { 123, 17, 19 } },
|
||||
{ "Upsdell Red", { 174, 32, 41 } },
|
||||
{ "Urobilin", { 225, 173, 33 } },
|
||||
{ "Usafa Blue", { 0, 79, 152 } },
|
||||
{ "Usc Cardinal", { 153, 0, 0 } },
|
||||
{ "Usc Gold", { 255, 204, 0 } },
|
||||
{ "Utah Crimson", { 211, 0, 63 } },
|
||||
{ "Vanilla", { 243, 229, 171 } },
|
||||
{ "Vegas Gold", { 197, 179, 88 } },
|
||||
{ "Venetian Red", { 200, 8, 21 } },
|
||||
{ "Verdigris", { 67, 179, 174 } },
|
||||
{ "Vermilion", { 227, 66, 52 } },
|
||||
{ "Veronica", { 160, 32, 240 } },
|
||||
{ "Violet", { 143, 0, 255 } },
|
||||
{ "Violet-Blue", { 50, 74, 178 } },
|
||||
{ "Violet (Color Wheel)", { 127, 0, 255 } },
|
||||
{ "Viridian", { 64, 130, 109 } },
|
||||
{ "Vivid Auburn", { 146, 39, 36 } },
|
||||
{ "Vivid Burgundy", { 159, 29, 53 } },
|
||||
{ "Vivid Cerise", { 218, 29, 129 } },
|
||||
{ "Vivid Tangerine", { 255, 160, 137 } },
|
||||
{ "Vivid Violet", { 159, 0, 255 } },
|
||||
{ "Warm Black", { 0, 66, 66 } },
|
||||
{ "Waterspout", { 164, 244, 249 } },
|
||||
{ "Wenge", { 100, 84, 82 } },
|
||||
{ "Wheat", { 245, 222, 179 } },
|
||||
{ "White", { 255, 255, 255 } },
|
||||
{ "White Smoke", { 245, 245, 245 } },
|
||||
{ "Wild Blue Yonder", { 162, 173, 208 } },
|
||||
{ "Wild Strawberry", { 255, 67, 164 } },
|
||||
{ "Wild Watermelon", { 252, 108, 133 } },
|
||||
{ "Wine", { 114, 47, 55 } },
|
||||
{ "Wine Dregs", { 103, 49, 71 } },
|
||||
{ "Wisteria", { 201, 160, 220 } },
|
||||
{ "Wood Brown", { 193, 154, 107 } },
|
||||
{ "Xanadu", { 115, 134, 120 } },
|
||||
{ "Yale Blue", { 15, 77, 146 } },
|
||||
{ "Yellow", { 255, 255, 0 } },
|
||||
{ "Yellow-Green", { 154, 205, 50 } },
|
||||
{ "Yellow Orange", { 255, 174, 66 } },
|
||||
{ "Zaffre", { 0, 20, 168 } },
|
||||
{ "Zinnwaldite Brown", { 44, 22, 8 } },
|
||||
{0, {0, 0, 0}},
|
||||
};
|
||||
|
||||
ColorInfo colorForName(const char *name) {
|
||||
String needleName(name);
|
||||
needleName.toLowerCase();
|
||||
for (int i = 0; color_data[i].name != 0; i++) {
|
||||
String colorName(color_data[i].name);
|
||||
colorName.toLowerCase();
|
||||
if (colorName == needleName) {
|
||||
return color_data[i];
|
||||
}
|
||||
}
|
||||
return ColorInfo();
|
||||
}
|
||||
|
||||
const ColorInfo* allColors() {
|
||||
return color_data;
|
||||
}
|
10
src/colors.h
Normal file
10
src/colors.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <FastLED.h>
|
||||
|
||||
typedef struct ColorInfo {
|
||||
const char *name;
|
||||
CRGB rgb;
|
||||
} ColorInfo;
|
||||
|
||||
ColorInfo colorForName(const char *name);
|
||||
const ColorInfo* allColors();
|
37
src/inputs/Buttons.cpp
Normal file
37
src/inputs/Buttons.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "./Buttons.h"
|
||||
|
||||
void
|
||||
Buttons::onStart()
|
||||
{
|
||||
for(int i = 0; i < 3; i++) {
|
||||
//Log.info("Bound pin %d to button %d", 2 + i, i);
|
||||
m_buttons[i].attach(2 + i, INPUT_PULLDOWN);
|
||||
m_buttons[i].interval(15);
|
||||
}
|
||||
}
|
||||
|
||||
InputEvent
|
||||
Buttons::read()
|
||||
{
|
||||
for(int i = 0; i < 3; i++) {
|
||||
m_buttons[i].update();
|
||||
if (m_buttons[i].rose()) {
|
||||
//Log.info("Read press on %d", i);
|
||||
int buttonID = m_buttonMap[i];
|
||||
for(int j = 0; j < 3; j++ ) {
|
||||
if (j != i && m_buttons[j].held()) {
|
||||
buttonID |= m_buttonMap[j];
|
||||
//Log.info("Chord with %d", j);
|
||||
m_wasChord[j] = true;
|
||||
}
|
||||
}
|
||||
if (m_wasChord[i]) {
|
||||
//Log.info("Not emitting release from previous chord");
|
||||
m_wasChord[i] = false;
|
||||
} else {
|
||||
return InputEvent{InputEvent::UserInput, buttonID};
|
||||
}
|
||||
}
|
||||
}
|
||||
return InputEvent{};
|
||||
}
|
94
src/inputs/Buttons.h
Normal file
94
src/inputs/Buttons.h
Normal file
@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include <Input.h>
|
||||
|
||||
class Bounce {
|
||||
public:
|
||||
void attach(int pin, int buttonPinMode) {
|
||||
m_pin = pin;
|
||||
pinMode(m_pin, buttonPinMode);
|
||||
//Log.info("Attaching a button to %d", pin);
|
||||
}
|
||||
|
||||
void update() {
|
||||
int readResult = digitalRead(m_pin);
|
||||
if (m_state == Ready) {
|
||||
if (readResult == HIGH) {
|
||||
m_state = Started;
|
||||
m_downStart = millis();
|
||||
//Log.info("Button %d is started!", m_pin);
|
||||
}
|
||||
} else if (m_state == Started && millis() - m_downStart >= m_interval) {
|
||||
if (readResult == HIGH) {
|
||||
m_state = Confirmed;
|
||||
//Log.info("Button %d is CONFIRMED!", m_pin);
|
||||
} else {
|
||||
m_state = Ready;
|
||||
//Log.info("Button %d bounced back to ready!", m_pin);
|
||||
}
|
||||
} else if (m_state == Confirmed || m_state == Held) {
|
||||
if (readResult == LOW) {
|
||||
//Log.info("Button %d is released", m_pin);
|
||||
m_state = Released;
|
||||
} else if (m_state == Confirmed) {
|
||||
m_state = Held;
|
||||
//Log.info("Button %d is being held down!", m_pin);
|
||||
}
|
||||
} else if (m_state == Released) {
|
||||
//Log.info("Button %d is ready!", m_pin);
|
||||
m_state = Ready;
|
||||
}
|
||||
}
|
||||
|
||||
void interval(uint8_t v) {
|
||||
m_interval = v;
|
||||
}
|
||||
|
||||
bool fell() const {
|
||||
return m_state == Confirmed;
|
||||
}
|
||||
|
||||
bool rose() const {
|
||||
return m_state == Released;
|
||||
}
|
||||
|
||||
bool held() const {
|
||||
return m_state == Held;
|
||||
}
|
||||
|
||||
private:
|
||||
enum State {
|
||||
Ready,
|
||||
Started,
|
||||
Confirmed,
|
||||
Held,
|
||||
Released
|
||||
};
|
||||
|
||||
State m_state = Ready;
|
||||
unsigned int m_pin = 0;
|
||||
unsigned int m_downStart = 0;
|
||||
unsigned int m_interval = 10;
|
||||
};
|
||||
|
||||
class Buttons: public InputSource {
|
||||
public:
|
||||
Buttons() : InputSource("Buttons") {}
|
||||
void onStart() override;
|
||||
InputEvent read() override;
|
||||
|
||||
enum Chord {
|
||||
None = 0,
|
||||
Circle = 1,
|
||||
Triangle = 2,
|
||||
Cross = 4,
|
||||
CircleTriangle = Circle | Triangle,
|
||||
CircleCross = Circle | Cross,
|
||||
TriangleCross = Triangle | Cross,
|
||||
CircleTriangleCross = Circle | Triangle | Cross
|
||||
};
|
||||
|
||||
private:
|
||||
Bounce m_buttons[3];
|
||||
Chord m_buttonMap[3] = {Circle, Triangle, Cross};
|
||||
bool m_wasChord[3] = {false, false, false};
|
||||
};
|
0
src/inputs/ColorCycle.cpp
Normal file
0
src/inputs/ColorCycle.cpp
Normal file
32
src/inputs/ColorCycle.h
Normal file
32
src/inputs/ColorCycle.h
Normal file
@ -0,0 +1,32 @@
|
||||
#include <Figments.h>
|
||||
#include <ArduinoLog.h>
|
||||
|
||||
template<int Period>
|
||||
class ColorSequenceInput: public InputSource {
|
||||
public:
|
||||
ColorSequenceInput(const std::vector<CRGB> &colors, const char* name, Task::State initialState)
|
||||
: InputSource(name, initialState), m_colors(colors) {}
|
||||
|
||||
InputEvent read() override {
|
||||
EVERY_N_SECONDS(Period) {
|
||||
m_idx %= m_colors.size();
|
||||
m_reset = true;
|
||||
m_idx++;
|
||||
}
|
||||
if (m_reset) {
|
||||
m_reset = false;
|
||||
Log.notice("Cycling %s color to %d [%d, %d, %d]", name, m_idx, m_colors[m_idx].r, m_colors[m_idx].g, m_colors[m_idx].b);
|
||||
return InputEvent{InputEvent::SetColor, m_colors[m_idx]};
|
||||
}
|
||||
return InputEvent{};
|
||||
}
|
||||
|
||||
void onStart() override {
|
||||
m_reset = true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<CRGB> m_colors;
|
||||
unsigned int m_idx = 0;
|
||||
bool m_reset = true;
|
||||
};
|
79
src/inputs/DMX.cpp
Normal file
79
src/inputs/DMX.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
#if 0
|
||||
#include "Particle.h"
|
||||
#include "../Input.h"
|
||||
#include "../colors.h"
|
||||
#include "../Static.h"
|
||||
|
||||
volatile uint8_t DmxRxField[8];
|
||||
volatile uint16_t DmxAddress;
|
||||
enum { IDLE, BREAK, STARTB, STARTADR };
|
||||
|
||||
volatile uint8_t gDmxState;
|
||||
|
||||
class DMXInput: public InputSource {
|
||||
public:
|
||||
void init() {
|
||||
UCSR1B = (1<<RXEN1) | (1<<RXCE1);
|
||||
Serial1.begin(250000, SERIAL_8N2);
|
||||
}
|
||||
|
||||
static void handleInterrupt() {
|
||||
static uint16_t DmxCount;
|
||||
uint8_t USARTstate = UCSR1A;
|
||||
uint8_t DmxByte = UDR1;
|
||||
uint8_t DmxState = gDmxState;
|
||||
|
||||
if (USARTstate &(1<<FE1)) {
|
||||
DmxCount = DmxAddress;
|
||||
gDmxState = BREAK;
|
||||
} else if (DmxState == BREAK) {
|
||||
if (DmxByte == 0) {
|
||||
gDmxState = STARTB;
|
||||
} else {
|
||||
gDmxState = IDLE;
|
||||
}
|
||||
} else if (DmxState == STARTB) {
|
||||
if (--DmxCount == 0) {
|
||||
DmxCount = 1;
|
||||
DmxRxField[0] = DmxByte;
|
||||
gDmxState = STARTADR;
|
||||
}
|
||||
} else if (DmxState == STARTADR) {
|
||||
DmxRxField[DmxCount++] = DmxByte;
|
||||
if (DmxCount >= sizeof(DmxRxField)) {
|
||||
gDmxState = IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void update() override {
|
||||
bool hasUpdate = false;
|
||||
if (gDmxState == IDLE) {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
if (m_lastReadFields[i] != DmxRxField[i]) {
|
||||
hasUpdate = true;
|
||||
m_lastReadFields[i] = DmxRxField[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasUpdate) {
|
||||
m_lastReadEvent = InputEvent(InputEvent::SetColor, {m_lastReadFields[0], m_lastReadFields[1], m_lastReadFields[2]});
|
||||
}
|
||||
m_lastEvent = m_lastReadEvent;
|
||||
m_lastReadEvent = InputEvent();
|
||||
}
|
||||
|
||||
virtual InputEvent read() const override {
|
||||
return m_lastEvent;
|
||||
}
|
||||
|
||||
private:
|
||||
InputEvent m_lastEvent;
|
||||
InputEvent m_lastReadEvent;
|
||||
uint8_t m_lastReadFields[8];
|
||||
};
|
||||
|
||||
ISR(USART1_RX_vect) {
|
||||
DMXInput::handleInterrupt();
|
||||
}
|
||||
#endif
|
0
src/inputs/MPU6050.cpp
Normal file
0
src/inputs/MPU6050.cpp
Normal file
97
src/inputs/MPU6050.h
Normal file
97
src/inputs/MPU6050.h
Normal file
@ -0,0 +1,97 @@
|
||||
#include <Wire.h>
|
||||
|
||||
class MPU5060: public InputSource {
|
||||
const int ACCEL_XOUT_HIGH = 0x3B;
|
||||
const int ACCEL_XOUT_LOW = 0x3C;
|
||||
const int ACCEL_YOUT_HIGH = 0x3D;
|
||||
const int ACCEL_YOUT_LOW = 0x3E;
|
||||
const int ACCEL_ZOUT_HIGH = 0x3F;
|
||||
const int ACCEL_ZOUT_LOW = 0x40;
|
||||
|
||||
const int I2C_ADDRESS = 0x68;
|
||||
|
||||
const int PWR_MGMT_1 = 0x6B;
|
||||
const int CONFIG_REG = 0x1A;
|
||||
const int ACCEL_CONFIG_REG = 0x1C;
|
||||
|
||||
public:
|
||||
MPU5060() : InputSource("MPU5060", HardwareConfig::HAS_MPU_6050 ? Task::Running : Task::Stopped) {}
|
||||
void onStart() override {
|
||||
Wire.begin();
|
||||
|
||||
// Turn on the sensor
|
||||
Wire.beginTransmission(I2C_ADDRESS);
|
||||
Wire.write(PWR_MGMT_1);
|
||||
Wire.write(0);
|
||||
Wire.endTransmission(true);
|
||||
|
||||
// Configure the filter
|
||||
Wire.beginTransmission(I2C_ADDRESS);
|
||||
Wire.write(CONFIG_REG);
|
||||
Wire.write(3);
|
||||
Wire.endTransmission(true);
|
||||
|
||||
// Configure the accel range
|
||||
Wire.beginTransmission(I2C_ADDRESS);
|
||||
Wire.write(ACCEL_CONFIG_REG);
|
||||
// 4G
|
||||
Wire.write(2 << 3);
|
||||
Wire.endTransmission(true);
|
||||
}
|
||||
|
||||
void onStop() override {
|
||||
Wire.beginTransmission(I2C_ADDRESS);
|
||||
// Turn off the sensor
|
||||
Wire.write(PWR_MGMT_1);
|
||||
Wire.write(1);
|
||||
Wire.endTransmission(true);
|
||||
//Wire.end();
|
||||
}
|
||||
|
||||
InputEvent read() override {
|
||||
EVERY_N_MILLISECONDS(5) {
|
||||
Wire.beginTransmission(I2C_ADDRESS);
|
||||
Wire.write(ACCEL_XOUT_HIGH);
|
||||
Wire.endTransmission(false);
|
||||
Wire.requestFrom(I2C_ADDRESS, 6);
|
||||
const int16_t accelX = Wire.read() << 8 | Wire.read();
|
||||
const int16_t accelY = Wire.read() << 8 | Wire.read();
|
||||
const int16_t accelZ = Wire.read() << 8 | Wire.read();
|
||||
const uint16_t accelSum = abs(accelX) + abs(accelY) + abs(accelZ);
|
||||
const uint16_t delta = abs(m_value.value() - accelSum);
|
||||
m_value.add(accelSum);
|
||||
if (delta > 32) {
|
||||
return InputEvent{InputEvent::Acceleration, delta};
|
||||
}
|
||||
}
|
||||
return InputEvent{};
|
||||
}
|
||||
|
||||
template<typename T, uint8_t Size = 8>
|
||||
struct Averager {
|
||||
std::array<T, Size> buf;
|
||||
unsigned int idx = 0;
|
||||
unsigned int count = 0;
|
||||
|
||||
void add(const T &value) {
|
||||
buf[idx] = value;
|
||||
idx = (idx + 1) % Size;
|
||||
if (count < Size) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
T value() const {
|
||||
if (count == 0) {
|
||||
return T{};
|
||||
}
|
||||
long long int sum = 0;
|
||||
for(unsigned int i = 0; i < count; i++) {
|
||||
sum += buf[i];
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
};
|
||||
|
||||
Averager<int16_t, 32> m_value;
|
||||
};
|
12
src/inputs/Serial.h
Normal file
12
src/inputs/Serial.h
Normal file
@ -0,0 +1,12 @@
|
||||
#include "Particle.h"
|
||||
#include "../Figments/Figments.h"
|
||||
|
||||
class SerialInput: public InputSource {
|
||||
public:
|
||||
void onAttach() override {
|
||||
//Serial.begin();
|
||||
}
|
||||
|
||||
InputEvent read() {
|
||||
}
|
||||
}
|
558
src/main.cpp
Normal file
558
src/main.cpp
Normal file
@ -0,0 +1,558 @@
|
||||
#define RENDERBUG_VERSION 1
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#include <FastLED.h>
|
||||
#include <Figments.h>
|
||||
|
||||
#ifndef PLATFORM_PHOTON
|
||||
#include <ArduinoLog.h>
|
||||
#include <NTP.h>
|
||||
#endif
|
||||
|
||||
#include "BootOptions.h"
|
||||
|
||||
#include "Static.h"
|
||||
#include "Config.h"
|
||||
#include "colors.h"
|
||||
|
||||
#include "Sequencer.h"
|
||||
|
||||
#include "animations/Power.cpp"
|
||||
#include "animations/SolidAnimation.cpp"
|
||||
#include "animations/Chimes.cpp"
|
||||
#include "animations/Flashlight.cpp"
|
||||
#include "animations/Drain.cpp"
|
||||
#include "animations/UpdateStatus.h"
|
||||
|
||||
#include "inputs/ColorCycle.h"
|
||||
#include "inputs/Buttons.h"
|
||||
#include "inputs/MPU6050.h"
|
||||
|
||||
#ifdef PLATFORM_PHOTON
|
||||
#include "platform/particle/inputs/Photon.h"
|
||||
#include "platform/particle/inputs/CloudStatus.h"
|
||||
#include "platform/particle/PhotonTelemetry.h"
|
||||
#include "platform/particle/WebTelemetry.cpp"
|
||||
#include "platform/particle/MDNSService.cpp"
|
||||
#else
|
||||
#include "WiFiTask.h"
|
||||
#include "platform/arduino/MQTTTelemetry.h"
|
||||
#include <ArduinoOTA.h>
|
||||
#endif
|
||||
|
||||
//SerialLogHandler logHandler;
|
||||
|
||||
#define MAX_BRIGHTNESS 255
|
||||
//#define PSU_MILLIAMPS 4800
|
||||
//#define PSU_MILLIAMPS 500
|
||||
//#define PSU_MILLIAMPS 1000
|
||||
#define PSU_MILLIAMPS 1000
|
||||
|
||||
// Enable system thread, so rendering happens while booting
|
||||
//SYSTEM_THREAD(ENABLED);
|
||||
|
||||
// Setup FastLED and the display
|
||||
CRGB leds[HardwareConfig::MAX_LED_NUM];
|
||||
Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()->coordMap());
|
||||
|
||||
LinearCoordinateMapping neckMap{60, 0};
|
||||
Display neckDisplay(leds, HardwareConfig::MAX_LED_NUM, &neckMap);
|
||||
|
||||
// Setup power management
|
||||
Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power;
|
||||
|
||||
// Clip the display at whatever is configured while still showing over-paints
|
||||
FigmentFunc displayClip([](Display* dpy) {
|
||||
auto coords = Static<ConfigService>::instance()->coordMap();
|
||||
for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) {
|
||||
if (i < coords->startPixel || i > coords->pixelCount + coords->startPixel) {
|
||||
dpy->pixelAt(i) %= 40;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FigmentFunc configDisplay([](Display* dpy) {
|
||||
uint8_t brightness = brighten8_video(beatsin8(60));
|
||||
auto coords = Static<ConfigService>::instance()->coordMap();
|
||||
for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) {
|
||||
if (i < coords->startPixel || i > coords->startPixel + coords->pixelCount) {
|
||||
dpy->pixelAt(i) += CRGB(brightness, 0, 0);
|
||||
} else {
|
||||
dpy->pixelAt(i) += CRGB(255 - brightness, 255 - brightness, 255 - brightness);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class InputBlip: public Figment {
|
||||
public:
|
||||
InputBlip() : Figment("InputBlip", Task::Stopped) {}
|
||||
|
||||
void handleEvent(const InputEvent& evt) override {
|
||||
if (evt.intent != InputEvent::None) {
|
||||
m_time = qadd8(m_time, 5);
|
||||
}
|
||||
}
|
||||
void loop() override {
|
||||
if (m_time > 0) {
|
||||
m_time--;
|
||||
}
|
||||
}
|
||||
void render(Display* dpy) const override {
|
||||
if (m_time > 0) {
|
||||
dpy->pixelAt(0) = CRGB(0, brighten8_video(ease8InOutApprox(m_time)), 0);
|
||||
}
|
||||
}
|
||||
private:
|
||||
uint8_t m_time = 0;
|
||||
};
|
||||
|
||||
InputBlip inputBlip;
|
||||
|
||||
class ArduinoOTAUpdater : public BufferedInputSource {
|
||||
public:
|
||||
ArduinoOTAUpdater() : BufferedInputSource("ArduinoOTA") {
|
||||
ArduinoOTA.onStart(&ArduinoOTAUpdater::s_onStart).onProgress(&ArduinoOTAUpdater::s_onProgress);
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
ArduinoOTA.handle();
|
||||
BufferedInputSource::loop();
|
||||
}
|
||||
private:
|
||||
static void s_onStart() {
|
||||
Static<ArduinoOTAUpdater>::instance()->setEvent(InputEvent::FirmwareUpdate);
|
||||
}
|
||||
|
||||
static void s_onProgress(unsigned int progress, unsigned int total) {
|
||||
Static<ArduinoOTAUpdater>::instance()->setEvent(InputEvent{InputEvent::FirmwareUpdate, progress});
|
||||
}
|
||||
};
|
||||
|
||||
InputFunc randomPulse([]() {
|
||||
static unsigned int pulse = 0;
|
||||
EVERY_N_MILLISECONDS(25) {
|
||||
if (pulse == 0) {
|
||||
pulse = random(25) + 25;
|
||||
}
|
||||
}
|
||||
if (pulse > 0) {
|
||||
if (random(255) >= 25) {
|
||||
pulse--;
|
||||
return InputEvent{InputEvent::Acceleration, beatsin16(60 * 8, 0, 4972)};
|
||||
}
|
||||
}
|
||||
return InputEvent{};
|
||||
}, "Pulse", Task::Running);
|
||||
|
||||
InputMapper keyMap([](const InputEvent& evt) {
|
||||
if (evt.intent == InputEvent::UserInput) {
|
||||
Buttons::Chord chord = (Buttons::Chord)evt.asInt();
|
||||
switch(chord) {
|
||||
case Buttons::Circle:
|
||||
return InputEvent::PowerToggle;
|
||||
break;
|
||||
case Buttons::Triangle:
|
||||
return InputEvent::NextPattern;
|
||||
break;
|
||||
case Buttons::Cross:
|
||||
return InputEvent::UserInput;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return InputEvent::None;
|
||||
}, "Keymap");
|
||||
|
||||
ChimesAnimation chimes{Task::Stopped};
|
||||
SolidAnimation solid{Task::Running};
|
||||
DrainAnimation drain{Task::Stopped};
|
||||
Flashlight flashlight{Task::Stopped};
|
||||
|
||||
Sequencer sequencer{{
|
||||
{"Idle", {"Solid", "MPU5060", "Pulse", "Hackerbots", "Kieryn", "CircadianRhythm"}},
|
||||
{"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}},
|
||||
{"Interactive", {"Drain", "CircadianRhythm"}},
|
||||
{"Flashlight", {"Flashlight"}},
|
||||
{"Nightlight", {"Drain", "Pulse", "Noisebridge"}},
|
||||
{"Gay", {"Solid", "Pulse", "Rainbow", "Hackerbots", "Kieryn"}},
|
||||
{"Acid", {"Chimes", "Pulse", "MPU5060", "Hackerbots", "Rainbow"}},
|
||||
}};
|
||||
|
||||
|
||||
// Render all layers to the displays
|
||||
Renderer renderer{
|
||||
//{&dpy, &neckDisplay},
|
||||
{&dpy},
|
||||
{
|
||||
&chimes,
|
||||
&drain,
|
||||
&solid,
|
||||
&flashlight,
|
||||
Static<UpdateStatus>::instance(),
|
||||
&displayClip,
|
||||
&inputBlip,
|
||||
&power,
|
||||
}
|
||||
};
|
||||
|
||||
Renderer configRenderer{
|
||||
{&dpy},
|
||||
{&drain, &configDisplay, &inputBlip, &power}
|
||||
};
|
||||
|
||||
// Cycle some random colors
|
||||
ColorSequenceInput<7> noisebridgeCycle{{colorForName("Red").rgb}, "Noisebridge", Task::Stopped};
|
||||
|
||||
ColorSequenceInput<13> kierynCycle{{
|
||||
CRGB(0, 123, 167), // Cerulean
|
||||
CRGB(80, 200, 120), // Emerald
|
||||
CRGB(207, 113, 175), // Sky Magenta
|
||||
}, "Kieryn", Task::Running};
|
||||
|
||||
ColorSequenceInput<7> rainbowCycle{{
|
||||
colorForName("Red").rgb,
|
||||
colorForName("Orange").rgb,
|
||||
colorForName("Yellow").rgb,
|
||||
colorForName("Green").rgb,
|
||||
colorForName("Blue").rgb,
|
||||
colorForName("Purple").rgb,
|
||||
colorForName("White").rgb,
|
||||
}, "Rainbow", Task::Stopped};
|
||||
|
||||
ColorSequenceInput<7> hackerbotsCycle{{
|
||||
CRGB(128, 0, 128), // Purple
|
||||
CRGB(255, 255, 255), // White
|
||||
CRGB(0, 255, 255), // Cyan
|
||||
}, "Hackerbots", Task::Running};
|
||||
|
||||
struct ConfigInputTask: public BufferedInputSource {
|
||||
public:
|
||||
ConfigInputTask() : BufferedInputSource("ConfigInput") {}
|
||||
|
||||
void handleEvent(const InputEvent& evt) override {
|
||||
if (evt.intent == InputEvent::UserInput) {
|
||||
Buttons::Chord chord = (Buttons::Chord) evt.asInt();
|
||||
switch (chord) {
|
||||
case Buttons::Circle:
|
||||
m_currentIntent = nextIntent();
|
||||
//Log.info("Next setting... (%d)", m_currentIntent);
|
||||
break;
|
||||
case Buttons::CircleTriangle:
|
||||
//Log.info("Increment...");
|
||||
increment();
|
||||
break;
|
||||
case Buttons::CircleCross:
|
||||
//Log.info("Decrement...");
|
||||
decrement();
|
||||
break;
|
||||
case Buttons::Triangle:
|
||||
//Log.info("Save...");
|
||||
setEvent(InputEvent::SaveConfigurationRequest);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
InputEvent::Intent m_currentIntent = InputEvent::SetDisplayLength;
|
||||
|
||||
void decrement() {
|
||||
int current = 0;
|
||||
switch (m_currentIntent) {
|
||||
case InputEvent::SetDisplayLength:
|
||||
current = Static<ConfigService>::instance()->coordMap()->pixelCount;
|
||||
break;
|
||||
case InputEvent::SetDisplayOffset:
|
||||
current = Static<ConfigService>::instance()->coordMap()->startPixel;
|
||||
break;
|
||||
}
|
||||
setEvent(InputEvent{m_currentIntent, current - 1});
|
||||
}
|
||||
|
||||
void increment() {
|
||||
int current = 0;
|
||||
switch (m_currentIntent) {
|
||||
case InputEvent::SetDisplayLength:
|
||||
current = Static<ConfigService>::instance()->coordMap()->pixelCount;
|
||||
break;
|
||||
case InputEvent::SetDisplayOffset:
|
||||
current = Static<ConfigService>::instance()->coordMap()->startPixel;
|
||||
break;
|
||||
}
|
||||
setEvent(InputEvent{m_currentIntent, current + 1});
|
||||
}
|
||||
|
||||
InputEvent::Intent nextIntent() {
|
||||
switch (m_currentIntent) {
|
||||
case InputEvent::SetDisplayLength:
|
||||
return InputEvent::SetDisplayOffset;
|
||||
case InputEvent::SetDisplayOffset:
|
||||
return InputEvent::SetDisplayLength;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum Phase {
|
||||
Null,
|
||||
AstronomicalDay,
|
||||
NauticalDay,
|
||||
CivilDay,
|
||||
CivilNight,
|
||||
NauticalNight,
|
||||
AstronomicalNight,
|
||||
Evening,
|
||||
Bedtime,
|
||||
};
|
||||
|
||||
struct ScheduleEntry {
|
||||
uint8_t hour;
|
||||
uint8_t brightness;
|
||||
};
|
||||
|
||||
std::array<ScheduleEntry, 10> schedule{{
|
||||
{0, 0},
|
||||
{5, 0},
|
||||
{6, 0},
|
||||
{7, 10},
|
||||
{8, 80},
|
||||
{11, 120},
|
||||
{18, 200},
|
||||
{19, 255},
|
||||
{22, 120},
|
||||
{23, 20}
|
||||
}};
|
||||
|
||||
uint8_t brightnessForTime(uint8_t hour, uint8_t minute) {
|
||||
ScheduleEntry start = schedule.back();
|
||||
ScheduleEntry end = schedule.front();
|
||||
for(ScheduleEntry cur : schedule) {
|
||||
// Find the last hour that is prior to or equal to now
|
||||
if (cur.hour <= hour) {
|
||||
start = cur;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for(ScheduleEntry cur : schedule) {
|
||||
// Find the first hour that is after now
|
||||
// If no such hour exists, we should automatically wrap back to hour 0
|
||||
if (cur.hour > hour) {
|
||||
end = cur;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (start.hour > end.hour) {
|
||||
end.hour += 24;
|
||||
}
|
||||
|
||||
uint16_t startTime = start.hour * 60;
|
||||
uint16_t endTime = end.hour * 60;
|
||||
uint16_t nowTime = hour * 60 + minute;
|
||||
|
||||
uint16_t duration = endTime - startTime;
|
||||
uint16_t curDuration = nowTime - startTime;
|
||||
|
||||
uint8_t frac = ((double)curDuration / (double)duration) * 255.0;
|
||||
|
||||
return lerp8by8(start.brightness, end.brightness, frac);
|
||||
}
|
||||
|
||||
WiFiUDP wifiUdp;
|
||||
NTP ntp(wifiUdp);
|
||||
|
||||
InputFunc circadianRhythm([]() {
|
||||
static bool needsUpdate = true;
|
||||
EVERY_N_SECONDS(60) {
|
||||
needsUpdate = true;
|
||||
}
|
||||
#ifdef PLATFORM_PHOTON
|
||||
if (Time.isValid() && needsUpdate) {
|
||||
needsUpdate = false;
|
||||
return InputEvent{InputEvent::SetBrightness, brightnessForTime(Time.hour(), Time.minute())};
|
||||
}
|
||||
#else
|
||||
ntp.update();
|
||||
if (needsUpdate) {
|
||||
needsUpdate = false;
|
||||
return InputEvent{InputEvent::SetBrightness, brightnessForTime(ntp.hours(), ntp.minutes())};
|
||||
}
|
||||
#endif
|
||||
return InputEvent{};
|
||||
}, "CircadianRhythm", Task::Running);
|
||||
|
||||
// A special mainloop app for configuring hardware settings that reboots the
|
||||
// device when the user is finished.
|
||||
MainLoop configApp{{
|
||||
// Manage read/write of configuration data
|
||||
Static<ConfigService>::instance(),
|
||||
|
||||
#ifdef PLATFORM_PHOTON
|
||||
// Update photon telemetry
|
||||
Static<PhotonTelemetry>::instance(),
|
||||
#endif
|
||||
|
||||
// Read hardware inputs
|
||||
new Buttons(),
|
||||
|
||||
// Map input buttons to configuration commands
|
||||
new ConfigInputTask(),
|
||||
|
||||
// Fill the entire display with a color, to see size
|
||||
&configDisplay,
|
||||
// Render some basic input feedback
|
||||
&inputBlip,
|
||||
// Render it all
|
||||
&configRenderer,
|
||||
}};
|
||||
|
||||
// Turn on,
|
||||
MainLoop renderbugApp{{
|
||||
|
||||
// Load/update graphics configuration from EEPROM and Particle
|
||||
Static<ConfigService>::instance(),
|
||||
|
||||
// Platform inputs
|
||||
#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
|
||||
|
||||
// Hardware drivers
|
||||
//new MPU5060(),
|
||||
//new Buttons(),
|
||||
|
||||
// Map buttons to events
|
||||
&keyMap,
|
||||
|
||||
// Pattern sequencer
|
||||
&sequencer,
|
||||
|
||||
// Daily rhythm activities
|
||||
&circadianRhythm,
|
||||
|
||||
// Periodic motion input
|
||||
&randomPulse,
|
||||
|
||||
// Periodic color inputs
|
||||
&noisebridgeCycle,
|
||||
&kierynCycle,
|
||||
&rainbowCycle,
|
||||
&hackerbotsCycle,
|
||||
|
||||
// Animations
|
||||
&chimes,
|
||||
&drain,
|
||||
&solid,
|
||||
&flashlight,
|
||||
|
||||
// Update UI layer
|
||||
&power,
|
||||
&displayClip,
|
||||
Static<UpdateStatus>::instance(),
|
||||
&inputBlip,
|
||||
|
||||
// Render everything
|
||||
&renderer,
|
||||
|
||||
// Platform telemetry
|
||||
#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(),
|
||||
#endif
|
||||
}};
|
||||
|
||||
MainLoop &runner = renderbugApp;
|
||||
|
||||
#ifdef PLATFORM_PHOTON
|
||||
STARTUP(BootOptions::initPins());
|
||||
retained BootOptions bootopts;
|
||||
#else
|
||||
BootOptions bootopts;
|
||||
|
||||
void printNewline(Print* logOutput) {
|
||||
logOutput->print("\r\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Tune in,
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
#ifdef PLATFORM_PHOTON
|
||||
System.enableFeature(FEATURE_RETAINED_MEMORY);
|
||||
if (bootopts.isFlash) {
|
||||
System.dfu();
|
||||
}
|
||||
if (bootopts.isSerial) {
|
||||
bootopts.waitForRelease();
|
||||
while(!Serial.isConnected()) {
|
||||
Particle.process();
|
||||
}
|
||||
//Log.info("\xf0\x9f\x94\x8c Serial connected");
|
||||
}
|
||||
#else
|
||||
Log.begin(LOG_LEVEL_VERBOSE, &Serial, true);
|
||||
Log.setSuffix(printNewline);
|
||||
Log.notice("Test log?");
|
||||
#endif
|
||||
Log.notice(u8"🐛 Booting Renderbug!");
|
||||
Log.notice(u8"🐞 I am built for %d LEDs running on %dmA", HardwareConfig::MAX_LED_NUM, PSU_MILLIAMPS);
|
||||
#ifdef PLATFORM_PHOTON
|
||||
Log.notice(u8"📡 Particle version %s", System.version().c_str());
|
||||
#endif
|
||||
Log.notice(u8" Boot pin configuration:");
|
||||
Log.notice(u8" 2: Setup - %d", bootopts.isSetup);
|
||||
Log.notice(u8" 3: Serial - %d", bootopts.isSerial);
|
||||
Log.notice(u8" 4: Flash - %d", bootopts.isFlash);
|
||||
|
||||
//Log.info(u8" Setting timezone to UTC-7");
|
||||
//Time.zone(-7);
|
||||
|
||||
Log.notice(u8"💡 Starting FastLED...");
|
||||
#ifdef PLATFORM_PHOTON
|
||||
FastLED.addLeds<NEOPIXEL, 6>(leds, HardwareConfig::MAX_LED_NUM);
|
||||
#else
|
||||
FastLED.addLeds<WS2812B, 13, RGB>(leds, HardwareConfig::MAX_LED_NUM);
|
||||
#endif
|
||||
|
||||
if (bootopts.isSetup) {
|
||||
Log.notice(u8"🌌 Starting Figment in configuration mode...");
|
||||
runner = configApp;
|
||||
} else {
|
||||
Log.notice(u8"🌌 Starting Figment...");
|
||||
}
|
||||
Serial.flush();
|
||||
runner.start();
|
||||
|
||||
//Log.info(u8"💽 %lu bytes of free RAM", System.freeMemory());
|
||||
Log.notice(u8"🚀 Setup complete! Ready to rock and roll.");
|
||||
Serial.flush();
|
||||
ntp.begin();
|
||||
ArduinoOTA.begin();
|
||||
}
|
||||
|
||||
// Drop out.
|
||||
void loop() {
|
||||
runner.loop();
|
||||
}
|
163
src/platform/arduino/MQTTTelemetry.h
Normal file
163
src/platform/arduino/MQTTTelemetry.h
Normal file
@ -0,0 +1,163 @@
|
||||
#include <Input.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <ArduinoLog.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ArduinoUniqueID.h>
|
||||
|
||||
WiFiClient wifi;
|
||||
PubSubClient m_mqtt(wifi);
|
||||
|
||||
class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
|
||||
public:
|
||||
MQTTTelemetry() : BufferedInputSource("MQTT") {}
|
||||
|
||||
void handleEventOnline(const InputEvent& evt) override {
|
||||
if (!m_mqtt.connected()) {
|
||||
Log.notice("Connecting to MQTT...");
|
||||
const IPAddress server(10, 0, 0, 2);
|
||||
uint64_t chipid = ESP.getEfuseMac();
|
||||
char deviceID[15];
|
||||
snprintf(deviceID, 15, "%08X", (uint32_t)chipid);
|
||||
Log.verbose("Device ID %s", deviceID);
|
||||
m_mqtt.setServer(server, 1883);
|
||||
m_mqtt.setBufferSize(512);
|
||||
m_mqtt.setCallback(&MQTTTelemetry::s_callback);
|
||||
if (m_mqtt.connect(deviceID)) {
|
||||
Log.notice("Connected to MQTT");
|
||||
|
||||
const String deviceName = String("Renderbug ESP32 ") + (char*)deviceID;
|
||||
const String rootTopic = String("homeassistant/light/renderbug/") + (char*)deviceID;
|
||||
const String configTopic = rootTopic + "/config";
|
||||
Log.verbose("root topic %s", rootTopic.c_str());
|
||||
Log.verbose("config topic %s", configTopic.c_str());
|
||||
const String statTopic = rootTopic + "/state";
|
||||
const String cmdTopic = rootTopic + "/set";
|
||||
const String attrTopic = rootTopic + "/attributes";
|
||||
strcpy(m_statTopic, statTopic.c_str());
|
||||
strcpy(m_attrTopic, attrTopic.c_str());
|
||||
strcpy(m_cmdTopic, cmdTopic.c_str());
|
||||
|
||||
StaticJsonDocument<1024> configJson;
|
||||
configJson["~"] = rootTopic;
|
||||
configJson["name"] = deviceName;
|
||||
configJson["ret"] = true;
|
||||
configJson["unique_id"] = (char*) deviceID;
|
||||
configJson["cmd_t"] = "~/set";
|
||||
configJson["stat_t"] = "~/state";
|
||||
configJson["json_attr_t"] = "~/attributes";
|
||||
configJson["schema"] = "json";
|
||||
configJson["brightness"] = true;
|
||||
configJson["rgb"] = true;
|
||||
|
||||
configJson["dev"]["name"] = "Renderbug";
|
||||
#ifdef PLATFORM_PHOTON
|
||||
configJson["dev"]["mdl"] = "Photon";
|
||||
#else
|
||||
configJson["dev"]["mdl"] = "ESP32";
|
||||
#endif
|
||||
configJson["dev"]["sw"] = RENDERBUG_VERSION;
|
||||
configJson["dev"]["mf"] = "Phong Robotics";
|
||||
configJson["dev"]["ids"][0] = (char*)deviceID;
|
||||
char buf[1024];
|
||||
serializeJson(configJson, buf, sizeof(buf));
|
||||
Log.verbose("Publish %s %s", configTopic.c_str(), buf);
|
||||
m_mqtt.publish(configTopic.c_str(), buf);
|
||||
m_mqtt.subscribe(m_cmdTopic);
|
||||
} else {
|
||||
Log.warning("Could not connect to MQTT");
|
||||
}
|
||||
} else {
|
||||
if (evt.intent == InputEvent::SetPower) {
|
||||
StaticJsonDocument<256> doc;
|
||||
char buf[256];
|
||||
doc["state"] = evt.asInt() ? "ON" : "OFF";
|
||||
serializeJson(doc, buf, sizeof(buf));
|
||||
m_mqtt.publish(m_statTopic, buf);
|
||||
} else if (evt.intent == InputEvent::SetBrightness) {
|
||||
StaticJsonDocument<256> doc;
|
||||
char buf[256];
|
||||
doc["brightness"] = evt.asInt();
|
||||
serializeJson(doc, buf, sizeof(buf));
|
||||
m_mqtt.publish(m_statTopic, buf);
|
||||
} else if (evt.intent == InputEvent::SetColor) {
|
||||
StaticJsonDocument<256> doc;
|
||||
char buf[256];
|
||||
CRGB color = evt.asRGB();
|
||||
doc["color"]["r"] = color.r;
|
||||
doc["color"]["g"] = color.g;
|
||||
doc["color"]["b"] = color.b;
|
||||
serializeJson(doc, buf, sizeof(buf));
|
||||
m_mqtt.publish(m_statTopic, buf);
|
||||
} else if (evt.intent == InputEvent::SetPattern) {
|
||||
StaticJsonDocument<256> doc;
|
||||
char buf[256];
|
||||
doc["effect"] = evt.asString();
|
||||
serializeJson(doc, buf, sizeof(buf));
|
||||
m_mqtt.publish(m_statTopic, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
BufferedInputSource::loop();
|
||||
OnlineTaskMixin::loop();
|
||||
}
|
||||
|
||||
void loopOnline() override {
|
||||
m_mqtt.loop();
|
||||
EVERY_N_SECONDS(10) {
|
||||
char buf[254];
|
||||
StaticJsonDocument<200> response;
|
||||
response["fps"] = FastLED.getFPS();
|
||||
response["RSSI"] = WiFi.RSSI();
|
||||
response["localip"] = WiFi.localIP().toString();
|
||||
response["free_ram"] = ESP.getFreeHeap();
|
||||
response["os_version"] = ESP.getSdkVersion();
|
||||
response["sketch_version"] = ESP.getSketchMD5();
|
||||
serializeJson(response, buf, sizeof(buf));
|
||||
m_mqtt.publish(m_attrTopic, buf);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
char m_statTopic[100];
|
||||
char m_attrTopic[100];
|
||||
char m_cmdTopic[100];
|
||||
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
DynamicJsonDocument doc(1024);
|
||||
deserializeJson(doc, payload, length);
|
||||
|
||||
if (doc.containsKey("state")) {
|
||||
if (doc["state"] == "ON") {
|
||||
setEvent(InputEvent{InputEvent::SetPower, true});
|
||||
} else if (doc["state"] == "OFF") {
|
||||
setEvent(InputEvent{InputEvent::SetPower, false});
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.containsKey("effect")) {
|
||||
strcpy(m_patternBuf, doc["effect"].as<const char*>());
|
||||
setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf});
|
||||
}
|
||||
|
||||
if (doc.containsKey("color")) {
|
||||
uint8_t r = doc["color"]["r"];
|
||||
uint8_t g = doc["color"]["g"];
|
||||
uint8_t b = doc["color"]["b"];
|
||||
setEvent(InputEvent{InputEvent::SetColor, CRGB(r, g, b)});
|
||||
}
|
||||
|
||||
if (doc.containsKey("brightness")) {
|
||||
setEvent(InputEvent{InputEvent::SetBrightness, (int)doc["brightness"]});
|
||||
}
|
||||
}
|
||||
|
||||
static void s_callback(char* topic, byte* payload, unsigned int length) {
|
||||
Static<MQTTTelemetry>::instance()->callback(topic, payload, length);
|
||||
}
|
||||
|
||||
char m_patternBuf[48];
|
||||
};
|
||||
|
||||
STATIC_ALLOC(MQTTTelemetry);
|
35
src/platform/particle/MDNSService.cpp
Normal file
35
src/platform/particle/MDNSService.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include "MDNS/MDNS.h"
|
||||
#include "Figments/Figments.h"
|
||||
#include "Figments/MainLoop.h"
|
||||
#include "Figments/Input.h"
|
||||
#include "colors.h"
|
||||
|
||||
class MDNSService : public Task {
|
||||
private:
|
||||
mdns::MDNS m_mdns;
|
||||
bool m_online = false;
|
||||
|
||||
public:
|
||||
MDNSService() : Task("MDNS") {
|
||||
}
|
||||
|
||||
void handleEvent(const InputEvent &evt) {
|
||||
if (evt.intent == InputEvent::NetworkStatus) {
|
||||
m_mdns.setHostname("renderbug");
|
||||
m_mdns.addService("tcp", "http", 80, "Renderbug");
|
||||
m_mdns.begin(true);
|
||||
m_online = true;
|
||||
}
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
if (m_online) {
|
||||
// Returns true when it reads at least one byte
|
||||
if (m_mdns.processQueries()) {
|
||||
MainLoop::instance()->dispatch(InputEvent::NetworkActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
STATIC_ALLOC(MDNSService);
|
205
src/platform/particle/MQTTTelemetry.cpp
Normal file
205
src/platform/particle/MQTTTelemetry.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
#include "MQTT/MQTT.h"
|
||||
|
||||
class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
|
||||
public:
|
||||
MQTTTelemetry() : BufferedInputSource("MQTT"), m_client("relay.malloc.hackerbots.net", 1883, 512, 15, MQTTTelemetry::s_callback, true) {
|
||||
strcpy(m_deviceName, System.deviceID().c_str());
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
BufferedInputSource::loop();
|
||||
OnlineTaskMixin::loop();
|
||||
}
|
||||
|
||||
void handleEventOnline(const InputEvent& event) override {
|
||||
char response[255];
|
||||
if (event.intent == InputEvent::SetPower) {
|
||||
JSONBufferWriter writer(response, sizeof(response));
|
||||
writer.beginObject();
|
||||
writer.name("state").value(event.asInt() == 0 ? "OFF" : "ON");
|
||||
writer.endObject();
|
||||
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
|
||||
m_client.publish(m_statTopic, response, MQTT::QOS1);
|
||||
} else if (event.intent == InputEvent::SetBrightness) {
|
||||
JSONBufferWriter writer(response, sizeof(response));
|
||||
writer.beginObject();
|
||||
writer.name("brightness").value(event.asInt());
|
||||
writer.endObject();
|
||||
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
|
||||
m_client.publish(m_statTopic, response, MQTT::QOS1);
|
||||
} else if (event.intent == InputEvent::SetColor) {
|
||||
JSONBufferWriter writer(response, sizeof(response));
|
||||
writer.beginObject();
|
||||
writer.name("color").beginObject();
|
||||
CRGB rgb = event.asRGB();
|
||||
writer.name("r").value(rgb.r);
|
||||
writer.name("g").value(rgb.g);
|
||||
writer.name("b").value(rgb.b);
|
||||
writer.endObject();
|
||||
writer.endObject();
|
||||
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
|
||||
m_client.publish(m_statTopic, response, MQTT::QOS1);
|
||||
} else if (event.intent == InputEvent::SetPattern) {
|
||||
JSONBufferWriter writer(response, sizeof(response));
|
||||
writer.beginObject();
|
||||
writer.name("effect").value(event.asString());
|
||||
writer.endObject();
|
||||
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
|
||||
m_client.publish(m_statTopic, response, MQTT::QOS1);
|
||||
} else {
|
||||
if (m_lastIntent != event.intent) {
|
||||
m_lastIntent = event.intent;
|
||||
JSONBufferWriter writer(response, sizeof(response));
|
||||
writer.beginObject();
|
||||
writer.name("intent").value(event.intent);
|
||||
writer.endObject();
|
||||
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
|
||||
m_client.publish("renderbug/events", response, MQTT::QOS1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loopOnline() override {
|
||||
if (m_client.isConnected()) {
|
||||
m_client.loop();
|
||||
EVERY_N_SECONDS(10) {
|
||||
char heartbeatBuf[255];
|
||||
JSONBufferWriter writer(heartbeatBuf, sizeof(heartbeatBuf));
|
||||
writer.beginObject();
|
||||
writer.name("fps").value(FastLED.getFPS());
|
||||
writer.name("os_version").value(System.version());
|
||||
writer.name("free_ram").value((unsigned int)System.freeMemory());
|
||||
//writer.name("uptime").value(System.uptime());
|
||||
/*WiFiSignal sig = WiFi.RSSI();
|
||||
writer.name("strength").value(sig.getStrength());
|
||||
writer.name("quality").value(sig.getQuality());*/
|
||||
writer.name("RSSI").value(WiFi.RSSI());
|
||||
writer.name("SSID").value(WiFi.SSID());
|
||||
//writer.name("MAC").value(WiFi.macAddress());
|
||||
writer.name("localip").value(WiFi.localIP().toString());
|
||||
writer.name("startPixel").value(Static<ConfigService>::instance()->coordMap()->startPixel);
|
||||
writer.name("pixelCount").value(Static<ConfigService>::instance()->coordMap()->pixelCount);
|
||||
writer.endObject();
|
||||
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
|
||||
m_client.publish(m_attrTopic, heartbeatBuf);
|
||||
}
|
||||
} else {
|
||||
m_client.connect("renderbug_" + String(m_deviceName) + "_" + String(Time.now()));
|
||||
char response[512];
|
||||
JSONBufferWriter writer(response, sizeof(response));
|
||||
|
||||
String devTopic = String("homeassistant/light/renderbug/") + m_deviceName;
|
||||
writer.beginObject();
|
||||
writer.name("~").value(devTopic.c_str());
|
||||
writer.name("name").value("Renderbug Photon");
|
||||
writer.name("unique_id").value(m_deviceName);
|
||||
writer.name("cmd_t").value("~/set");
|
||||
writer.name("stat_t").value("~/state");
|
||||
writer.name("json_attr_t").value("~/attributes");
|
||||
writer.name("schema").value("json");
|
||||
writer.name("brightness").value(true);
|
||||
writer.name("rgb").value(true);
|
||||
writer.name("ret").value(true);
|
||||
|
||||
writer.name("dev").beginObject();
|
||||
writer.name("name").value("Renderbug");
|
||||
writer.name("mdl").value("Renderbug");
|
||||
writer.name("sw").value(RENDERBUG_VERSION);
|
||||
writer.name("mf").value("Phong Robotics");
|
||||
|
||||
writer.name("ids").beginArray();
|
||||
writer.value(m_deviceName);
|
||||
writer.endArray();
|
||||
|
||||
writer.endObject();
|
||||
|
||||
writer.name("fx_list").beginArray();
|
||||
for(const Sequencer::Scene& scene : sequencer.scenes()) {
|
||||
writer.value(scene.name);
|
||||
}
|
||||
writer.endArray();
|
||||
|
||||
writer.endObject();
|
||||
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
|
||||
|
||||
String configTopic = devTopic + "/config";
|
||||
m_client.publish(configTopic, response, true);
|
||||
|
||||
String statTopic = devTopic + "/state";
|
||||
String cmdTopic = devTopic + "/set";
|
||||
String attrTopic = devTopic + "/attributes";
|
||||
strcpy(m_statTopic, statTopic.c_str());
|
||||
strcpy(m_attrTopic, attrTopic.c_str());
|
||||
strcpy(m_cmdTopic, cmdTopic.c_str());
|
||||
m_client.subscribe(m_cmdTopic, MQTT::QOS1);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void callback(char* topic, byte* payload, unsigned int length) {
|
||||
MainLoop::instance()->dispatch(InputEvent::NetworkActivity);
|
||||
if (!strcmp(topic, m_cmdTopic)) {
|
||||
JSONValue cmd = JSONValue::parseCopy((char*)payload, length);
|
||||
JSONObjectIterator cmdIter(cmd);
|
||||
while(cmdIter.next()) {
|
||||
if (cmdIter.name() == "state" && cmdIter.value().toString() == "ON") {
|
||||
setEvent({InputEvent::SetPower, 1});
|
||||
} else if (cmdIter.name() == "state" && cmdIter.value().toString() == "OFF") {
|
||||
setEvent({InputEvent::SetPower, 0});
|
||||
}
|
||||
|
||||
if (cmdIter.name() == "color") {
|
||||
JSONObjectIterator colorIter(cmdIter.value());
|
||||
uint8_t r, g, b;
|
||||
while(colorIter.next()) {
|
||||
if (colorIter.name() == "r") {
|
||||
r = colorIter.value().toInt();
|
||||
} else if (colorIter.name() == "g") {
|
||||
g = colorIter.value().toInt();
|
||||
} else if (colorIter.name() == "b") {
|
||||
b = colorIter.value().toInt();
|
||||
}
|
||||
}
|
||||
setEvent(InputEvent{InputEvent::SetColor, CRGB{r, g, b}});
|
||||
}
|
||||
|
||||
if (cmdIter.name() == "brightness") {
|
||||
uint8_t brightness = cmdIter.value().toInt();
|
||||
setEvent(InputEvent{InputEvent::SetBrightness, brightness});
|
||||
}
|
||||
|
||||
if (cmdIter.name() == "effect") {
|
||||
strcpy(m_patternBuf, cmdIter.value().toString().c_str());
|
||||
setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf});
|
||||
}
|
||||
|
||||
if (cmdIter.name() == "pixelCount") {
|
||||
setEvent(InputEvent{InputEvent::SetDisplayLength, cmdIter.value().toInt()});
|
||||
}
|
||||
|
||||
if (cmdIter.name() == "startPixel") {
|
||||
setEvent(InputEvent{InputEvent::SetDisplayOffset, cmdIter.value().toInt()});
|
||||
}
|
||||
|
||||
if (cmdIter.name() == "save") {
|
||||
setEvent(InputEvent{InputEvent::SaveConfigurationRequest});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void s_callback(char* topic, byte* payload, unsigned int length) {
|
||||
Static<MQTTTelemetry>::instance()->callback(topic, payload, length);
|
||||
};
|
||||
|
||||
MQTT m_client;
|
||||
InputEvent::Intent m_lastIntent;
|
||||
char m_deviceName[100];
|
||||
char m_statTopic[100];
|
||||
char m_attrTopic[100];
|
||||
char m_cmdTopic[100];
|
||||
char m_patternBuf[48];
|
||||
};
|
||||
|
||||
STATIC_ALLOC(MQTTTelemetry);
|
||||
|
165
src/platform/particle/PhotonTelemetry.cpp
Normal file
165
src/platform/particle/PhotonTelemetry.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
#include "PhotonTelemetry.h"
|
||||
#include "../../Static.h"
|
||||
|
||||
using namespace NSFastLED;
|
||||
|
||||
PhotonTelemetry::PhotonTelemetry() : Task("PhotonTelemetry") {}
|
||||
|
||||
void
|
||||
PhotonTelemetry::onConnected()
|
||||
{
|
||||
Log.info("Connecting photon telemetry...");
|
||||
Particle.variable("frame", m_frameIdx);
|
||||
Particle.variable("brightness", m_currentBrightness);
|
||||
Particle.variable("fps", m_fps);
|
||||
Particle.variable("services", m_serviceList);
|
||||
Particle.variable("localip", m_localIP);
|
||||
m_online = true;
|
||||
}
|
||||
|
||||
void
|
||||
PhotonTelemetry::loop()
|
||||
{
|
||||
m_frameIdx++;
|
||||
if (m_rgbPulseFrame == 1) {
|
||||
m_ledStatus.setActive(false);
|
||||
} else if (m_rgbPulseFrame > 0) {
|
||||
m_rgbPulseFrame--;
|
||||
}
|
||||
|
||||
m_currentBrightness = NSFastLED::FastLED.getBrightness();
|
||||
NSFastLED::FastLED.countFPS();
|
||||
m_fps = NSFastLED::FastLED.getFPS();
|
||||
|
||||
if (m_online) {
|
||||
EVERY_N_SECONDS(30) {
|
||||
m_localIP = WiFi.localIP().toString();
|
||||
char valueBuf[255];
|
||||
snprintf(valueBuf, sizeof(valueBuf), "{\"fps\": %lu, \"localip\": \"%s\"}", m_fps, m_localIP.c_str());
|
||||
Log.info("Heartbeat: %s", valueBuf);
|
||||
Particle.publish("renderbug/heartbeat", valueBuf);
|
||||
auto sched = MainLoop::instance()->scheduler;
|
||||
m_serviceList = String{};
|
||||
for(auto task : sched.tasks) {
|
||||
m_serviceList.concat(task->name);
|
||||
m_serviceList.concat(':');
|
||||
if (task->state == Task::Running) {
|
||||
m_serviceList.concat(1);
|
||||
} else {
|
||||
m_serviceList.concat(0);
|
||||
}
|
||||
m_serviceList.concat(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PhotonTelemetry::handleEvent(const InputEvent &evt)
|
||||
{
|
||||
Serial.flush();
|
||||
if (evt.intent == InputEvent::NetworkStatus) {
|
||||
onConnected();
|
||||
}
|
||||
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 (m_online) {
|
||||
if (evt.intent != m_lastEvent.intent) {
|
||||
if (m_duplicateEvents > 0) {
|
||||
Log.info("Suppressed reporting %ld duplicate events.", m_duplicateEvents);
|
||||
}
|
||||
Log.info("Event: %s", buf);
|
||||
m_duplicateEvents = 0;
|
||||
m_lastEvent = evt;
|
||||
Particle.publish("renderbug/event", buf, PRIVATE);
|
||||
} else {
|
||||
m_duplicateEvents++;
|
||||
}
|
||||
} else {
|
||||
Log.info("[offline] Event: %s", buf);
|
||||
}
|
||||
|
||||
if (evt.intent == InputEvent::SetColor) {
|
||||
NSFastLED::CRGB rgb {evt.asRGB()};
|
||||
uint32_t color = (rgb.r << 16) + (rgb.g << 8) + (rgb.b << 0);
|
||||
m_ledStatus.setColor(color);
|
||||
m_ledStatus.setActive(true);
|
||||
m_rgbPulseFrame = 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
STATIC_ALLOC(PhotonTelemetry);
|
25
src/platform/particle/PhotonTelemetry.h
Normal file
25
src/platform/particle/PhotonTelemetry.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "./Figments/Figments.h"
|
||||
|
||||
class PhotonTelemetry: public Task {
|
||||
public:
|
||||
PhotonTelemetry();
|
||||
void loop() override;
|
||||
void handleEvent(const InputEvent& evt) override;
|
||||
|
||||
private:
|
||||
void onConnected();
|
||||
|
||||
int m_frameIdx;
|
||||
String m_serviceList;
|
||||
String m_localIP;
|
||||
uint32_t m_currentBrightness;
|
||||
LEDStatus m_ledStatus = LEDStatus(0, LED_PATTERN_FADE, LED_SPEED_FAST);
|
||||
uint32_t m_rgbPulseFrame = 0;
|
||||
uint32_t m_pixelCount = 0;
|
||||
uint32_t m_startPixel = 0;
|
||||
uint32_t m_fps = 0;
|
||||
uint32_t m_duplicateEvents = 0;
|
||||
InputEvent m_lastEvent;
|
||||
bool m_online = false;
|
||||
};
|
30
src/platform/particle/Watchdog.cpp
Normal file
30
src/platform/particle/Watchdog.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include <Figments.h>
|
||||
|
||||
BootOpts bootopts;
|
||||
|
||||
void watchdogHandler() {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
leds[i] = CRGB(i % 3 ? 35 : 255, 0, 0);
|
||||
}
|
||||
FastLED.show();
|
||||
if (bootopts.lastBootWasFlash) {
|
||||
System.dfu();
|
||||
} else {
|
||||
System.enterSafeMode();
|
||||
}
|
||||
}
|
||||
|
||||
class Watchdog : public Task {
|
||||
public:
|
||||
Watchdog() : Task("Watchdog") {
|
||||
m_watchdog = new ApplicationWatchdog(5000, watchdogHandler, 1536);
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
m_watchdog->checkin();
|
||||
}
|
||||
private:
|
||||
ApplicationWatchdog *m_watchdog;
|
||||
};
|
||||
|
||||
STATIC_ALLOC(Watchdog);
|
157
src/platform/particle/WebTelemetry.cpp
Normal file
157
src/platform/particle/WebTelemetry.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
#include "WebDuino/WebDuino.h"
|
||||
#include "Figments/Figments.h"
|
||||
#include "Figments/Input.h"
|
||||
#include "colors.h"
|
||||
#include "Sequencer.h"
|
||||
|
||||
class WebTelemetry : public Task {
|
||||
private:
|
||||
TCPServer m_server;
|
||||
TCPClient m_client;
|
||||
Sequencer& m_sequencer;
|
||||
|
||||
void onConnected() {
|
||||
m_server.begin();
|
||||
Log.info("HTTP server started on %s:80", WiFi.localIP().toString().c_str());
|
||||
}
|
||||
|
||||
void redirectToRoot() {
|
||||
m_server.write("HTTP/1.1 303 Redirect\n");
|
||||
m_server.write("Location: /\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
}
|
||||
|
||||
public:
|
||||
WebTelemetry(Sequencer& sequencer) : Task("WebTelemetry"), m_server(80), m_sequencer(sequencer) {
|
||||
}
|
||||
|
||||
void handleEvent(const InputEvent &evt) {
|
||||
if (evt.intent == InputEvent::NetworkStatus) {
|
||||
onConnected();
|
||||
}
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
static String taskName;
|
||||
if (m_client.connected()) {
|
||||
if (m_client.available()) {
|
||||
MainLoop::instance()->dispatch(InputEvent::NetworkActivity);
|
||||
String requestLine = m_client.readStringUntil('\n');
|
||||
Log.info("%s %s", m_client.remoteIP().toString().c_str(), requestLine.c_str());
|
||||
if (requestLine.startsWith("GET")) {
|
||||
int httpVersionIdx = requestLine.lastIndexOf(" ");
|
||||
String uri = requestLine.substring(4, httpVersionIdx);
|
||||
if (uri.equals("/")) {
|
||||
m_server.write("HTTP/1.1 200 Renderbug is here!\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
m_server.write("<!DOCTYPE HTML><html><body>");
|
||||
m_server.write("<p>Scenes</p><table>");
|
||||
auto curScene = m_sequencer.currentSceneName();
|
||||
for(auto scene : m_sequencer.scenes()) {
|
||||
bool isEnabled = strcmp(curScene, scene.name) == 0;
|
||||
m_server.write("<tr><td>");
|
||||
if (isEnabled) {
|
||||
m_server.write("<strong>");
|
||||
}
|
||||
m_server.write(scene.name);
|
||||
if (isEnabled) {
|
||||
m_server.write("</strong>");
|
||||
}
|
||||
m_server.write("</td><td><ul>");
|
||||
for(auto patternName : scene.patterns) {
|
||||
m_server.write("<li>");
|
||||
m_server.write(patternName);
|
||||
m_server.write("</li>");
|
||||
}
|
||||
m_server.write("</ul></td></tr>");
|
||||
}
|
||||
m_server.write("</table>");
|
||||
m_server.write("<form><button name=\"pattern\" value=\"prev\">Previous pattern</button><button name=\"pattern\" value=\"next\">Next pattern</button></form>");
|
||||
m_server.write("<form><select name=\"color\">");
|
||||
const ColorInfo* colors = allColors();
|
||||
for(int i = 0; colors[i].name != 0;i++) {
|
||||
m_server.write("<option>");
|
||||
m_server.write(colors[i].name);
|
||||
m_server.write("</option>");
|
||||
}
|
||||
m_server.write("</select>");
|
||||
m_server.write("<button>Set Color</button>");
|
||||
m_server.write("</form>");
|
||||
m_server.write("<p>Tasks</p><table>");
|
||||
auto sched = MainLoop::instance()->scheduler;
|
||||
for(auto task : sched.tasks) {
|
||||
bool isFigment = task->isFigment();
|
||||
|
||||
m_server.write("<tr><td>");
|
||||
if (isFigment) {
|
||||
m_server.write("<strong>");
|
||||
}
|
||||
m_server.write(task->name);
|
||||
if (isFigment) {
|
||||
m_server.write("</strong>");
|
||||
}
|
||||
m_server.write("</td><td>");
|
||||
if (task->state == Task::Running) {
|
||||
m_server.write("Running");
|
||||
} else {
|
||||
m_server.write("Paused");
|
||||
}
|
||||
m_server.write("</td><td>");
|
||||
m_server.write("<a href=\"/?stop=");
|
||||
m_server.write(task->name);
|
||||
m_server.write("\">Stop</a></td><td>");
|
||||
m_server.write("<a href=\"/?start=");
|
||||
m_server.write(task->name);
|
||||
m_server.write("\">Start</a></td></tr>");
|
||||
}
|
||||
m_server.write("</table>");
|
||||
m_server.write("<a href='/reboot'>Reboot Renderbug</a>");
|
||||
m_server.write("<a href='/save'>Save configuration</a>");
|
||||
} else if (uri.startsWith("/save")) {
|
||||
MainLoop::instance()->dispatch(InputEvent::SaveConfigurationRequest);
|
||||
redirectToRoot();
|
||||
} else if (uri.startsWith("/?color=")) {
|
||||
int varStart = uri.indexOf("=");
|
||||
String colorName = uri.substring(varStart + 1);
|
||||
colorName.replace('+', ' ');
|
||||
colorName.replace("%20", " ");
|
||||
ColorInfo nextColor = colorForName(colorName);
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetColor, nextColor.rgb});
|
||||
redirectToRoot();
|
||||
} else if (uri.startsWith("/?start=")) {
|
||||
int varStart = uri.indexOf("=");
|
||||
taskName = uri.substring(varStart + 1);
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, taskName.c_str()});
|
||||
redirectToRoot();
|
||||
} else if (uri.startsWith("/?stop=")) {
|
||||
int varStart = uri.indexOf("=");
|
||||
taskName = uri.substring(varStart + 1);
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, taskName.c_str()});
|
||||
redirectToRoot();
|
||||
} else if (uri.equals("/?pattern=prev")) {
|
||||
redirectToRoot();
|
||||
MainLoop::instance()->dispatch(InputEvent::PreviousPattern);
|
||||
} else if (uri.equals("/?pattern=next")) {
|
||||
redirectToRoot();
|
||||
MainLoop::instance()->dispatch(InputEvent::NextPattern);
|
||||
} else if (uri.equals("/reboot")) {
|
||||
m_server.write("HTTP/1.1 200 Ok\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
m_server.write("Rebooting!");
|
||||
} else {
|
||||
m_server.write("HTTP/1.1 404 Not found\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
}
|
||||
} else {
|
||||
m_server.write("HTTP/1.1 501 Not Implemented\n");
|
||||
m_server.write("Connection: close\n\n");
|
||||
}
|
||||
}
|
||||
m_client.stop();
|
||||
} else {
|
||||
m_client = m_server.available();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
28
src/platform/particle/inputs/CloudStatus.cpp
Normal file
28
src/platform/particle/inputs/CloudStatus.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "CloudStatus.h"
|
||||
#include "../../../Static.h"
|
||||
|
||||
void
|
||||
CloudStatus::onStart()
|
||||
{
|
||||
SINGLE_THREADED_BLOCK() {
|
||||
if (Particle.connected()) {
|
||||
initNetwork(0, cloud_status_connected);
|
||||
} else {
|
||||
System.on(cloud_status, &CloudStatus::initNetwork);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CloudStatus::initNetwork(system_event_t event, int param) {
|
||||
if (param == cloud_status_connected) {
|
||||
Log.info("Connected to T H E C L O U D");
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::NetworkStatus, true});
|
||||
} else if (param == cloud_status_disconnected) {
|
||||
Log.info("Lost cloud connection!!");
|
||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::NetworkStatus, false});
|
||||
}
|
||||
}
|
||||
|
||||
STATIC_ALLOC(CloudStatus);
|
11
src/platform/particle/inputs/CloudStatus.h
Normal file
11
src/platform/particle/inputs/CloudStatus.h
Normal file
@ -0,0 +1,11 @@
|
||||
#include "../../../Figments/Figments.h"
|
||||
|
||||
class CloudStatus: public Task {
|
||||
public:
|
||||
CloudStatus() : Task("Cloud") {}
|
||||
void loop() override {}
|
||||
void onStart() override;
|
||||
|
||||
private:
|
||||
static void initNetwork(system_event_t event, int param);
|
||||
};
|
187
src/platform/particle/inputs/Photon.cpp
Normal file
187
src/platform/particle/inputs/Photon.cpp
Normal file
@ -0,0 +1,187 @@
|
||||
#include "Particle.h"
|
||||
#include "../../../Figments/Figments.h"
|
||||
#include "../../../colors.h"
|
||||
#include "../../../Static.h"
|
||||
#include "./Photon.h"
|
||||
|
||||
void
|
||||
PhotonInput::onConnected()
|
||||
{
|
||||
Log.info("Connecting photon input...");
|
||||
Particle.function("save", &PhotonInput::save, this);
|
||||
Particle.function("power", &PhotonInput::setPower, this);
|
||||
Particle.function("next", &PhotonInput::nextPattern, this);
|
||||
Particle.function("input", &PhotonInput::input, this);
|
||||
Particle.function("previous", &PhotonInput::previousPattern, this);
|
||||
Particle.function("pattern", &PhotonInput::setPattern, this);
|
||||
Particle.function("setHue", &PhotonInput::setHue, this);
|
||||
Particle.function("brightness", &PhotonInput::setBrightness, this);
|
||||
Particle.function("reboot", &PhotonInput::reboot, this);
|
||||
Particle.function("start", &PhotonInput::startThing, this);
|
||||
Particle.function("stop", &PhotonInput::stopThing, this);
|
||||
|
||||
//Log.info("Connecting photon configuration...");
|
||||
Particle.function("pixelCount", &PhotonInput::setPixelCount, this);
|
||||
Particle.function("startPixel", &PhotonInput::setStartPixel, this);
|
||||
|
||||
Particle.function("save", &PhotonInput::photonSave, this);
|
||||
Particle.variable("pixelCount", m_pixelCountInt);
|
||||
Particle.variable("startPixel", m_startPixelInt);
|
||||
|
||||
publishConfig();
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::startThing(String command)
|
||||
{
|
||||
command.toCharArray(m_commandBuf, sizeof(m_commandBuf));
|
||||
setEvent(InputEvent(InputEvent::StartThing, m_commandBuf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::stopThing(String command)
|
||||
{
|
||||
command.toCharArray(m_commandBuf, sizeof(m_commandBuf));
|
||||
setEvent(InputEvent(InputEvent::StopThing, m_commandBuf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::onStart()
|
||||
{
|
||||
System.on(firmware_update, &PhotonInput::onFirmwareUpdate);
|
||||
System.on(button_click, &PhotonInput::onButtonClick);
|
||||
System.on(reset, &PhotonInput::onReset);
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::handleEvent(const InputEvent &evt)
|
||||
{
|
||||
if (evt.intent == InputEvent::NetworkStatus) {
|
||||
onConnected();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::reboot(String command)
|
||||
{
|
||||
System.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::onReset(system_event_t event, int param)
|
||||
{
|
||||
NSFastLED::FastLED.clear();
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::onButtonClick(system_event_t event, int param)
|
||||
{
|
||||
Static<PhotonInput>::instance()->setEvent(InputEvent{InputEvent::NextPattern, param});
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::onFirmwareUpdate(system_event_t event, int param)
|
||||
{
|
||||
Static<PhotonInput>::instance()->setEvent(InputEvent{InputEvent::FirmwareUpdate, param});
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::input(String command)
|
||||
{
|
||||
command.toCharArray(m_commandBuf, sizeof(m_commandBuf));
|
||||
setEvent(InputEvent(InputEvent::UserInput, m_commandBuf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setPattern(String command)
|
||||
{
|
||||
command.toCharArray(m_commandBuf, sizeof(m_commandBuf));
|
||||
setEvent(InputEvent(InputEvent::SetPattern, m_commandBuf));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setHue(String colorName)
|
||||
{
|
||||
ColorInfo nextColor = colorForName(colorName);
|
||||
setEvent(InputEvent(InputEvent::SetColor, nextColor.rgb));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::nextPattern(String command)
|
||||
{
|
||||
setEvent(InputEvent(InputEvent::NextPattern));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::previousPattern(String command)
|
||||
{
|
||||
setEvent(InputEvent(InputEvent::PreviousPattern));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::save(String command) {
|
||||
setEvent(InputEvent::SaveConfigurationRequest);
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setPower(String command)
|
||||
{
|
||||
if (command == "off") {
|
||||
setEvent(InputEvent(InputEvent::SetPower, 0));
|
||||
} else if (command == "on") {
|
||||
setEvent(InputEvent(InputEvent::SetPower, 1));
|
||||
} else {
|
||||
setEvent(InputEvent(InputEvent::PowerToggle));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setBrightness(String command)
|
||||
{
|
||||
setEvent(InputEvent(InputEvent::SetBrightness, command.toInt()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
PhotonInput::publishConfig() const
|
||||
{
|
||||
char buf[255];
|
||||
snprintf(buf, sizeof(buf), "{\"pixels\": \"%d\", \"offset\": \"%d\"}", m_config.data.pixelCount, m_config.data.startPixel);
|
||||
Particle.publish("renderbug/config", buf, PRIVATE);
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::photonSave(String command)
|
||||
{
|
||||
setEvent(InputEvent::SaveConfiguration);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setPixelCount(String command)
|
||||
{
|
||||
m_pixelCount = command.toInt();
|
||||
setEvent(InputEvent{InputEvent::SetDisplayLength, m_pixelCount})
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
PhotonInput::setStartPixel(String command)
|
||||
{
|
||||
m_startPixel = command.toInt();
|
||||
setEvent(InputEvent{InputEvent::SetDisplayLength, m_startPixel})
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
STATIC_ALLOC(PhotonInput);
|
38
src/platform/particle/inputs/Photon.h
Normal file
38
src/platform/particle/inputs/Photon.h
Normal file
@ -0,0 +1,38 @@
|
||||
#include "Particle.h"
|
||||
#include "../../../Figments/Figments.h"
|
||||
|
||||
class PhotonInput: public BufferedInputSource {
|
||||
public:
|
||||
PhotonInput() : BufferedInputSource("PhotonInput") {}
|
||||
void onStart() override;
|
||||
void handleEvent(const InputEvent &evt) override;
|
||||
|
||||
private:
|
||||
char m_commandBuf[16];
|
||||
|
||||
void onConnected();
|
||||
int reboot(String command);
|
||||
int input(String command);
|
||||
int setPattern(String command);
|
||||
int setHue(String colorName);
|
||||
int nextPattern(String command);
|
||||
int previousPattern(String command);
|
||||
int setPower(String command);
|
||||
int setBrightness(String command);
|
||||
int save(String command);
|
||||
|
||||
int startThing(String command);
|
||||
int stopThing(String command);
|
||||
|
||||
void publishConfig() const;
|
||||
int photonSave(String command);
|
||||
int setPixelCount(String command);
|
||||
int setStartPixel(String command);
|
||||
|
||||
static void onReset(system_event_t event, int param);
|
||||
static void onButtonClick(system_event_t event, int param);
|
||||
static void onFirmwareUpdate(system_event_t event, int param);
|
||||
|
||||
int m_startPixel;
|
||||
int m_pixelCount;
|
||||
};
|
72
src/sprites/Blob.h
Normal file
72
src/sprites/Blob.h
Normal file
@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <ArduinoLog.h>
|
||||
|
||||
class Blob {
|
||||
uint16_t m_pos;
|
||||
int8_t m_velocity;
|
||||
uint8_t m_hue;
|
||||
int16_t m_brightness;
|
||||
uint8_t m_saturation;
|
||||
int8_t m_fadeDir;
|
||||
public:
|
||||
Blob()
|
||||
: m_pos(0),
|
||||
m_velocity(1),
|
||||
m_hue(0),
|
||||
m_brightness(1),
|
||||
m_saturation(200),
|
||||
m_fadeDir(1) {}
|
||||
|
||||
void setSaturation(uint8_t v) {
|
||||
m_saturation = v;
|
||||
}
|
||||
|
||||
void setPos(uint16_t p) {
|
||||
m_pos = p;
|
||||
m_brightness = p % 120 + 1;
|
||||
}
|
||||
|
||||
void setHue(uint8_t p) {
|
||||
m_hue = p;
|
||||
}
|
||||
|
||||
void setBrightness(uint8_t p) {
|
||||
m_brightness = p;
|
||||
}
|
||||
|
||||
void setVelocity(int8_t v) {
|
||||
m_velocity = v;
|
||||
}
|
||||
|
||||
void update() {
|
||||
m_pos += m_velocity;
|
||||
m_hue += 1;
|
||||
m_brightness += m_fadeDir;
|
||||
if (m_brightness >= 255 || m_brightness <= 0) {
|
||||
m_fadeDir *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
void render(Display* display) const {
|
||||
const uint8_t width = 25;
|
||||
//Log.notice("get coords");
|
||||
auto map = display->coordinateMapping();
|
||||
// Grab the physical pixel we'll start with
|
||||
PhysicalCoordinates startPos = map->virtualToPhysicalCoords({m_pos, 0});
|
||||
PhysicalCoordinates endPos = map->virtualToPhysicalCoords({m_pos + width, 0});
|
||||
int scaledWidth = std::abs(endPos.x - startPos.x);
|
||||
|
||||
//Log.notice("blob w=%d x=%d", scaledWidth, startPos.x);
|
||||
for(uint8_t i = 0;i < scaledWidth; i++) {
|
||||
// Blobs desaturate towards their tail
|
||||
//Log.notice("blob i=%d w=%d x=%d", i, scaledWidth, startPos.x);
|
||||
CHSV blobColor(m_hue, m_saturation, quadwave8((i / (double)scaledWidth) * m_brightness));
|
||||
|
||||
PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), 0};
|
||||
|
||||
CRGB src(display->pixelAt(pos));
|
||||
display->pixelAt(pos) = blend(CRGB(blobColor), src, 200);
|
||||
}
|
||||
}
|
||||
};
|
77
src/sprites/Chime.h
Normal file
77
src/sprites/Chime.h
Normal file
@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
template<int ChimeLength>
|
||||
class Chime {
|
||||
uint16_t m_pos;
|
||||
uint8_t m_speed;
|
||||
uint16_t m_hue;
|
||||
uint16_t m_saturation;
|
||||
uint16_t m_brightness;
|
||||
unsigned int m_offset;
|
||||
|
||||
public:
|
||||
|
||||
static const int Length = ChimeLength;
|
||||
|
||||
Chime()
|
||||
: m_pos(0),
|
||||
m_speed(128),
|
||||
m_hue(210),
|
||||
m_saturation(255),
|
||||
m_brightness(255),
|
||||
m_offset(0) {}
|
||||
|
||||
void setSaturation(uint8_t i) {
|
||||
m_saturation = i % 255;
|
||||
}
|
||||
|
||||
void setSpeed(uint8_t i) {
|
||||
m_speed = i % 255;
|
||||
}
|
||||
|
||||
void setOffset(unsigned int i) {
|
||||
m_offset = i;
|
||||
}
|
||||
|
||||
void setPos(uint8_t i) {
|
||||
m_pos = i;
|
||||
}
|
||||
|
||||
void setHue(uint8_t i) {
|
||||
m_hue = i % 255;
|
||||
}
|
||||
|
||||
void setBrightness(uint8_t i) {
|
||||
m_brightness = i % 255;
|
||||
}
|
||||
|
||||
void update() {
|
||||
m_pos += 1;
|
||||
|
||||
if (random(255) > m_speed) {
|
||||
m_pos += 2;
|
||||
}
|
||||
|
||||
if (m_pos > ChimeLength * 20) {
|
||||
m_pos = 0;
|
||||
m_hue += 3;
|
||||
m_hue %= 255;
|
||||
}
|
||||
}
|
||||
|
||||
void render(Display* dpy) const {
|
||||
for(int i = 0; i < ChimeLength; i++) {
|
||||
if (i > m_pos) {
|
||||
dpy->pixelAt(i + m_offset) = CHSV(0, 0, 0);
|
||||
} else {
|
||||
uint8_t distance = m_pos - i;
|
||||
uint16_t brightness = scale8(quadwave8((ChimeLength / (double)distance) * 255), m_brightness);
|
||||
if (brightness <= 0.2)
|
||||
brightness = 0;
|
||||
dpy->pixelAt(VirtualCoordinates{i + m_offset, 0}) = CHSV(m_hue, std::min(m_saturation, brightness), brightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user