diff --git a/firmware/Config.cpp b/firmware/Config.cpp new file mode 100644 index 0000000..b59c6b7 --- /dev/null +++ b/firmware/Config.cpp @@ -0,0 +1,130 @@ +#include "./Config.h" +#include "./Static.h" + +HardwareConfig +HardwareConfig::load() { + HardwareConfig ret; + EEPROM.get(0, ret); + return ret; +} + +void +HardwareConfig::save() { + HardwareConfig dataCopy{*this}; + dataCopy.checksum = getCRC(); + EEPROM.put(0, dataCopy); +} + +bool +HardwareConfig::isValid() const +{ + return version == 1 && checksum == getCRC(); +} + +uint8_t +HardwareConfig::getCRC() const +{ + const unsigned char* message = reinterpret_cast(&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.info("Starting configuration..."); + m_config = HardwareConfig::load(); + if (m_config.isValid()) { + Log.info("Configuration found!"); + } else { + Log.info("No configuration found. Writing defaults..."); + m_config = HardwareConfig{}; + m_config.save(); + } + m_pixelCount = AnimatedNumber{m_config.data.pixelCount}; + m_startPixel = AnimatedNumber{m_config.data.startPixel}; + Log.info("Configured to use %d pixels, starting at %d", m_pixelCount.value(), m_startPixel.value()); + Log.info("Loading task states..."); + for(int i = 0; i < 32; i++) { + auto svc = m_config.data.serviceStates[i]; + if (strlen(svc.name) > 0) { + Log.info("* %s: %s", svc.name, svc.isRunning ? "RUNNING" : "STOPPED"); + } + } +} + +void +ConfigService::onConnected() +{ + Log.info("Connecting photon configuration..."); + Particle.function("pixelCount", &ConfigService::setPixelCount, this); + Particle.function("startPixel", &ConfigService::setStartPixel, this); + + Particle.function("save", &ConfigService::photonSave, this); + Particle.variable("pixelCount", m_pixelCountInt); + Particle.variable("startPixel", m_startPixelInt); + + publishConfig(); +} + +void +ConfigService::loop() +{ + m_startPixel.update(); + m_pixelCount.update(); + m_coordMap.startPixel = m_startPixel; + m_coordMap.pixelCount = m_pixelCount; +} + +void +ConfigService::handleEvent(const InputEvent &evt) +{ + if (evt.intent == InputEvent::NetworkStatus) { + onConnected(); + } +} + +void +ConfigService::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 +ConfigService::photonSave(String command) +{ + m_config.save(); + return 0; +} + +int +ConfigService::setPixelCount(String command) +{ + m_config.data.pixelCount = command.toInt(); + m_pixelCount = m_config.data.pixelCount; + publishConfig(); + return 0; +} + +int +ConfigService::setStartPixel(String command) +{ + m_config.data.startPixel = command.toInt(); + m_startPixel = m_config.data.startPixel; + publishConfig(); + return 0; +} + +STATIC_ALLOC(ConfigService); diff --git a/firmware/Config.h b/firmware/Config.h new file mode 100644 index 0000000..925f759 --- /dev/null +++ b/firmware/Config.h @@ -0,0 +1,53 @@ +#pragma once +#include "Particle.h" +#include "./Figments/Figments.h" + +struct HardwareConfig { + uint8_t version = 1; + uint8_t checksum = 0; + struct TaskState { + char name[16] = {0}; + bool isRunning = false; + }; + struct Data { + uint16_t pixelCount = 255; + uint16_t startPixel = 0; + TaskState serviceStates[32]; + }; + Data data; + + static HardwareConfig load(); + void save(); + bool isValid() const; + +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: + void onConnected(); + void publishConfig() const; + int photonSave(String command); + int setPixelCount(String command); + int setStartPixel(String command); + + AnimatedNumber m_pixelCount; + AnimatedNumber m_startPixel; + int m_pixelCountInt; + int m_startPixelInt; + + HardwareConfig m_config; + LinearCoordinateMapping m_coordMap; +}; diff --git a/firmware/FastLED/FastLED.cpp b/firmware/FastLED/FastLED.cpp new file mode 120000 index 0000000..e82b2a1 --- /dev/null +++ b/firmware/FastLED/FastLED.cpp @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/FastLED.cpp \ No newline at end of file diff --git a/firmware/FastLED/FastLED.h b/firmware/FastLED/FastLED.h new file mode 120000 index 0000000..703ee95 --- /dev/null +++ b/firmware/FastLED/FastLED.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/FastLED.h \ No newline at end of file diff --git a/firmware/FastLED/FastSPI_LED2.h b/firmware/FastLED/FastSPI_LED2.h new file mode 120000 index 0000000..c3284ca --- /dev/null +++ b/firmware/FastLED/FastSPI_LED2.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/FastSPI_LED2.h \ No newline at end of file diff --git a/firmware/FastLED/LICENSE b/firmware/FastLED/LICENSE new file mode 120000 index 0000000..9f51ffe --- /dev/null +++ b/firmware/FastLED/LICENSE @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/LICENSE \ No newline at end of file diff --git a/firmware/FastLED/PORTING.md b/firmware/FastLED/PORTING.md new file mode 120000 index 0000000..62a7ccf --- /dev/null +++ b/firmware/FastLED/PORTING.md @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/PORTING.md \ No newline at end of file diff --git a/firmware/FastLED/README.md b/firmware/FastLED/README.md new file mode 120000 index 0000000..7cebb30 --- /dev/null +++ b/firmware/FastLED/README.md @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/README.md \ No newline at end of file diff --git a/firmware/FastLED/bitswap.h b/firmware/FastLED/bitswap.h new file mode 120000 index 0000000..484fbe0 --- /dev/null +++ b/firmware/FastLED/bitswap.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/bitswap.h \ No newline at end of file diff --git a/firmware/FastLED/chipsets.h b/firmware/FastLED/chipsets.h new file mode 120000 index 0000000..9a5ccd1 --- /dev/null +++ b/firmware/FastLED/chipsets.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/chipsets.h \ No newline at end of file diff --git a/firmware/FastLED/clockless_arm_stm32.h b/firmware/FastLED/clockless_arm_stm32.h new file mode 120000 index 0000000..d42deb4 --- /dev/null +++ b/firmware/FastLED/clockless_arm_stm32.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/clockless_arm_stm32.h \ No newline at end of file diff --git a/firmware/FastLED/color.h b/firmware/FastLED/color.h new file mode 120000 index 0000000..4488c49 --- /dev/null +++ b/firmware/FastLED/color.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/color.h \ No newline at end of file diff --git a/firmware/FastLED/colorpalettes.cpp b/firmware/FastLED/colorpalettes.cpp new file mode 120000 index 0000000..75d0c46 --- /dev/null +++ b/firmware/FastLED/colorpalettes.cpp @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/colorpalettes.cpp \ No newline at end of file diff --git a/firmware/FastLED/colorpalettes.h b/firmware/FastLED/colorpalettes.h new file mode 120000 index 0000000..a8ca684 --- /dev/null +++ b/firmware/FastLED/colorpalettes.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/colorpalettes.h \ No newline at end of file diff --git a/firmware/FastLED/colorutils.cpp b/firmware/FastLED/colorutils.cpp new file mode 120000 index 0000000..5fb0ce9 --- /dev/null +++ b/firmware/FastLED/colorutils.cpp @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/colorutils.cpp \ No newline at end of file diff --git a/firmware/FastLED/colorutils.h b/firmware/FastLED/colorutils.h new file mode 120000 index 0000000..339e209 --- /dev/null +++ b/firmware/FastLED/colorutils.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/colorutils.h \ No newline at end of file diff --git a/firmware/FastLED/controller.h b/firmware/FastLED/controller.h new file mode 120000 index 0000000..c00e95e --- /dev/null +++ b/firmware/FastLED/controller.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/controller.h \ No newline at end of file diff --git a/firmware/FastLED/delay.h b/firmware/FastLED/delay.h new file mode 120000 index 0000000..3bf3b32 --- /dev/null +++ b/firmware/FastLED/delay.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/delay.h \ No newline at end of file diff --git a/firmware/FastLED/dmx.h b/firmware/FastLED/dmx.h new file mode 120000 index 0000000..a69a4bc --- /dev/null +++ b/firmware/FastLED/dmx.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/dmx.h \ No newline at end of file diff --git a/firmware/FastLED/docs b/firmware/FastLED/docs new file mode 120000 index 0000000..e7df508 --- /dev/null +++ b/firmware/FastLED/docs @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/docs \ No newline at end of file diff --git a/firmware/FastLED/examples b/firmware/FastLED/examples new file mode 120000 index 0000000..6a3e154 --- /dev/null +++ b/firmware/FastLED/examples @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/examples \ No newline at end of file diff --git a/firmware/FastLED/fastled_arm_stm32.h b/firmware/FastLED/fastled_arm_stm32.h new file mode 120000 index 0000000..d7d422a --- /dev/null +++ b/firmware/FastLED/fastled_arm_stm32.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastled_arm_stm32.h \ No newline at end of file diff --git a/firmware/FastLED/fastled_config.h b/firmware/FastLED/fastled_config.h new file mode 120000 index 0000000..22e3c58 --- /dev/null +++ b/firmware/FastLED/fastled_config.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastled_config.h \ No newline at end of file diff --git a/firmware/FastLED/fastpin.h b/firmware/FastLED/fastpin.h new file mode 120000 index 0000000..8f80ce2 --- /dev/null +++ b/firmware/FastLED/fastpin.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastpin.h \ No newline at end of file diff --git a/firmware/FastLED/fastpin_arm_stm32.h b/firmware/FastLED/fastpin_arm_stm32.h new file mode 120000 index 0000000..80eb7da --- /dev/null +++ b/firmware/FastLED/fastpin_arm_stm32.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastpin_arm_stm32.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi.h b/firmware/FastLED/fastspi.h new file mode 120000 index 0000000..248b025 --- /dev/null +++ b/firmware/FastLED/fastspi.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_bitbang.h b/firmware/FastLED/fastspi_bitbang.h new file mode 120000 index 0000000..eb2b1ae --- /dev/null +++ b/firmware/FastLED/fastspi_bitbang.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_bitbang.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_dma.h b/firmware/FastLED/fastspi_dma.h new file mode 120000 index 0000000..706d926 --- /dev/null +++ b/firmware/FastLED/fastspi_dma.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_dma.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_nop.h b/firmware/FastLED/fastspi_nop.h new file mode 120000 index 0000000..e155d4a --- /dev/null +++ b/firmware/FastLED/fastspi_nop.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_nop.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_ref.h b/firmware/FastLED/fastspi_ref.h new file mode 120000 index 0000000..b3e57ef --- /dev/null +++ b/firmware/FastLED/fastspi_ref.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_ref.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_types.h b/firmware/FastLED/fastspi_types.h new file mode 120000 index 0000000..3c63fb3 --- /dev/null +++ b/firmware/FastLED/fastspi_types.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_types.h \ No newline at end of file diff --git a/firmware/FastLED/hsv2rgb.cpp b/firmware/FastLED/hsv2rgb.cpp new file mode 120000 index 0000000..859dbb2 --- /dev/null +++ b/firmware/FastLED/hsv2rgb.cpp @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/hsv2rgb.cpp \ No newline at end of file diff --git a/firmware/FastLED/hsv2rgb.h b/firmware/FastLED/hsv2rgb.h new file mode 120000 index 0000000..8427440 --- /dev/null +++ b/firmware/FastLED/hsv2rgb.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/hsv2rgb.h \ No newline at end of file diff --git a/firmware/FastLED/keywords.txt b/firmware/FastLED/keywords.txt new file mode 120000 index 0000000..d466098 --- /dev/null +++ b/firmware/FastLED/keywords.txt @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/keywords.txt \ No newline at end of file diff --git a/firmware/FastLED/led_sysdefs.h b/firmware/FastLED/led_sysdefs.h new file mode 120000 index 0000000..2656be0 --- /dev/null +++ b/firmware/FastLED/led_sysdefs.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/led_sysdefs.h \ No newline at end of file diff --git a/firmware/FastLED/led_sysdefs_arm_stm32.h b/firmware/FastLED/led_sysdefs_arm_stm32.h new file mode 120000 index 0000000..e8f4e74 --- /dev/null +++ b/firmware/FastLED/led_sysdefs_arm_stm32.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/led_sysdefs_arm_stm32.h \ No newline at end of file diff --git a/firmware/FastLED/lib8tion.cpp b/firmware/FastLED/lib8tion.cpp new file mode 120000 index 0000000..b7688bb --- /dev/null +++ b/firmware/FastLED/lib8tion.cpp @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/lib8tion.cpp \ No newline at end of file diff --git a/firmware/FastLED/lib8tion.h b/firmware/FastLED/lib8tion.h new file mode 120000 index 0000000..1dc6fe0 --- /dev/null +++ b/firmware/FastLED/lib8tion.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/lib8tion.h \ No newline at end of file diff --git a/firmware/FastLED/noise.cpp b/firmware/FastLED/noise.cpp new file mode 120000 index 0000000..7b4909d --- /dev/null +++ b/firmware/FastLED/noise.cpp @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/noise.cpp \ No newline at end of file diff --git a/firmware/FastLED/noise.h b/firmware/FastLED/noise.h new file mode 120000 index 0000000..16747fc --- /dev/null +++ b/firmware/FastLED/noise.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/noise.h \ No newline at end of file diff --git a/firmware/FastLED/pixeltypes.h b/firmware/FastLED/pixeltypes.h new file mode 120000 index 0000000..4bb6865 --- /dev/null +++ b/firmware/FastLED/pixeltypes.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/pixeltypes.h \ No newline at end of file diff --git a/firmware/FastLED/platforms b/firmware/FastLED/platforms new file mode 120000 index 0000000..36ddb1e --- /dev/null +++ b/firmware/FastLED/platforms @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/platforms \ No newline at end of file diff --git a/firmware/FastLED/platforms.h b/firmware/FastLED/platforms.h new file mode 120000 index 0000000..872b881 --- /dev/null +++ b/firmware/FastLED/platforms.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/platforms.h \ No newline at end of file diff --git a/firmware/FastLED/power_mgt.cpp b/firmware/FastLED/power_mgt.cpp new file mode 120000 index 0000000..87ed38b --- /dev/null +++ b/firmware/FastLED/power_mgt.cpp @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/power_mgt.cpp \ No newline at end of file diff --git a/firmware/FastLED/power_mgt.h b/firmware/FastLED/power_mgt.h new file mode 120000 index 0000000..d0fe179 --- /dev/null +++ b/firmware/FastLED/power_mgt.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/power_mgt.h \ No newline at end of file diff --git a/firmware/FastLED/preview_changes.txt b/firmware/FastLED/preview_changes.txt new file mode 120000 index 0000000..f03f6c1 --- /dev/null +++ b/firmware/FastLED/preview_changes.txt @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/preview_changes.txt \ No newline at end of file diff --git a/firmware/FastLED/release_notes.md b/firmware/FastLED/release_notes.md new file mode 120000 index 0000000..9f66b3a --- /dev/null +++ b/firmware/FastLED/release_notes.md @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/release_notes.md \ No newline at end of file diff --git a/firmware/FastLED/wiring.cpp b/firmware/FastLED/wiring.cpp new file mode 120000 index 0000000..62c12ec --- /dev/null +++ b/firmware/FastLED/wiring.cpp @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/FastLED/firmware/wiring.cpp \ No newline at end of file diff --git a/firmware/Figments/Animation.cpp b/firmware/Figments/Animation.cpp new file mode 100644 index 0000000..3eeac2a --- /dev/null +++ b/firmware/Figments/Animation.cpp @@ -0,0 +1,10 @@ +#include "./Animation.h" +#include "./Input.h" +#include "./Display.h" +#include + +uint8_t +AnimatedNumber::value() const +{ + return NSFastLED::lerp8by8(m_start, m_end, m_idx); +} diff --git a/firmware/Figments/Animation.h b/firmware/Figments/Animation.h new file mode 100644 index 0000000..2b93258 --- /dev/null +++ b/firmware/Figments/Animation.h @@ -0,0 +1,147 @@ +#pragma once +#include "FastLED/FastLED.h" +#include "./Figment.h" +#include + +class Display; + +struct AnimatedNumber { + void set(uint8_t v) { + set(value(), v); + } + + void set(uint8_t start, uint8_t end) { + m_start = start; + m_end = end; + m_idx = 0; + } + + uint8_t value() const; + + void update() { + if (m_idx < 255) { + m_idx += 1; + } + } + + AnimatedNumber() {} + AnimatedNumber(uint8_t v) : m_end(v) {} + + AnimatedNumber& operator=(uint8_t v) { + set(v); + return *this; + } + + AnimatedNumber& operator+=(uint8_t v) { + set(value()+v); + return *this; + } + + operator uint8_t() const { + return value(); + } + + operator int() const { + return value(); + } + + operator unsigned int() const { + return value(); + } + + bool operator==(int other) const { + return value() == other; + } + + uint8_t operator+(uint8_t other) const { + return value() + other; + } + + int operator+(int other) const { + return value() + other; + } + +private: + uint8_t m_idx = 255; + uint8_t m_start = 0; + uint8_t m_end = 0; +}; + +struct AnimatedRGB { + NSFastLED::CRGB start; + NSFastLED::CRGB end; + AnimatedNumber pos; + + AnimatedRGB(const NSFastLED::CRGB& color) + : start(color), end(color) {} + + AnimatedRGB() {} + + AnimatedRGB& operator=(const NSFastLED::CRGB& rgb) { + start = *this; + end = rgb; + pos.set(0, 255); + return *this; + } + + void update() { + pos.update(); + } + + operator NSFastLED::CRGB() const { + uint8_t red = NSFastLED::lerp8by8(start.red, end.red, pos); + uint8_t green = NSFastLED::lerp8by8(start.green, end.green, pos); + uint8_t blue = NSFastLED::lerp8by8(start.blue, end.blue, pos); + return NSFastLED::CRGB(red, green, blue); + } +}; + +template +struct SpriteList { + void update() { + if (!m_enabled) return; + for(int i = 0; i < size; i++) { + animations[i].update(); + } + } + + void render(Display* dpy) const { + if (!m_enabled) return; + for(int i = 0; i < size; i++) { + animations[i].render(dpy); + } + } + + void forEach(std::function func) { + for(int i = 0; i < size; i++) { + func(animations[i]); + } + } + + void forEach(std::function func) const { + for(int i = 0; i < size; i++) { + func(animations[i]); + } + } + + void disable() { + m_enabled = false; + } + + void enable() { + m_enabled = true; + } + + void toggle() { + m_enabled = !m_enabled; + } + + T& operator[](int idx) { + return animations[idx]; + } + + T animations[Size]; + bool m_enabled = true; + static constexpr int size = Size; + using Type = T; +}; diff --git a/firmware/Figments/Display.cpp b/firmware/Figments/Display.cpp new file mode 100644 index 0000000..a2455c8 --- /dev/null +++ b/firmware/Figments/Display.cpp @@ -0,0 +1,59 @@ +#include "Display.h" +#include + +using namespace NSFastLED; + +int +Display::pixelCount() const +{ + return m_pixelCount; +} + +CRGB* +Display::pixelBacking() const +{ + return m_pixels; +} + +void +Display::clear() +{ + clear(CRGB(0, 0, 0)); +} + +void +Display::clear(const CRGB& color) +{ + for(int i = 0; i < m_pixelCount;i++) { + m_pixels[i] = color; + } +} + +CRGB& +Display::pixelAt(const PhysicalCoordinates coords) +{ + return pixelAt(m_coordMap->physicalCoordsToIndex(coords)); +} + +CRGB& +Display::pixelAt(const VirtualCoordinates coords) +{ + return pixelAt(m_coordMap->virtualToPhysicalCoords(coords)); +} + +CRGB& +Display::pixelAt(int idx) +{ + const int kx = idx % pixelCount(); + if (kx < 0) { + return m_pixels[pixelCount() + 1 + kx]; + } else { + return m_pixels[kx]; + } +} + +const CoordinateMapping* +Display::coordinateMapping() const +{ + return m_coordMap; +} diff --git a/firmware/Figments/Display.h b/firmware/Figments/Display.h new file mode 100644 index 0000000..9a60a00 --- /dev/null +++ b/firmware/Figments/Display.h @@ -0,0 +1,56 @@ +#pragma once +#include "FastLED/FastLED.h" + +#include "Geometry.h" + +struct CoordinateMapping { + virtual VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const = 0; + virtual PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const = 0; + virtual int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const = 0; + virtual unsigned int physicalPixelCount() const = 0; +}; + +struct LinearCoordinateMapping: CoordinateMapping { + unsigned int pixelCount = 1; + unsigned int startPixel = 0; + LinearCoordinateMapping() {} + LinearCoordinateMapping(unsigned int count, unsigned int start) : pixelCount(count), startPixel(start) {} + VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const { + return VirtualCoordinates{(uint8_t)((localCoords.x) / pixelCount), 0}; + } + + PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const { + return PhysicalCoordinates{NSFastLED::scale8(pixelCount, virtualCoords.x), 0}; + } + + int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override { + return localCoords.x + startPixel; + } + + unsigned int physicalPixelCount() const { + return pixelCount; + } +}; + +class Display { + public: + + Display(NSFastLED::CRGB* pixels, int pixelCount, const CoordinateMapping* map) + : m_pixels(pixels), m_pixelCount(pixelCount), m_coordMap(map) {} + + NSFastLED::CRGB& pixelAt(const PhysicalCoordinates coords); + NSFastLED::CRGB& pixelAt(const VirtualCoordinates coords); + NSFastLED::CRGB& pixelAt(int idx); + + int pixelCount() const; + NSFastLED::CRGB* pixelBacking() const; + const CoordinateMapping* coordinateMapping() const; + void clear(); + void clear(const NSFastLED::CRGB& color); + void clear(VirtualCoordinates& start, VirtualCoordinates& end, const NSFastLED::CRGB& color); + + private: + NSFastLED::CRGB* m_pixels; + int m_pixelCount; + const CoordinateMapping* m_coordMap; +}; diff --git a/firmware/Figments/Figment.h b/firmware/Figments/Figment.h new file mode 100644 index 0000000..65719ee --- /dev/null +++ b/firmware/Figments/Figment.h @@ -0,0 +1,47 @@ +#pragma once +#include "application.h" +#include + +class Display; +class InputEvent; +class InputSource; + +struct Task { + virtual void handleEvent(const InputEvent& event) {} + virtual void loop() = 0; + virtual void onStart() {}; + virtual void onStop() {}; + + enum State { + Running, + Stopped, + }; + + Task() {} + Task(State initialState) : Task(0, initialState) {} + Task(const char* name) : Task(name, Running) {} + Task(const char* name, State initialState) : name(name), state(initialState) {} + + void start() { Log.info("* Starting %s...", name); state = Running; onStart(); } + void stop() { Log.info("* Stopping %s...", name); onStop(); state = Stopped; } + + const char* name = 0; + State state = Running; +}; + +struct Figment: public Task { + Figment() : Task() {} + Figment(State initialState) : Task(initialState) {} + Figment(const char* name) : Task(name) {} + Figment(const char* name, State initialState) : Task(name, initialState) {} + virtual void render(Display* dpy) const = 0; +}; + +struct FigmentFunc: public Figment { + FigmentFunc(std::function func) : Figment("lambda"), func(func) {} + void loop() override {} + void render(Display* dpy) const override { + func(dpy); + } + std::function func; +}; diff --git a/firmware/Figments/Figments.h b/firmware/Figments/Figments.h new file mode 100644 index 0000000..90c7c09 --- /dev/null +++ b/firmware/Figments/Figments.h @@ -0,0 +1,8 @@ +#pragma once +#include "./Display.h" +#include "./Input.h" +#include "./Figment.h" +#include "./Animation.h" +#include "./MainLoop.h" +#include "./Renderer.h" +#include "./Surface.h" diff --git a/firmware/Figments/Geometry.h b/firmware/Figments/Geometry.h new file mode 100644 index 0000000..170b171 --- /dev/null +++ b/firmware/Figments/Geometry.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +template struct Coordinates { + Coordinates(T _x, T _y) : x(_x), y(_y) {} + T x; + T y; +}; + +struct VirtualCoordinates: Coordinates { + VirtualCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {} +}; + +struct PhysicalCoordinates: Coordinates { + PhysicalCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {} +}; + +template struct Vector3d { + Vector3d(T _x, T _y, T _z) : x(_x), y(_y), z(_z) {} + Vector3d() : Vector3d(0, 0, 0) {} + T x; + T y; + T z; + + T magnitude() const { + return abs(max(x, max(y, z))); + } + + Vector3d operator-(const Vector3d& other) const { + return Vector3d(x - other.x, y - other.y, z - other.z); + } + + Vector3d absolute() const { + return Vector3d(abs(x), abs(y), abs(z)); + } +}; + +typedef Vector3d Vec3; diff --git a/firmware/Figments/Input.cpp b/firmware/Figments/Input.cpp new file mode 100644 index 0000000..c98fed9 --- /dev/null +++ b/firmware/Figments/Input.cpp @@ -0,0 +1,41 @@ +#include "application.h" +#include "./Input.h" +#include "./MainLoop.h" + +NSFastLED::CRGB +Variant::asRGB() const +{ + return NSFastLED::CRGB(m_value.asRGB[0], m_value.asRGB[1], m_value.asRGB[2]); +} + +const char* +Variant::asString() const +{ + return m_value.asString; +} + +int +Variant::asInt() const +{ + return m_value.asInt; +} + +void +InputSource::loop() +{ + MainLoop::instance()->dispatch(read()); +} + +InputEvent +BufferedInputSource::read() +{ + InputEvent ret = m_lastEvent; + m_lastEvent = InputEvent{}; + return ret; +} + +void +BufferedInputSource::setEvent(InputEvent &&evt) +{ + m_lastEvent = std::move(evt); +} diff --git a/firmware/Figments/Input.h b/firmware/Figments/Input.h new file mode 100644 index 0000000..fa94304 --- /dev/null +++ b/firmware/Figments/Input.h @@ -0,0 +1,95 @@ +#pragma once +#include "application.h" +#include "./Geometry.h" +#include "./Figment.h" +#include "FastLED/FastLED.h" + +typedef Vector3d MotionVec; + +struct Variant { + enum Type { + Null, + Integer, + String, + Color, + }; + + Variant(int v) + : type(Integer), m_value{.asInt=v} {} + + Variant(const char* v) + : type(String), m_value{.asString=v} {} + + Variant(const NSFastLED::CRGB &v) + : type(Color), m_value{.asRGB={v.r, v.g, v.b}} {} + + Variant() + : type(Null) {} + + Type type; + + const char* asString() const; + NSFastLED::CRGB asRGB() const; + int asInt() const; + +private: + union { + int asInt; + const char* asString; + uint8_t asRGB[3]; + } m_value; +}; + +struct InputEvent: public Variant { + enum Intent { + None, + PowerToggle, + SetPower, + SetBrightness, + PreviousPattern, + NextPattern, + SetPattern, + SetColor, + Acceleration, + FirmwareUpdate, + NetworkStatus, + StartThing, + StopThing, + UserInput, + }; + + template + InputEvent(Intent s, Value v) + : Variant(v), intent(s) {} + + InputEvent(Intent s) + : Variant(), intent(s) {} + + InputEvent() + : Variant(), intent(None) {} + + Intent intent; +}; + +class InputSource: public Task { +public: + InputSource() : Task() {} + InputSource(const char* name) : Task(name) {} + InputSource(Task::State initialState) : Task(initialState) {} + InputSource(const char* name, Task::State initialState) : Task(name, initialState) {} + void loop() override; + virtual InputEvent read() = 0; +}; + +class BufferedInputSource: public InputSource { +public: + BufferedInputSource() : InputSource() {} + BufferedInputSource(const char* name) : InputSource(name) {} + InputEvent read() override; + +protected: + void setEvent(InputEvent &&evt); + +private: + InputEvent m_lastEvent; +}; diff --git a/firmware/Figments/MainLoop.cpp b/firmware/Figments/MainLoop.cpp new file mode 100644 index 0000000..61d1cd8 --- /dev/null +++ b/firmware/Figments/MainLoop.cpp @@ -0,0 +1,51 @@ +#include "./MainLoop.h" +#include "./Input.h" +#include "./Figment.h" + +void +MainLoop::dispatch(const InputEvent& evt) +{ + if (evt.intent == InputEvent::None) { + return; + } + m_eventBuf.insert(evt); +} + +void +MainLoop::loop() +{ + InputEvent evt; + while (m_eventBuf.take(evt)) { + if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) { + const bool jobState = (evt.intent == InputEvent::StartThing); + for(auto figmentJob: scheduler.tasks) { + if (strcmp(figmentJob->name, evt.asString()) == 0) { + if (jobState) { + figmentJob->start(); + } else { + figmentJob->stop(); + } + } + } + } + + for(Task* task : scheduler) { + task->handleEvent(evt); + } + } + for(Task* task : scheduler) { + task->loop(); + } +} + +void +MainLoop::start() +{ + Log.info("*** Starting %d tasks...", scheduler.tasks.size()); + Serial.flush(); + for(auto task: scheduler) { + task->start(); + } +} + +MainLoop* MainLoop::s_instance; diff --git a/firmware/Figments/MainLoop.h b/firmware/Figments/MainLoop.h new file mode 100644 index 0000000..b6d50cd --- /dev/null +++ b/firmware/Figments/MainLoop.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include "./Input.h" + +class Task; +class InputSource; + +struct Scheduler { + std::vector tasks; + + bool operator==(const Scheduler& other) const { + return tasks == other.tasks; + } + + Scheduler(std::vector &&tasks) + : tasks(std::move(tasks)) + {} + + struct iterator: public std::iterator { + Scheduler& queue; + int idx = 0; + explicit iterator(Scheduler& queue) : queue(queue), idx(nextEnabled(0)) {} + iterator(Scheduler& queue, int start) : queue(queue), idx(nextEnabled(start)) {} + iterator& operator++() { + while (idx < queue.tasks.size() && !queue.tasks[idx]->state == Task::Running) { + idx++; + } + idx = nextEnabled(idx+1); + return *this; + } + + int nextEnabled(int start) const { + for(int pos = start; pos < queue.tasks.size();pos++) { + if (queue.tasks[pos]->state == Task::Running) { + return pos; + } + } + return queue.tasks.size(); + } + + iterator operator++(int) {iterator ret = *this; ++(*this); return ret;} + bool operator==(iterator other) const { return idx == other.idx && queue == other.queue; } + bool operator!=(iterator other) const { return !(*this == other); } + Task* operator*() const { return queue.tasks[idx]; } + }; + + iterator begin() { return iterator(*this); } + iterator end() { return iterator(*this, tasks.size()); } +}; + +template +struct Ringbuf { + Ringbuf() : m_head(0), m_tail(0) {} + + void clear() { + m_head = 0; + m_tail = 0; + } + + bool take(T& dest) { + if (m_head == m_tail) { + return false; + } + const int cur = m_head; + const int nextHead = (m_head + 1) % Size; + m_head = nextHead; + dest = m_items[cur]; + return true; + } + + void insert(const T& src) { + const int cur = m_tail; + const int nextTail = (m_tail + 1) % Size; + if (nextTail == m_head) { + return; + } else { + m_tail = nextTail; + } + m_items[cur] = src; + } +private: + int m_head = 0; + int m_tail = 0; + std::array m_items; +}; + + +struct MainLoop { + Scheduler scheduler; + + MainLoop(std::vector &&tasks) + : scheduler(std::move(tasks)) {s_instance = this;} + + void start(); + void loop(); + void dispatch(const InputEvent& event); + static MainLoop* instance() { return s_instance; } + +private: + Ringbuf m_eventBuf; + + static MainLoop* s_instance; +}; diff --git a/firmware/Figments/Renderer.cpp b/firmware/Figments/Renderer.cpp new file mode 100644 index 0000000..9e2dbf5 --- /dev/null +++ b/firmware/Figments/Renderer.cpp @@ -0,0 +1,24 @@ +#include "./Renderer.h" +#include "./Display.h" + +void +Renderer::loop() +{ + for(Display* dpy : m_displays) { + for(Figment* figment : m_figments) { + if (figment->state == Task::Running) { + figment->render(dpy); + } + }; + } + NSFastLED::FastLED.show(); +} + +void +Renderer::onStart() +{ + for(Display* dpy : m_displays) { + dpy->clear(); + } + NSFastLED::FastLED.show(); +} diff --git a/firmware/Figments/Renderer.h b/firmware/Figments/Renderer.h new file mode 100644 index 0000000..8f008df --- /dev/null +++ b/firmware/Figments/Renderer.h @@ -0,0 +1,16 @@ +#include "./Figment.h" +#include + +class Display; + +struct Renderer: public Task { +public: + Renderer(std::vector displays, const std::vector &figments) : Task("Renderer"), m_figments(figments), m_displays(displays) {} + + void loop() override; + void onStart() override; + +private: + const std::vector m_figments; + const std::vector m_displays; +}; diff --git a/firmware/Figments/Service.h b/firmware/Figments/Service.h new file mode 100644 index 0000000..e69de29 diff --git a/firmware/Figments/Surface.cpp b/firmware/Figments/Surface.cpp new file mode 100644 index 0000000..9cbd580 --- /dev/null +++ b/firmware/Figments/Surface.cpp @@ -0,0 +1,37 @@ +#include "./Surface.h" +#include "./Display.h" + +Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end) + : start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)), + end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)), + m_display(dpy) +{ +} + +Surface& +Surface::operator=(const NSFastLED::CRGB& color) +{ + paintWith([&](NSFastLED::CRGB& pixel) { + pixel = color; + }); + return *this; +} + +Surface& +Surface::operator+=(const NSFastLED::CRGB& color) +{ + paintWith([&](NSFastLED::CRGB& pixel) { + pixel += color; + }); + return *this; +} + +void +Surface::paintWith(std::function func) +{ + for(uint8_t x = start.x; x <= end.x; x++) { + for(uint8_t y = start.y; y <= end.y; y++) { + func(m_display->pixelAt(PhysicalCoordinates{x, y})); + } + } +} diff --git a/firmware/Figments/Surface.h b/firmware/Figments/Surface.h new file mode 100644 index 0000000..d629770 --- /dev/null +++ b/firmware/Figments/Surface.h @@ -0,0 +1,20 @@ +#include "FastLED/FastLED.h" +#include "./Geometry.h" + +class Display; + +class Surface { +public: + Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end); + + Surface& operator=(const NSFastLED::CRGB& color); + Surface& operator+=(const NSFastLED::CRGB& color); + + void paintWith(std::function func); + + const PhysicalCoordinates start; + const PhysicalCoordinates end; + +private: + Display* m_display; +}; diff --git a/firmware/ParticleWebLog/ParticleWebLog.cpp b/firmware/ParticleWebLog/ParticleWebLog.cpp new file mode 120000 index 0000000..b6e225c --- /dev/null +++ b/firmware/ParticleWebLog/ParticleWebLog.cpp @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/ParticleWebLog/src/ParticleWebLog.cpp \ No newline at end of file diff --git a/firmware/ParticleWebLog/ParticleWebLog.h b/firmware/ParticleWebLog/ParticleWebLog.h new file mode 120000 index 0000000..67b88d6 --- /dev/null +++ b/firmware/ParticleWebLog/ParticleWebLog.h @@ -0,0 +1 @@ +/home/tdfischer/.po-util/lib/ParticleWebLog/src/ParticleWebLog.h \ No newline at end of file diff --git a/firmware/PhotonTelemetry.cpp b/firmware/PhotonTelemetry.cpp new file mode 100644 index 0000000..cd8ab33 --- /dev/null +++ b/firmware/PhotonTelemetry.cpp @@ -0,0 +1,135 @@ +#include "PhotonTelemetry.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); + 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) { + char valueBuf[255]; + snprintf(valueBuf, sizeof(valueBuf), "{\"fps\": %lu}", m_fps); + 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) +{ + m_lastEvent = evt; + 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::StartThing: + sourceName = "start-thing"; + break; + case InputEvent::StopThing: + sourceName = "stop-thing"; + break; + default: + sourceName = "unknown"; + break; + } + char valueBuf[255]; + switch(evt.type) { + case InputEvent::Null: + snprintf(valueBuf, sizeof(valueBuf), "null");break; + case InputEvent::Integer: + snprintf(valueBuf, sizeof(valueBuf), "%d", 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]; + snprintf(buf, sizeof(buf), "{\"intent\": \"%s\", \"value\": %s}", sourceName, valueBuf); + if (m_online) { + Log.info("Event: %s", buf); + Particle.publish("renderbug/event", buf, PRIVATE); + } 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; + } + } +} diff --git a/firmware/PhotonTelemetry.h b/firmware/PhotonTelemetry.h new file mode 100644 index 0000000..1e9a137 --- /dev/null +++ b/firmware/PhotonTelemetry.h @@ -0,0 +1,23 @@ +#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; + InputEvent m_lastEvent; + 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; + bool m_online = false; +}; diff --git a/firmware/Static.h b/firmware/Static.h new file mode 100644 index 0000000..54737f6 --- /dev/null +++ b/firmware/Static.h @@ -0,0 +1,16 @@ +#pragma once + +// Utility class mostly for when certain inputs need singleton callback handlers +template 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::s_instance=&_staticAlloc__ ## StaticName; + +#define STATIC_ALLOC(Cls) NAMED_STATIC_ALLOC(Cls, Cls) diff --git a/firmware/animations/Chimes.cpp b/firmware/animations/Chimes.cpp new file mode 100644 index 0000000..9fff541 --- /dev/null +++ b/firmware/animations/Chimes.cpp @@ -0,0 +1,77 @@ +#include "../Figments/Figments.h" +#include "../sprites/Chime.h" +#include "../sprites/Blob.h" + +using namespace NSFastLED; + +#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) { + chime.setPos(random(Chime::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) { + 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}); + NSFastLED::CRGB scaledColor = NSFastLED::CRGB(m_flashColor).nscale8_video(max(10, NSFastLED::ease8InOutCubic(m_flashBrightness))); + fullSurface.paintWith([&](NSFastLED::CRGB& pixel) { + pixel = NSFastLED::blend(scaledColor, pixel, 200); + //pixel = scaledColor; + }); + } + + SpriteList, CHIME_COUNT> m_chimes; + SpriteList m_blobs; + AnimatedRGB m_flashColor; + AnimatedNumber m_flashBrightness; +}; diff --git a/firmware/animations/Flashlight.cpp b/firmware/animations/Flashlight.cpp new file mode 100644 index 0000000..c4d71fa --- /dev/null +++ b/firmware/animations/Flashlight.cpp @@ -0,0 +1,49 @@ +#pragma once + +#include "../Figments/Figments.h" +#include "../sprites/Blob.h" + +using NSFastLED::CHSV; + +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 m_blobs; +}; diff --git a/firmware/animations/Power.cpp b/firmware/animations/Power.cpp new file mode 100644 index 0000000..ba0b4b1 --- /dev/null +++ b/firmware/animations/Power.cpp @@ -0,0 +1,40 @@ +#include "../Figments/Figments.h" + +template +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 = NSFastLED::scale8(m_powerState, clippedBrightness); + const uint8_t videoBrightness = NSFastLED::brighten8_video(scaledBrightness); + const uint8_t powerBrightness = NSFastLED::calculate_max_brightness_for_power_mW(videoBrightness, Watts); + NSFastLED::FastLED.setBrightness(powerBrightness); + } + + static constexpr uint32_t Watts = Voltage * MaxMilliAmps; + +private: + AnimatedNumber m_powerState = 255; + AnimatedNumber m_brightness = MaxBrightness; +}; diff --git a/firmware/animations/SolidAnimation.cpp b/firmware/animations/SolidAnimation.cpp new file mode 100644 index 0000000..cf21bb7 --- /dev/null +++ b/firmware/animations/SolidAnimation.cpp @@ -0,0 +1,54 @@ +#include "../Figments/Figments.h" +#include "../sprites/Blob.h" + +using NSFastLED::CRGB; +using NSFastLED::CHSV; +using namespace NSFastLED; + +class SolidAnimation: public Figment { +private: + AnimatedNumber m_red, m_green, m_blue; + static constexpr int blobCount = 20; + SpriteList 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 = NSFastLED::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); + } +}; diff --git a/firmware/animations/TestAnimation.cpp b/firmware/animations/TestAnimation.cpp new file mode 100644 index 0000000..bcd338b --- /dev/null +++ b/firmware/animations/TestAnimation.cpp @@ -0,0 +1,56 @@ +#include "./TestAnimation.h" +#include "FastLED/FastLED.h" + +using NSFastLED::CHSV; +using NSFastLED::scale8; + +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)); + } + } +} diff --git a/firmware/animations/TestAnimation.h b/firmware/animations/TestAnimation.h new file mode 100644 index 0000000..8ece8f3 --- /dev/null +++ b/firmware/animations/TestAnimation.h @@ -0,0 +1,16 @@ +#include "../Figments/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; +}; diff --git a/firmware/animations/UpdateStatus.cpp b/firmware/animations/UpdateStatus.cpp new file mode 100644 index 0000000..5bda05d --- /dev/null +++ b/firmware/animations/UpdateStatus.cpp @@ -0,0 +1,36 @@ +#include "./UpdateStatus.h" +#include "FastLED/FastLED.h" +#include "../Static.h" + +using NSFastLED::CRGB; + +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); diff --git a/firmware/animations/UpdateStatus.h b/firmware/animations/UpdateStatus.h new file mode 100644 index 0000000..6e15634 --- /dev/null +++ b/firmware/animations/UpdateStatus.h @@ -0,0 +1,13 @@ +#include "../Figments/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; +}; diff --git a/firmware/colors.cpp b/firmware/colors.cpp new file mode 100644 index 0000000..6093ea2 --- /dev/null +++ b/firmware/colors.cpp @@ -0,0 +1,809 @@ +#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(); +} diff --git a/firmware/colors.h b/firmware/colors.h new file mode 100644 index 0000000..0798d50 --- /dev/null +++ b/firmware/colors.h @@ -0,0 +1,9 @@ +#pragma once +#include "FastLED/FastLED.h" + +typedef struct ColorInfo { + const char *name; + NSFastLED::CRGB rgb; +} ColorInfo; + +ColorInfo colorForName(const char *name); diff --git a/firmware/inputs/.FlaschenTaschen.cpp.swp b/firmware/inputs/.FlaschenTaschen.cpp.swp new file mode 100644 index 0000000..05274eb Binary files /dev/null and b/firmware/inputs/.FlaschenTaschen.cpp.swp differ diff --git a/firmware/inputs/Buttons.h b/firmware/inputs/Buttons.h new file mode 100644 index 0000000..7487469 --- /dev/null +++ b/firmware/inputs/Buttons.h @@ -0,0 +1,82 @@ +#pragma once + +class Bounce { +public: + void attach(int pin, PinMode 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) { + m_state = Ready; + Log.info("Button %d is released and back to ready!", m_pin); + } else if (m_state == Confirmed) { + m_state = Held; + Log.info("Button %d is being held down!", m_pin); + } + } + } + + void interval(uint8_t v) { + m_interval = v; + } + + bool fell() const { + return m_state == Confirmed; + } + +private: + enum State { + Ready, + Started, + Confirmed, + Held + }; + + State m_state = Ready; + unsigned int m_pin = 0; + unsigned int m_downStart = 0; + unsigned int m_interval = 10; +}; + +class Buttons: public InputSource { +public: + void onStart() override { + for(int i = 0; i < 3; i++) { + m_buttons[i].attach(2 + i, INPUT_PULLDOWN); + m_buttons[i].interval(15); + } + } + + InputEvent read() override { + for(int i = 0; i < 3; i++) { + m_buttons[i].update(); + if (m_buttons[i].fell()) { + return InputEvent{m_buttonMap[i]}; + } + } + return InputEvent{}; + } + +private: + Bounce m_buttons[3]; + InputEvent::Intent m_buttonMap[3] = {InputEvent::PowerToggle, InputEvent::NextPattern, InputEvent::UserInput}; +}; diff --git a/firmware/inputs/CloudStatus.cpp b/firmware/inputs/CloudStatus.cpp new file mode 100644 index 0000000..7082c89 --- /dev/null +++ b/firmware/inputs/CloudStatus.cpp @@ -0,0 +1,25 @@ +#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}); + } +} + +STATIC_ALLOC(CloudStatus); diff --git a/firmware/inputs/CloudStatus.h b/firmware/inputs/CloudStatus.h new file mode 100644 index 0000000..ae361ad --- /dev/null +++ b/firmware/inputs/CloudStatus.h @@ -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); +}; diff --git a/firmware/inputs/ColorCycle.cpp b/firmware/inputs/ColorCycle.cpp new file mode 100644 index 0000000..e69de29 diff --git a/firmware/inputs/ColorCycle.h b/firmware/inputs/ColorCycle.h new file mode 100644 index 0000000..2577a42 --- /dev/null +++ b/firmware/inputs/ColorCycle.h @@ -0,0 +1,20 @@ +#include "../Figments/Figments.h" + +template +class ColorSequenceInput: public InputSource { +public: + ColorSequenceInput(const std::vector &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(); + return InputEvent{InputEvent::SetColor, m_colors[m_idx++]}; + } + return InputEvent{}; + } + +private: + std::vector m_colors; + int m_idx = 0; +}; diff --git a/firmware/inputs/DMX.cpp b/firmware/inputs/DMX.cpp new file mode 100644 index 0000000..be77b4c --- /dev/null +++ b/firmware/inputs/DMX.cpp @@ -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<= 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 diff --git a/firmware/inputs/MPU6050.cpp b/firmware/inputs/MPU6050.cpp new file mode 100644 index 0000000..e69de29 diff --git a/firmware/inputs/MPU6050.h b/firmware/inputs/MPU6050.h new file mode 100644 index 0000000..85603c3 --- /dev/null +++ b/firmware/inputs/MPU6050.h @@ -0,0 +1,94 @@ +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: + 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 + struct Averager { + std::array 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 m_value; +}; diff --git a/firmware/inputs/Photon.cpp b/firmware/inputs/Photon.cpp new file mode 100644 index 0000000..dc0972d --- /dev/null +++ b/firmware/inputs/Photon.cpp @@ -0,0 +1,138 @@ +#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("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); +} + +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::instance()->setEvent(InputEvent{InputEvent::NextPattern, param}); +} + +void +PhotonInput::onFirmwareUpdate(system_event_t event, int param) +{ + Static::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::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; +} + +STATIC_ALLOC(PhotonInput); diff --git a/firmware/inputs/Photon.h b/firmware/inputs/Photon.h new file mode 100644 index 0000000..958aa73 --- /dev/null +++ b/firmware/inputs/Photon.h @@ -0,0 +1,29 @@ +#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 startThing(String command); + int stopThing(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); +}; diff --git a/firmware/inputs/Serial.h b/firmware/inputs/Serial.h new file mode 100644 index 0000000..36fae7f --- /dev/null +++ b/firmware/inputs/Serial.h @@ -0,0 +1,12 @@ +#include "Particle.h" +#include "../Figments/Figments.h" + +class SerialInput: public InputSource { +public: + void onAttach() override { + //Serial.begin(); + } + + InputEvent read() { + } +} diff --git a/firmware/main.cpp b/firmware/main.cpp index 93d9104..677b3e0 100644 --- a/firmware/main.cpp +++ b/firmware/main.cpp @@ -1,11 +1,222 @@ -#include "Particle.h" +#include "FastLED/FastLED.h" +#include "Figments/Figments.h" -void setup() // Put setup code here to run once -{ +#include "PhotonTelemetry.h" +#include "Static.h" +#include "Config.h" +#include "colors.h" +#include "animations/Power.cpp" +#include "animations/SolidAnimation.cpp" +#include "animations/Chimes.cpp" +#include "animations/Flashlight.cpp" +#include "animations/UpdateStatus.h" + +#include "inputs/Photon.h" +#include "inputs/ColorCycle.h" +#include "inputs/CloudStatus.h" +#include "inputs/MPU6050.h" +#include "inputs/Buttons.h" + +SerialLogHandler logHandler; + +using namespace NSFastLED; + +#define LED_NUM 256 +#define MAX_BRIGHTNESS 255 +#define PSU_MILLIAMPS 4800 + +// Enable system thread, so rendering happens while booting +SYSTEM_THREAD(ENABLED); + +// Setup FastLED and the display +CRGB leds[LED_NUM]; +Display dpy(leds, LED_NUM, Static::instance()->coordMap()); + +LinearCoordinateMapping neckMap{60, 0}; +Display neckDisplay(leds, LED_NUM, &neckMap); + +// Setup power management +Power power; + +class DrainAnimation: public Figment { +public: + + DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {} + + void loop() override { + EVERY_N_MILLISECONDS(15) { + m_pos++; + m_fillColor.update(); + } + EVERY_N_MILLISECONDS(50) { + 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; +}; + +// Clip the display at whatever is configured while still showing over-paints +FigmentFunc displayClip([](Display* dpy) { + auto coords = Static::instance()->coordMap(); + for(int i = 0; i < coords->startPixel; i++) { + dpy->pixelAt(i) %= 40; + } + for(int i = LED_NUM; i > coords->pixelCount + coords->startPixel; i--) { + dpy->pixelAt(i) %= 40; + } +}); + +ChimesAnimation chimes{Task::Stopped}; +SolidAnimation solid{Task::Stopped}; +DrainAnimation drain{Task::Running}; +Flashlight flashlight{Task::Stopped}; + + +// Render all layers to the displays +Renderer renderer{ + //{&dpy, &neckDisplay}, + {&dpy}, + { + &chimes, + &drain, + &solid, + &flashlight, + Static::instance(), + &displayClip, + &power, + } +}; + +// Photon telemetry needs a reference to the selected animation's name, so we +// set it up here +PhotonTelemetry telemetry; + +// Cycle some random colors +ColorSequenceInput<7> noisebridgeCycle{{colorForName("Red").rgb}, "Noisebridge", Task::Stopped}; +ColorSequenceInput<7> hackerbotsCycle{{colorForName("Purple").rgb}, "Hackerbots", Task::Stopped}; + +ColorSequenceInput<13> kierynCycle{{ + colorForName("Cerulean").rgb, + colorForName("Electric Purple").rgb, + colorForName("Emerald").rgb, + colorForName("Sky Magenta").rgb +}, "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::Running}; + +// Turn on, +MainLoop runner{{ + + // Load/update graphics configuration from EEPROM and Particle + Static::instance(), + + // Particle cloud status + Static::instance(), + + // Monitor network state and provide particle API events + Static::instance(), + + new MPU5060(), + new Buttons(), + + // Periodic color inputs + &noisebridgeCycle, + &hackerbotsCycle, + &kierynCycle, + &rainbowCycle, + + // Animations + &chimes, + &drain, + &solid, + &flashlight, + + // Update UI layer + &power, + &displayClip, + Static::instance(), + + // Render everything + &renderer, + + // Update photon telemetry + &telemetry, +}}; + +// Tune in, +void setup() { + Serial.begin(115200); + //while(!Serial.isConnected()) { Particle.process(); } + //Serial.println("Hello, there!"); + Log.info("๐Ÿ› Booting Renderbug %s!", System.deviceID().c_str()); + Log.info("๐Ÿž I am built for %d LEDs running on %dmA", LED_NUM, PSU_MILLIAMPS); + Log.info("๐Ÿ“ก Particle version %s", System.version().c_str()); + + Log.info("๐Ÿ’ก Starting FastLED..."); + FastLED.addLeds(leds, LED_NUM); + + Log.info("๐ŸŒŒ Starting Figment..."); + Serial.flush(); + runner.start(); + + Log.info("๐Ÿ’ฝ %lu bytes of free RAM", System.freeMemory()); + Log.info("๐Ÿš€ Setup complete! Ready to rock and roll."); + Serial.flush(); } -void loop() // Put code here to loop forever -{ - +// Drop out. +void loop() { + runner.loop(); } diff --git a/firmware/sprites/Blob.h b/firmware/sprites/Blob.h new file mode 100644 index 0000000..7e124af --- /dev/null +++ b/firmware/sprites/Blob.h @@ -0,0 +1,67 @@ +#pragma once + +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; + 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); + + for(uint8_t i = 0;i < scaledWidth; i++) { + // Blobs desaturate towards their tail + NSFastLED::CHSV blobColor(m_hue, m_saturation, NSFastLED::quadwave8((i / (double)scaledWidth) * m_brightness)); + + PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), 0}; + + NSFastLED::CRGB src(display->pixelAt(pos)); + display->pixelAt(pos) = NSFastLED::blend(NSFastLED::CRGB(blobColor), src, 200); + } + } +}; diff --git a/firmware/sprites/Chime.h b/firmware/sprites/Chime.h new file mode 100644 index 0000000..031b6f9 --- /dev/null +++ b/firmware/sprites/Chime.h @@ -0,0 +1,79 @@ +#pragma once + +using namespace NSFastLED; + +template +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; + uint8_t brightness = NSFastLED::scale8(NSFastLED::quadwave8((ChimeLength / (double)distance) * 255), m_brightness); + if (brightness <= 0.2) + brightness = 0; + dpy->pixelAt(VirtualCoordinates{i + m_offset, 0}) = CHSV(m_hue, min(m_saturation, brightness), brightness); + } + } + } +}; + + diff --git a/libs.txt b/libs.txt new file mode 100644 index 0000000..7c72db8 --- /dev/null +++ b/libs.txt @@ -0,0 +1,2 @@ +https://github.com/focalintent/fastled-sparkcore.git FastLED +https://github.com/geeksville/ParticleWebLog ParticleWebLog