From 1a3d6f39d25785b61b74baf5f0b3ca5c55961a94 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 19 Jan 2024 15:54:08 -0800 Subject: [PATCH 1/3] Huge structure refactor --- Button.cpp | 250 ------- Button.h | 111 --- ColorConstants.h | 161 ---- Colorset.cpp | 358 --------- Colorset.h | 132 ---- Colortypes.cpp | 409 ---------- Colortypes.h | 93 --- Helios.cpp | 782 -------------------- Helios.h | 106 --- HeliosCLI/Makefile | 6 +- HeliosConfig.h | 115 --- Makefile => HeliosEmbedded/Makefile | 6 +- avrdude.conf => HeliosEmbedded/avrdude.conf | 0 avrsize.sh => HeliosEmbedded/avrsize.sh | 0 main.cpp => HeliosEmbedded/main.cpp | 0 HeliosVortex.atsln | 22 - HeliosVortex.componentinfo.xml | 86 --- HeliosVortex.cppproj | 260 ------- HeliosVortex.ino | 11 - Led.cpp | 141 ---- Led.h | 57 -- Pattern.cpp | 294 -------- Pattern.h | 140 ---- Patterns.cpp | 221 ------ Patterns.h | 78 -- Random.cpp | 45 -- Random.h | 20 - Storage.cpp | 204 ----- Storage.h | 42 -- TimeControl.cpp | 165 ----- TimeControl.h | 59 -- Timer.cpp | 59 -- Timer.h | 30 - Timings.h | 45 -- 34 files changed, 6 insertions(+), 4502 deletions(-) delete mode 100644 Button.cpp delete mode 100644 Button.h delete mode 100644 ColorConstants.h delete mode 100644 Colorset.cpp delete mode 100644 Colorset.h delete mode 100644 Colortypes.cpp delete mode 100644 Colortypes.h delete mode 100644 Helios.cpp delete mode 100644 Helios.h delete mode 100644 HeliosConfig.h rename Makefile => HeliosEmbedded/Makefile (93%) rename avrdude.conf => HeliosEmbedded/avrdude.conf (100%) rename avrsize.sh => HeliosEmbedded/avrsize.sh (100%) rename main.cpp => HeliosEmbedded/main.cpp (100%) delete mode 100644 HeliosVortex.atsln delete mode 100644 HeliosVortex.componentinfo.xml delete mode 100644 HeliosVortex.cppproj delete mode 100644 HeliosVortex.ino delete mode 100644 Led.cpp delete mode 100644 Led.h delete mode 100644 Pattern.cpp delete mode 100644 Pattern.h delete mode 100644 Patterns.cpp delete mode 100644 Patterns.h delete mode 100644 Random.cpp delete mode 100644 Random.h delete mode 100644 Storage.cpp delete mode 100644 Storage.h delete mode 100644 TimeControl.cpp delete mode 100644 TimeControl.h delete mode 100644 Timer.cpp delete mode 100644 Timer.h delete mode 100644 Timings.h diff --git a/Button.cpp b/Button.cpp deleted file mode 100644 index 5ee6c4e1..00000000 --- a/Button.cpp +++ /dev/null @@ -1,250 +0,0 @@ -#include "Button.h" -#include "TimeControl.h" - -#ifdef HELIOS_EMBEDDED -#include -#include -#ifdef HELIOS_ARDUINO -#include -#endif -#define BUTTON_PIN 3 -#define BUTTON_PORT 2 -#endif - -#include "Helios.h" - -// static members of Button -uint32_t Button::m_pressTime = 0; -uint32_t Button::m_releaseTime = 0; -uint32_t Button::m_holdDuration = 0; -uint32_t Button::m_releaseDuration = 0; -uint8_t Button::m_releaseCount = 0; -bool Button::m_buttonState = false; -bool Button::m_newPress = false; -bool Button::m_newRelease = false; -bool Button::m_isPressed = false; -bool Button::m_shortClick = false; -bool Button::m_longClick = false; - -#ifdef HELIOS_CLI -// an input queue for the button, each tick one even is processed -// out of this queue and used to produce input -std::queue Button::m_inputQueue; -// the virtual pin state -bool Button::m_pinState = false; -// whether the button is waiting to wake the device -bool Button::m_enableWake = false; -#endif - -// initialize a new button object with a pin number -bool Button::init() -{ - m_pressTime = 0; - m_releaseTime = 0; - m_holdDuration = 0; - m_releaseDuration = 0; - m_newPress = false; - m_newRelease = false; - m_shortClick = false; - m_longClick = false; - m_buttonState = check(); - m_releaseCount = !m_buttonState; - m_isPressed = m_buttonState; -#ifdef HELIOS_CLI - m_pinState = false; - m_enableWake = false; -#endif -#ifdef HELIOS_EMBEDDED -#ifdef HELIOS_ARDUINO - pinMode(3, INPUT); -#else - // turn off wake - PCMSK &= ~(1 << PCINT3); - GIMSK &= ~(1 << PCIE); -#endif -#endif - return true; -} - -// enable wake on press -void Button::enableWake() -{ -#ifdef HELIOS_EMBEDDED - // Configure INT0 to trigger on falling edge - PCMSK |= (1 << PCINT3); - GIMSK |= (1 << PCIE); - sei(); -#else // HELIOS_CLI - m_enableWake = false; -#endif -} - -#ifdef HELIOS_EMBEDDED -ISR(PCINT0_vect) { - PCMSK &= ~(1 << PCINT3); - GIMSK &= ~(1 << PCIE); - Helios::wakeup(); -} -#endif - -// directly poll the pin for whether it's pressed right now -bool Button::check() -{ -#ifdef HELIOS_EMBEDDED -#ifdef HELIOS_ARDUINO - return digitalRead(3) == HIGH; -#else - return (PINB & (1 << 3)) != 0; -#endif -#elif defined(HELIOS_CLI) - // then just return the pin state as-is, the input event may have - // adjusted this value - return m_pinState; -#endif -} - -// poll the button pin and update the state of the button object -void Button::update() -{ -#ifdef HELIOS_CLI - // process any pre-input events in the queue - bool processed_pre = processPreInput(); -#endif - - bool newButtonState = check(); - m_newPress = false; - m_newRelease = false; - if (newButtonState != m_buttonState) { - m_buttonState = newButtonState; - m_isPressed = m_buttonState; - if (m_isPressed) { - m_pressTime = Time::getCurtime(); - m_newPress = true; - } else { - m_releaseTime = Time::getCurtime(); - m_newRelease = true; - m_releaseCount++; - } - } - if (m_isPressed) { - m_holdDuration = (Time::getCurtime() >= m_pressTime) ? (uint32_t)(Time::getCurtime() - m_pressTime) : 0; - } else { - m_releaseDuration = (Time::getCurtime() >= m_releaseTime) ? (uint32_t)(Time::getCurtime() - m_releaseTime) : 0; - } - m_shortClick = (m_newRelease && (m_holdDuration <= SHORT_CLICK_THRESHOLD)); - m_longClick = (m_newRelease && (m_holdDuration > SHORT_CLICK_THRESHOLD)); - -#ifdef HELIOS_CLI - // if there was no pre-input event this tick, process a post input event - // to ensure there is only one event per tick processed - if (!processed_pre) { - processPostInput(); - } - - if (m_enableWake) { - if (m_isPressed || m_shortClick || m_longClick) { - Helios::wakeup(); - } - } -#endif -} - -#ifdef HELIOS_CLI -bool Button::processPreInput() -{ - if (!m_inputQueue.size()) { - return false; - } - char command = m_inputQueue.front(); - switch (command) { - case 'p': // press - Button::doPress(); - break; - case 'r': // release - Button::doRelease(); - break; - case 't': // toggle - Button::doToggle(); - break; - case 'q': // quit - Helios::terminate(); - break; - case 'w': // wait - // wait is pre input I guess - break; - default: - // return here! do not pop the queue - // do not process post input events - return false; - } - // now pop whatever pre-input command was processed - m_inputQueue.pop(); - return true; -} - -bool Button::processPostInput() -{ - if (!m_inputQueue.size()) { - // probably processed the pre-input event already - return false; - } - // process input queue from the command line - char command = m_inputQueue.front(); - switch (command) { - case 'c': // click button - Button::doShortClick(); - break; - case 'l': // long click button - Button::doLongClick(); - break; - default: - // should never happen - return false; - } - m_inputQueue.pop(); - return true; -} - -void Button::doShortClick() -{ - m_shortClick = true; - m_releaseCount++; -} - -void Button::doLongClick() -{ - m_longClick = true; - m_releaseCount++; -} - -// this will actually press down the button, it's your responsibility to wait -// for the appropriate number of ticks and then release the button -void Button::doPress() -{ - m_pinState = true; -} - -void Button::doRelease() -{ - m_pinState = false; -} - -void Button::doToggle() -{ - m_pinState = !m_pinState; -} - -// queue up an input event for the button -void Button::queueInput(char input) -{ - m_inputQueue.push(input); -} - -uint32_t Button::inputQueueSize() -{ - return m_inputQueue.size(); -} -#endif - -// global button -Button button; diff --git a/Button.h b/Button.h deleted file mode 100644 index 600b9748..00000000 --- a/Button.h +++ /dev/null @@ -1,111 +0,0 @@ -#include - -#ifdef HELIOS_CLI -#include -#endif - -class Button -{ -public: - // initialize a new button object with a pin number - static bool init(); - // directly poll the pin for whether it's pressed right now - static bool check(); - // poll the button pin and update the state of the button object - static void update(); - - // whether the button was pressed this tick - static bool onPress() { return m_newPress; } - // whether the button was released this tick - static bool onRelease() { return m_newRelease; } - // whether the button is currently pressed - static bool isPressed() { return m_isPressed; } - - // whether the button was shortclicked this tick - static bool onShortClick() { return m_shortClick; } - // whether the button was long clicked this tick - static bool onLongClick() { return m_longClick; } - - // when the button was last pressed - static uint32_t pressTime() { return m_pressTime; } - // when the button was last released - static uint32_t releaseTime() { return m_releaseTime; } - - // how long the button is currently or was last held down (in ticks) - static uint32_t holdDuration() { return m_holdDuration; } - // how long the button is currently or was last released for (in ticks) - static uint32_t releaseDuration() { return m_releaseDuration; } - - // the number of releases - static uint8_t releaseCount() { return m_releaseCount; } - - // enable wake on press - static void enableWake(); - -#ifdef HELIOS_CLI - // these will 'inject' a short/long click without actually touching the - // button state, it's important that code uses 'onShortClick' or - // 'onLongClick' to capture this injected input event. Code that uses - // for example: 'button.holdDuration() >= threshold && button.onRelease()' - // will never trigger because the injected input event doesn't actually - // press the button or change the button state it just sets the 'shortClick' - // or 'longClick' values accordingly - static void doShortClick(); - static void doLongClick(); - - // this will actually press down the button, it's your responsibility to wait - // for the appropriate number of ticks and then release the button - static void doPress(); - static void doRelease(); - static void doToggle(); - - // queue up an input event for the button - static void queueInput(char input); - static uint32_t inputQueueSize(); -#endif - -private: - // ======================================== - // state data that is populated each check - - // the timestamp of when the button was pressed - static uint32_t m_pressTime; - // the timestamp of when the button was released - static uint32_t m_releaseTime; - - // the last hold duration - static uint32_t m_holdDuration; - // the last release duration - static uint32_t m_releaseDuration; - - // the number of times released, will overflow at 255 - static uint8_t m_releaseCount; - - // the active state of the button - static bool m_buttonState; - - // whether pressed this tick - static bool m_newPress; - // whether released this tick - static bool m_newRelease; - // whether currently pressed - static bool m_isPressed; - // whether a short click occurred - static bool m_shortClick; - // whether a long click occurred - static bool m_longClick; - -#ifdef HELIOS_CLI - // process pre or post input events from the queue - static bool processPreInput(); - static bool processPostInput(); - - // an input queue for the button, each tick one even is processed - // out of this queue and used to produce input - static std::queue m_inputQueue; - // the virtual pin state that is polled instead of a digital pin - static bool m_pinState; - // whether the button is waiting to wake the device - static bool m_enableWake; -#endif -}; diff --git a/ColorConstants.h b/ColorConstants.h deleted file mode 100644 index 8801d4ac..00000000 --- a/ColorConstants.h +++ /dev/null @@ -1,161 +0,0 @@ -#pragma once - -#include - -// ==================================================================================================== -// Color Constants -// -// This file defines constants for colors as we best see them for the Helios -// Engine - -// Pre-defined hue values -#define HUE_RED 0 -#define HUE_CORAL_ORANGE 5 -#define HUE_ORANGE 10 -#define HUE_YELLOW 20 - -#define HUE_LIME_GREEN 70 -#define HUE_GREEN 85 -#define HUE_SEAFOAM 95 -#define HUE_TURQUOISE 120 - -#define HUE_ICE_BLUE 142 -#define HUE_LIGHT_BLUE 158 -#define HUE_BLUE 170 -#define HUE_ROYAL_BLUE 175 - -#define HUE_PURPLE 192 -#define HUE_PINK 205 -#define HUE_HOT_PINK 225 -#define HUE_MAGENTA 245 - -// Helios Colors -#define RGB_OFF (uint32_t)0x000000 // 0 0 0 -#define RGB_WHITE (uint32_t)0xFFFFFF // 255 255 255 -#define RGB_RED (uint32_t)0xFF0000 // 255, 0, 0 -#define RGB_CORAL_ORANGE (uint32_t)0xFF1E00 // 255, 30, 0 -#define RGB_ORANGE (uint32_t)0xFF3C00 // 255, 60, 0 -#define RGB_YELLOW (uint32_t)0xFF7800 // 255, 120, 0 -#define RGB_LIME_GREEN (uint32_t)0x59FF00 // 89, 255, 0 -#define RGB_GREEN (uint32_t)0x00FF00 // 0, 255, 0 -#define RGB_SEAFOAM (uint32_t)0x00FF3C // 0, 255, 60 -#define RGB_TURQUOISE (uint32_t)0x00FFD1 // 0, 255, 209 -#define RGB_ICE_BLUE (uint32_t)0x00A7FF // 0, 167, 255 -#define RGB_LIGHT_BLUE (uint32_t)0x0047FF // 0, 71, 255 -#define RGB_BLUE (uint32_t)0x0000FF // 0, 0, 255 -#define RGB_ROYAL_BLUE (uint32_t)0x1D00FF // 29, 0, 255 -#define RGB_PURPLE (uint32_t)0x8300FF // 131, 0, 255 -#define RGB_PINK (uint32_t)0xD200FF // 210, 0, 255 -#define RGB_HOT_PINK (uint32_t)0xFF00B4 // 255, 0, 180 -#define RGB_MAGENTA (uint32_t)0xFF003C // 255, 0, 60 - -// Helios Medium Brightness Colors -#define RGB_WHITE_BRI_MEDIUM (uint32_t)0x787878 // 120 120 120 -#define RGB_RED_BRI_MEDIUM (uint32_t)0x780000 // 120, 0, 0 -#define RGB_CORAL_ORANGE_BRI_MEDIUM (uint32_t)0x780E00 // 120, 14, 0 -#define RGB_ORANGE_BRI_MEDIUM (uint32_t)0x781C00 // 120, 28, 0 -#define RGB_YELLOW_BRI_MEDIUM (uint32_t)0x783800 // 120, 56, 0 -#define RGB_LIME_GREEN_BRI_MEDIUM (uint32_t)0x297800 // 41, 120, 0 -#define RGB_GREEN_BRI_MEDIUM (uint32_t)0x007800 // 0, 120, 0 -#define RGB_SEAFOAM_BRI_MEDIUM (uint32_t)0x00781C // 0, 120, 28 -#define RGB_TURQUOISE_BRI_MEDIUM (uint32_t)0x007862 // 0, 120, 98 -#define RGB_ICE_BLUE_BRI_MEDIUM (uint32_t)0x004E78 // 0, 78, 120 -#define RGB_LIGHT_BLUE_BRI_MEDIUM (uint32_t)0x002178 // 0, 33, 120 -#define RGB_BLUE_BRI_MEDIUM (uint32_t)0x000078 // 0, 0, 120 -#define RGB_ROYAL_BLUE_BRI_MEDIUM (uint32_t)0x0D0078 // 13, 0, 120 -#define RGB_PURPLE_BRI_MEDIUM (uint32_t)0x3D0078 // 61, 0, 120 -#define RGB_PINK_BRI_MEDIUM (uint32_t)0x620078 // 98, 0, 120 -#define RGB_HOT_PINK_BRI_MEDIUM (uint32_t)0x780054 // 120, 0, 84 -#define RGB_MAGENTA_BRI_MEDIUM (uint32_t)0x78001C // 120, 0, 28 - -// Helios Low Brightness Colors -#define RGB_WHITE_BRI_LOW (uint32_t)0x3C3C3C // 60 60 60 -#define RGB_RED_BRI_LOW (uint32_t)0x3C0000 // 60, 0, 0 -#define RGB_CORAL_ORANGE_BRI_LOW (uint32_t)0x3C0700 // 60, 7, 0 -#define RGB_ORANGE_BRI_LOW (uint32_t)0x3C0E00 // 60, 14, 0 -#define RGB_YELLOW_BRI_LOW (uint32_t)0x3C1C00 // 60, 28, 0 -#define RGB_LIME_GREEN_BRI_LOW (uint32_t)0x143C00 // 20, 60, 0 -#define RGB_GREEN_BRI_LOW (uint32_t)0x003C00 // 0, 60, 0 -#define RGB_SEAFOAM_BRI_LOW (uint32_t)0x003C0E // 0, 60, 14 -#define RGB_TURQUOISE_BRI_LOW (uint32_t)0x003C31 // 0, 60, 49 -#define RGB_ICE_BLUE_BRI_LOW (uint32_t)0x00273C // 0, 39, 60 -#define RGB_LIGHT_BLUE_BRI_LOW (uint32_t)0x00103C // 0, 16, 60 -#define RGB_BLUE_BRI_LOW (uint32_t)0x00003C // 0, 0, 60 -#define RGB_ROYAL_BLUE_BRI_LOW (uint32_t)0x06003C // 6, 0, 60 -#define RGB_PURPLE_BRI_LOW (uint32_t)0x1E003C // 30, 0, 60 -#define RGB_PINK_BRI_LOW (uint32_t)0x31003C // 49, 0, 60 -#define RGB_HOT_PINK_BRI_LOW (uint32_t)0x3C002A // 60, 0, 42 -#define RGB_MAGENTA_BRI_LOW (uint32_t)0x3C000E // 60, 0, 14 - -// Helios Lowest Brightness Colors -#define RGB_WHITE_BRI_LOWEST (uint32_t)0x0A0A0A // 10 10 10 -#define RGB_RED_BRI_LOWEST (uint32_t)0x0A0000 // 10, 0, 0 -#define RGB_CORAL_ORANGE_BRI_LOWEST (uint32_t)0x0A0100 // 10, 1, 0 -#define RGB_ORANGE_BRI_LOWEST (uint32_t)0x0A0200 // 10, 2, 0 -#define RGB_YELLOW_BRI_LOWEST (uint32_t)0x0A0400 // 10, 4, 0 -#define RGB_LIME_GREEN_BRI_LOWEST (uint32_t)0x030A00 // 3, 10, 0 -#define RGB_GREEN_BRI_LOWEST (uint32_t)0x000A00 // 0, 10, 0 -#define RGB_SEAFOAM_BRI_LOWEST (uint32_t)0x000A02 // 0, 10, 2 -#define RGB_TURQUOISE_BRI_LOWEST (uint32_t)0x000A08 // 0, 10, 8 -#define RGB_ICE_BLUE_BRI_LOWEST (uint32_t)0x00060A // 0, 6, 10 -#define RGB_LIGHT_BLUE_BRI_LOWEST (uint32_t)0x00020A // 0, 2, 10 -#define RGB_BLUE_BRI_LOWEST (uint32_t)0x00000A // 0, 0, 10 -#define RGB_ROYAL_BLUE_BRI_LOWEST (uint32_t)0x01000A // 1, 0, 10 -#define RGB_PURPLE_BRI_LOWEST (uint32_t)0x05000A // 5, 0, 10 -#define RGB_PINK_BRI_LOWEST (uint32_t)0x08000A // 8, 0, 10 -#define RGB_HOT_PINK_BRI_LOWEST (uint32_t)0x0A0007 // 10, 0, 7 -#define RGB_MAGENTA_BRI_LOWEST (uint32_t)0x0A0002 // 10, 0, 2 - -// Helios Medium Saturation Colors -#define RGB_RED_SAT_MEDIUM (uint32_t)0xFF2222 // 255, 34, 34 -#define RGB_CORAL_ORANGE_SAT_MEDIUM (uint32_t)0xFF3C22 // 255, 60, 34 -#define RGB_ORANGE_SAT_MEDIUM (uint32_t)0xFF5622 // 255, 86, 34 -#define RGB_YELLOW_SAT_MEDIUM (uint32_t)0xFF8A22 // 255, 138, 34 -#define RGB_LIME_GREEN_SAT_MEDIUM (uint32_t)0x6FFF22 // 111, 255, 34 -#define RGB_GREEN_SAT_MEDIUM (uint32_t)0x22FF22 // 34, 255, 34 -#define RGB_SEAFOAM_SAT_MEDIUM (uint32_t)0x22FF56 // 34, 255, 86 -#define RGB_TURQUOISE_SAT_MEDIUM (uint32_t)0x22FFD7 // 34, 255, 215 -#define RGB_ICE_BLUE_SAT_MEDIUM (uint32_t)0x22B3FF // 34, 179, 255 -#define RGB_LIGHT_BLUE_SAT_MEDIUM (uint32_t)0x2260FF // 34, 96, 255 -#define RGB_BLUE_SAT_MEDIUM (uint32_t)0x2222FF // 34, 34, 255 -#define RGB_ROYAL_BLUE_SAT_MEDIUM (uint32_t)0x3C22FF // 60, 34, 255 -#define RGB_PURPLE_SAT_MEDIUM (uint32_t)0x9422FF // 148, 34, 255 -#define RGB_PINK_SAT_MEDIUM (uint32_t)0xD822FF // 216, 34, 255 -#define RGB_HOT_PINK_SAT_MEDIUM (uint32_t)0xFF22BE // 255, 34, 190 -#define RGB_MAGENTA_SAT_MEDIUM (uint32_t)0xFF2256 // 255, 34, 86 - -// Helios Low Saturation Colors -#define RGB_RED_SAT_LOW (uint32_t)0xFF5555 // 255, 85, 85 -#define RGB_CORAL_ORANGE_SAT_LOW (uint32_t)0xFF6955 // 255, 105, 85 -#define RGB_ORANGE_SAT_LOW (uint32_t)0xFF7D55 // 255, 125, 85 -#define RGB_YELLOW_SAT_LOW (uint32_t)0xFFA555 // 255, 165, 85 -#define RGB_LIME_GREEN_SAT_LOW (uint32_t)0x90FF55 // 144, 255, 85 -#define RGB_GREEN_SAT_LOW (uint32_t)0x55FF55 // 85, 255, 85 -#define RGB_SEAFOAM_SAT_LOW (uint32_t)0x55FF7D // 85, 255, 125 -#define RGB_TURQUOISE_SAT_LOW (uint32_t)0x55FFE0 // 85, 255, 224 -#define RGB_ICE_BLUE_SAT_LOW (uint32_t)0x55C4FF // 85, 196, 255 -#define RGB_LIGHT_BLUE_SAT_LOW (uint32_t)0x5584FF // 85, 132, 255 -#define RGB_BLUE_SAT_LOW (uint32_t)0x5555FF // 85, 85, 255 -#define RGB_ROYAL_BLUE_SAT_LOW (uint32_t)0x6855FF // 104, 85, 255 -#define RGB_PURPLE_SAT_LOW (uint32_t)0xAC55FF // 172, 85, 255 -#define RGB_PINK_SAT_LOW (uint32_t)0xE055FF // 224, 85, 255 -#define RGB_HOT_PINK_SAT_LOW (uint32_t)0xFF55CD // 255, 85, 205 -#define RGB_MAGENTA_SAT_LOW (uint32_t)0xFF557D // 255, 85, 125 - -// Helios Lowest Saturation Colors -#define RGB_RED_SAT_LOWEST (uint32_t)0xFF7D7D // 255, 125, 125 -#define RGB_CORAL_ORANGE_SAT_LOWEST (uint32_t)0xFF8C7D // 255, 140, 125 -#define RGB_ORANGE_SAT_LOWEST (uint32_t)0xFF9B7D // 255, 155, 125 -#define RGB_YELLOW_SAT_LOWEST (uint32_t)0xFFBA7D // 255, 186, 125 -#define RGB_LIME_GREEN_SAT_LOWEST (uint32_t)0xAAFF7D // 170, 255, 125 -#define RGB_GREEN_SAT_LOWEST (uint32_t)0x7DFF7D // 125, 255, 125 -#define RGB_SEAFOAM_SAT_LOWEST (uint32_t)0x7DFF9B // 125, 255, 155 -#define RGB_TURQUOISE_SAT_LOWEST (uint32_t)0x7DFFE7 // 125, 255, 231 -#define RGB_ICE_BLUE_SAT_LOWEST (uint32_t)0x7DD2FF // 125, 210, 255 -#define RGB_LIGHT_BLUE_SAT_LOWEST (uint32_t)0x7DA1FF // 125, 161, 255 -#define RGB_BLUE_SAT_LOWEST (uint32_t)0x7D7DFF // 125, 125, 255 -#define RGB_ROYAL_BLUE_SAT_LOWEST (uint32_t)0x8B7DFF // 139, 125, 255 -#define RGB_PURPLE_SAT_LOWEST (uint32_t)0xBF7DFF // 191, 125, 255 -#define RGB_PINK_SAT_LOWEST (uint32_t)0xE87DFF // 232, 125, 255 -#define RGB_HOT_PINK_SAT_LOWEST (uint32_t)0xFF7DD8 // 255, 125, 216 -#define RGB_MAGENTA_SAT_LOWEST (uint32_t)0xFF7D9B // 255, 125, 155 diff --git a/Colorset.cpp b/Colorset.cpp deleted file mode 100644 index e61e83ec..00000000 --- a/Colorset.cpp +++ /dev/null @@ -1,358 +0,0 @@ -#include "Colorset.h" - -#include "Random.h" - -#include - -// when no color is selected in the colorset the index is this -// then when you call getNext() for the first time it returns -// the 0th color in the colorset and after the index will be 0 -#define INDEX_NONE 255 - -Colorset::Colorset() : - m_palette(), - m_numColors(0), - m_curIndex(INDEX_NONE) -{ - init(); -} - -Colorset::Colorset(RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, - RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8) : - Colorset() -{ - init(c1, c2, c3, c4, c5, c6, c7, c8); -} - -Colorset::Colorset(uint8_t numCols, const uint32_t *cols) : - Colorset() -{ - if (numCols > NUM_COLOR_SLOTS) { - numCols = NUM_COLOR_SLOTS; - } - for (uint8_t i = 0; i < numCols; ++i) { - addColor(RGBColor(cols[i])); - } -} - -Colorset::Colorset(const Colorset &other) : - Colorset() -{ - // invoke = operator - *this = other; -} - -Colorset::~Colorset() -{ - clear(); -} - -bool Colorset::operator==(const Colorset &other) const -{ - // only compare the palettes for equality - return (m_numColors == other.m_numColors) && - (memcmp(m_palette, other.m_palette, m_numColors * sizeof(RGBColor)) == 0); -} - -bool Colorset::operator!=(const Colorset &other) const -{ - return !operator==(other); -} - -void Colorset::init(RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, - RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8) -{ - // clear any existing colors - clear(); - // would be nice if we could do this another way - if (!c1.empty()) addColor(c1); - if (!c2.empty()) addColor(c2); - if (!c3.empty()) addColor(c3); - if (!c4.empty()) addColor(c4); - if (!c5.empty()) addColor(c5); - if (!c6.empty()) addColor(c6); - if (!c7.empty()) addColor(c7); - if (!c8.empty()) addColor(c8); -} - -void Colorset::clear() -{ - memset((void *)m_palette, 0, sizeof(m_palette)); - m_numColors = 0; - resetIndex(); -} - -bool Colorset::equals(const Colorset &set) const -{ - return operator==(set); -} - -bool Colorset::equals(const Colorset *set) const -{ - if (!set) { - return false; - } - return operator==(*set); -} - -RGBColor Colorset::operator[](int index) const -{ - return get(index); -} - -// add a single color -bool Colorset::addColor(RGBColor col) -{ - if (m_numColors >= NUM_COLOR_SLOTS) { - return false; - } - // insert new color and increment number of colors - m_palette[m_numColors] = col; - m_numColors++; - return true; -} - -bool Colorset::addColorHSV(uint8_t hue, uint8_t sat, uint8_t val) -{ - return addColor(HSVColor(hue, sat, val)); -} - -void Colorset::addColorWithValueStyle(Random &ctx, uint8_t hue, uint8_t sat, ValueStyle valStyle, uint8_t numColors, uint8_t colorPos) -{ - if (numColors == 1) { - addColorHSV(hue, sat, ctx.next8(16, 255)); - return; - } - switch (valStyle) { - default: - case VAL_STYLE_RANDOM: - addColorHSV(hue, sat, 85 * ctx.next8(1, 4)); - break; - case VAL_STYLE_LOW_FIRST_COLOR: - if (m_numColors == 0) { - addColorHSV(hue, sat, ctx.next8(0, 86)); - } else { - addColorHSV(hue, sat, 85 * ctx.next8(1, 4)); - } - break; - case VAL_STYLE_HIGH_FIRST_COLOR: - if (m_numColors == 0) { - addColorHSV(hue, sat, 255); - } else { - addColorHSV(hue, sat, ctx.next8(0, 86)); - } - break; - case VAL_STYLE_ALTERNATING: - if (m_numColors % 2 == 0) { - addColorHSV(hue, sat, 255); - } else { - addColorHSV(hue, sat, 85); - } - break; - case VAL_STYLE_ASCENDING: - addColorHSV(hue, sat, (colorPos + 1) * (255 / numColors)); - break; - case VAL_STYLE_DESCENDING: - addColorHSV(hue, sat, 255 - (colorPos * (255 / numColors))); - break; - case VAL_STYLE_CONSTANT: - addColorHSV(hue, sat, 255); - } -} - -void Colorset::removeColor(uint8_t index) -{ - if (index >= m_numColors) { - return; - } - for (uint8_t i = index; i < (m_numColors - 1); ++i) { - m_palette[i] = m_palette[i + 1]; - } - m_palette[--m_numColors].clear(); -} - -uint8_t current_color_mode = Colorset::THEORY; - -void Colorset::randomizeColors(Random &ctx, uint8_t numColors) -{ - current_color_mode = (current_color_mode + 1) % COLOR_MODE_COUNT; - ColorMode mode = (ColorMode)current_color_mode; - clear(); - if (!numColors) { - numColors = ctx.next8(mode == MONOCHROMATIC ? 2 : 1, 9); - } - uint8_t randomizedHue = ctx.next8(); - uint8_t colorGap = 0; - if (mode == THEORY && numColors > 1) { - colorGap = ctx.next8(16, 256 / (numColors - 1)); - } - ValueStyle valStyle = (ValueStyle)ctx.next8(0, VAL_STYLE_COUNT); - // the doubleStyle decides if some colors are added to the set twice - uint8_t doubleStyle = 0; - if (numColors <= 7) { - doubleStyle = (ctx.next8(0, 1)); - } - if (numColors <= 4) { - doubleStyle = (ctx.next8(0, 2)); - } - for (uint8_t i = 0; i < numColors; i++) { - uint8_t hueToUse; - uint8_t valueToUse = 255; - if (mode == THEORY) { - hueToUse = (randomizedHue + (i * colorGap)); - } else if (mode == MONOCHROMATIC) { - hueToUse = randomizedHue; - valueToUse = 255 - (i * (256 / numColors)); - } else { // EVENLY_SPACED - hueToUse = (randomizedHue + (256 / numColors) * i); - } - addColorWithValueStyle(ctx, hueToUse, valueToUse, valStyle, numColors, i); - // double all colors or only first color - if (doubleStyle == 2 || (doubleStyle == 1 && !i)) { - addColorWithValueStyle(ctx, hueToUse, valueToUse, valStyle, numColors, i); - } - } -} - -void Colorset::adjustBrightness(uint8_t fadeby) -{ - for (uint8_t i = 0; i < m_numColors; ++i) { - m_palette[i].adjustBrightness(fadeby); - } -} - -// get a color from the colorset -RGBColor Colorset::get(uint8_t index) const -{ - if (index >= m_numColors) { - return RGBColor(0, 0, 0); - } - return m_palette[index]; -} - -// set an rgb color in a slot, or add a new color if you specify -// a slot higher than the number of colors in the colorset -void Colorset::set(uint8_t index, RGBColor col) -{ - // special case for 'setting' a color at the edge of the palette, - // ie adding a new color when you set an index higher than the max - if (index >= m_numColors) { - if (!addColor(col)) { - //ERROR_LOGF("Failed to add new color at index %u", index); - } - return; - } - m_palette[index] = col; -} - -// skip some amount of colors -void Colorset::skip(int32_t amount) -{ - if (!m_numColors) { - return; - } - // if the colorset hasn't started yet - if (m_curIndex == INDEX_NONE) { - m_curIndex = 0; - } - - // first modulate the amount to skip to be within +/- the number of colors - amount %= (int32_t)m_numColors; - - // max = 3 - // m_curIndex = 2 - // amount = -10 - m_curIndex = ((int32_t)m_curIndex + (int32_t)amount) % (int32_t)m_numColors; - if (m_curIndex > m_numColors) { // must have wrapped - // simply wrap it back - m_curIndex += m_numColors; - } -} - -RGBColor Colorset::cur() -{ - if (m_curIndex >= m_numColors) { - return RGBColor(0, 0, 0); - } - if (m_curIndex == INDEX_NONE) { - return m_palette[0]; - } - return m_palette[m_curIndex]; -} - -void Colorset::setCurIndex(uint8_t index) -{ - if (!m_numColors) { - return; - } - if (index > (m_numColors - 1)) { - return; - } - m_curIndex = index; -} - -void Colorset::resetIndex() -{ - m_curIndex = INDEX_NONE; -} - -RGBColor Colorset::getPrev() -{ - if (!m_numColors) { - return RGB_OFF; - } - // handle wrapping at 0 - if (m_curIndex == 0 || m_curIndex == INDEX_NONE) { - m_curIndex = numColors() - 1; - } else { - m_curIndex--; - } - // return the color - return m_palette[m_curIndex]; -} - -RGBColor Colorset::getNext() -{ - if (!m_numColors) { - return RGB_OFF; - } - // iterate current index, let it wrap at max uint8 - m_curIndex++; - // then modulate the result within max colors - m_curIndex %= numColors(); - // return the color - return m_palette[m_curIndex]; -} - -// peek at the next color but don't iterate -RGBColor Colorset::peek(int32_t offset) const -{ - if (!m_numColors) { - return RGB_OFF; - } - uint8_t nextIndex = 0; - // get index of the next color - if (offset >= 0) { - nextIndex = (m_curIndex + offset) % numColors(); - } else { - if (offset < -1 * (int32_t)(numColors())) { - return RGB_OFF; - } - nextIndex = ((m_curIndex + numColors()) + (int)offset) % numColors(); - } - // return the color - return m_palette[nextIndex]; -} - -bool Colorset::onStart() const -{ - return (m_curIndex == 0); -} - -bool Colorset::onEnd() const -{ - if (!m_numColors) { - return false; - } - return (m_curIndex == m_numColors - 1); -} diff --git a/Colorset.h b/Colorset.h deleted file mode 100644 index 0cab1c0c..00000000 --- a/Colorset.h +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef COLORSET_H -#define COLORSET_H - -#include "Colortypes.h" - -#include "HeliosConfig.h" - -class Random; - -class Colorset -{ -public: - // empty colorset - Colorset(); - // constructor for 1-8 color slots - Colorset(RGBColor c1, RGBColor c2 = RGB_OFF, RGBColor c3 = RGB_OFF, - RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, RGBColor c6 = RGB_OFF, - RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); - Colorset(uint8_t numCols, const uint32_t *cols); - ~Colorset(); - - // copy and assignment operators - Colorset(const Colorset &other); - - // equality operators - bool operator==(const Colorset &other) const; - bool operator!=(const Colorset &other) const; - - // initialize the colorset - void init(RGBColor c1 = RGB_OFF, RGBColor c2 = RGB_OFF, RGBColor c3 = RGB_OFF, - RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, RGBColor c6 = RGB_OFF, - RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); - - // clear the colorset - void clear(); - - // pointer comparison - bool equals(const Colorset &set) const; - bool equals(const Colorset *set) const; - - // index operator to access color index - RGBColor operator[](int index) const; - - enum ValueStyle : uint8_t - { - // Random values - VAL_STYLE_RANDOM = 0, - // First color low value, the rest are random - VAL_STYLE_LOW_FIRST_COLOR, - // First color high value, the rest are low - VAL_STYLE_HIGH_FIRST_COLOR, - // Alternat between high and low value - VAL_STYLE_ALTERNATING, - // Ascending values from low to high - VAL_STYLE_ASCENDING, - // Descending values from high to low - VAL_STYLE_DESCENDING, - // Constant value - VAL_STYLE_CONSTANT, - // Total number of value styles - VAL_STYLE_COUNT - }; - - // add a single color - bool addColor(RGBColor col); - bool addColorHSV(uint8_t hue, uint8_t sat, uint8_t val); - void addColorWithValueStyle(Random &ctx, uint8_t hue, uint8_t sat, - ValueStyle valStyle, uint8_t numColors, uint8_t colorPos); - void removeColor(uint8_t index); - - - // function to randomize the colors with various different modes of randomization - enum ColorMode { - THEORY, - MONOCHROMATIC, - EVENLY_SPACED, - COLOR_MODE_COUNT - }; - void randomizeColors(Random &ctx, uint8_t numColors); - - // fade all of the colors in the set - void adjustBrightness(uint8_t fadeby); - - // get a color from the colorset - RGBColor get(uint8_t index = 0) const; - - // set an rgb color in a slot, or add a new color if you specify - // a slot higher than the number of colors in the colorset - void set(uint8_t index, RGBColor col); - - // skip some amount of colors - void skip(int32_t amount = 1); - - // get current color in cycle - RGBColor cur(); - - // set the current index of the colorset - void setCurIndex(uint8_t index); - void resetIndex(); - - // the current index - uint8_t curIndex() const { return m_curIndex; } - - // get the prev color in cycle - RGBColor getPrev(); - - // get the next color in cycle - RGBColor getNext(); - - // peek at the color indexes from current but don't iterate - RGBColor peek(int32_t offset) const; - - // better wording for peek 1 ahead - RGBColor peekNext() const { return peek(1); } - - // the number of colors in the palette - uint8_t numColors() const { return m_numColors; } - - // whether the colorset is currently on the first color or last color - bool onStart() const; - bool onEnd() const; -private: - // palette of colors - RGBColor m_palette[NUM_COLOR_SLOTS]; - // the actual number of colors in the set - uint8_t m_numColors; - // the current index, starts at UINT8_MAX so that - // the very first call to getNext will iterate to 0 - uint8_t m_curIndex; -}; - -#endif diff --git a/Colortypes.cpp b/Colortypes.cpp deleted file mode 100644 index 6c965ca5..00000000 --- a/Colortypes.cpp +++ /dev/null @@ -1,409 +0,0 @@ -#include "Colortypes.h" - -#if ALTERNATIVE_HSV_RGB == 1 -// global hsv to rgb algorithm selector -hsv_to_rgb_algorithm g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; -#endif - -HSVColor::HSVColor() : - hue(0), - sat(0), - val(0) -{ -} - -HSVColor::HSVColor(uint8_t hue, uint8_t sat, uint8_t val) : - hue(hue), sat(sat), val(val) -{ -} - -HSVColor::HSVColor(uint32_t dwVal) : - HSVColor() -{ - *this = dwVal; -} - -// assignment from uint32_t -HSVColor &HSVColor::operator=(const uint32_t &rhs) -{ - hue = ((rhs >> 16) & 0xFF); - sat = ((rhs >> 8) & 0xFF); - val = (rhs & 0xFF); - return *this; -} - -// construction/assignment from RGB -HSVColor::HSVColor(const RGBColor &rhs) -{ - *this = rhs; -} - -HSVColor &HSVColor::operator=(const RGBColor &rhs) -{ - // always use generic - *this = rgb_to_hsv_generic(rhs); - return *this; -} - -bool HSVColor::operator==(const HSVColor &other) const -{ - return (other.raw() == raw()); -} - -bool HSVColor::operator!=(const HSVColor &other) const -{ - return (other.raw() != raw()); -} - -bool HSVColor::empty() const -{ - return !hue && !sat && !val; -} - -void HSVColor::clear() -{ - hue = 0; - sat = 0; - val = 0; -} - -// ========== -// RGBColor - -RGBColor::RGBColor() : - red(0), - green(0), - blue(0) -{ -} - -RGBColor::RGBColor(uint8_t red, uint8_t green, uint8_t blue) : - red(red), green(green), blue(blue) -{ -} - -RGBColor::RGBColor(uint32_t dwVal) : - RGBColor() -{ - *this = dwVal; -} - -// assignment from uint32_t -RGBColor &RGBColor::operator=(const uint32_t &rhs) -{ - red = ((rhs >> 16) & 0xFF); - green = ((rhs >> 8) & 0xFF); - blue = (rhs & 0xFF); - return *this; -} - -RGBColor::RGBColor(const HSVColor &rhs) -{ - *this = rhs; -} - -RGBColor &RGBColor::operator=(const HSVColor &rhs) -{ -#if ALTERNATIVE_HSV_RGB == 1 - switch (g_hsv_rgb_alg) { - case HSV_TO_RGB_RAINBOW: - *this = hsv_to_rgb_rainbow(rhs); - break; - case HSV_TO_RGB_GENERIC: - *this = hsv_to_rgb_generic(rhs); - break; - } -#else - *this = hsv_to_rgb_generic(rhs); -#endif - return *this; -} - -bool RGBColor::operator==(const RGBColor &other) const -{ - return (other.raw() == raw()); -} - -bool RGBColor::operator!=(const RGBColor &other) const -{ - return (other.raw() != raw()); -} - -bool RGBColor::empty() const -{ - return !red && !green && !blue; -} - -void RGBColor::clear() -{ - red = 0; - green = 0; - blue = 0; -} - -RGBColor RGBColor::adjustBrightness(uint8_t fadeBy) -{ - red = (((int)red) * (int)(256 - fadeBy)) >> 8; - green = (((int)green) * (int)(256 - fadeBy)) >> 8; - blue = (((int)blue) * (int)(256 - fadeBy)) >> 8; - return *this; -} - -// ======================================================== -// Below are various functions for converting hsv <-> rgb - -#if ALTERNATIVE_HSV_RGB == 1 -#define SCALE8(i, scale) (((uint16_t)i * (uint16_t)(scale)) >> 8) -#define FIXFRAC8(N,D) (((N)*256)/(D)) - -// Stolen from FastLED hsv to rgb full rainbox where all colours -// are given equal weight, this makes for-example yellow larger -// best to use this function as it is the legacy choice -RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs) -{ - RGBColor col; - // Yellow has a higher inherent brightness than - // any other color; 'pure' yellow is perceived to - // be 93% as bright as white. In order to make - // yellow appear the correct relative brightness, - // it has to be rendered brighter than all other - // colors. - // Level Y1 is a moderate boost, the default. - // Level Y2 is a strong boost. - const uint8_t Y1 = 1; - const uint8_t Y2 = 0; - - // G2: Whether to divide all greens by two. - // Depends GREATLY on your particular LEDs - const uint8_t G2 = 0; - - // Gscale: what to scale green down by. - // Depends GREATLY on your particular LEDs - const uint8_t Gscale = 185; - - - uint8_t hue = rhs.hue; - uint8_t sat = rhs.sat; - uint8_t val = rhs.val; - - uint8_t offset = hue & 0x1F; // 0..31 - - // offset8 = offset * 8 - uint8_t offset8 = offset; - offset8 <<= 3; - - uint8_t third = SCALE8(offset8, (256 / 3)); // max = 85 - uint8_t r, g, b; - if (!(hue & 0x80)) { - // 0XX - if (!(hue & 0x40)) { - // 00X - //section 0-1 - if (!(hue & 0x20)) { - // 000 - //case 0: // R -> O - r = 255 - third; - g = third; - b = 0; - } else { - // 001 - //case 1: // O -> Y - if (Y1) { - r = 171; - g = 85 + third; - b = 0; - } - if (Y2) { - r = 170 + third; - //uint8_t twothirds = (third << 1); - uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); // max=170 - g = 85 + twothirds; - b = 0; - } - } - } else { - //01X - // section 2-3 - if (!(hue & 0x20)) { - // 010 - //case 2: // Y -> G - if (Y1) { - //uint8_t twothirds = (third << 1); - uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); // max=170 - r = 171 - twothirds; - g = 170 + third; - b = 0; - } - if (Y2) { - r = 255 - offset8; - g = 255; - b = 0; - } - } else { - // 011 - // case 3: // G -> A - r = 0; - g = 255 - third; - b = third; - } - } - } else { - // section 4-7 - // 1XX - if (!(hue & 0x40)) { - // 10X - if (!(hue & 0x20)) { - // 100 - //case 4: // A -> B - r = 0; - //uint8_t twothirds = (third << 1); - uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); // max=170 - g = 171 - twothirds; //170? - b = 85 + twothirds; - } else { - // 101 - //case 5: // B -> P - r = third; - g = 0; - b = 255 - third; - } - } else { - if (!(hue & 0x20)) { - // 110 - //case 6: // P -- K - r = 85 + third; - g = 0; - b = 171 - third; - } else { - // 111 - //case 7: // K -> R - r = 170 + third; - g = 0; - b = 85 - third; - } - } - } - - // This is one of the good places to scale the green down, - // although the client can scale green down as well. - if (G2) g = g >> 1; - if (Gscale) g = SCALE8(g, Gscale); - - // Scale down colors if we're desaturated at all - // and add the brightness_floor to r, g, and b. - if (sat != 255) { - if (sat == 0) { - r = 255; b = 255; g = 255; - } else { - if (r) r = SCALE8(r, sat) + 1; - if (g) g = SCALE8(g, sat) + 1; - if (b) b = SCALE8(b, sat) + 1; - - uint8_t desat = 255 - sat; - desat = SCALE8(desat, desat); - - uint8_t brightness_floor = desat; - r += brightness_floor; - g += brightness_floor; - b += brightness_floor; - } - } - - // Now scale everything down if we're at value < 255. - if (val != 255) { - val = SCALE8(val, val); - if (val == 0) { - r = 0; g = 0; b = 0; - } else { - // nSCALE8x3_video( r, g, b, val); - if (r) r = SCALE8(r, val) + 1; - if (g) g = SCALE8(g, val) + 1; - if (b) b = SCALE8(b, val) + 1; - } - } - - // Here we have the old AVR "missing std X+n" problem again - // It turns out that fixing it winds up costing more than - // not fixing it. - // To paraphrase Dr Bronner, profile! profile! profile! - col.red = r; - col.green = g; - col.blue = b; - return col; -} -#endif - -// generic hsv to rgb conversion nothing special -RGBColor hsv_to_rgb_generic(const HSVColor &rhs) -{ - unsigned char region, remainder, p, q, t; - RGBColor col; - - if (rhs.sat == 0) { - col.red = rhs.val; - col.green = rhs.val; - col.blue = rhs.val; - return col; - } - - region = rhs.hue / 43; - remainder = ((rhs.hue - (region * 43)) * 6); - - // extraneous casts to uint16_t are to prevent overflow - p = (uint8_t)(((uint16_t)(rhs.val) * (255 - rhs.sat)) >> 8); - q = (uint8_t)(((uint16_t)(rhs.val) * (255 - (((uint16_t)(rhs.sat) * remainder) >> 8))) >> 8); - t = (uint8_t)(((uint16_t)(rhs.val) * (255 - (((uint16_t)(rhs.sat) * (255 - remainder)) >> 8))) >> 8); - - switch (region) { - case 0: - col.red = rhs.val; col.green = t; col.blue = p; - break; - case 1: - col.red = q; col.green = rhs.val; col.blue = p; - break; - case 2: - col.red = p; col.green = rhs.val; col.blue = t; - break; - case 3: - col.red = p; col.green = q; col.blue = rhs.val; - break; - case 4: - col.red = t; col.green = p; col.blue = rhs.val; - break; - default: - col.red = rhs.val; col.green = p; col.blue = q; - break; - } - return col; -} - -// Convert rgb to hsv with generic fast method -HSVColor rgb_to_hsv_generic(const RGBColor &rhs) -{ - unsigned char rgbMin, rgbMax; - rgbMin = rhs.red < rhs.green ? (rhs.red < rhs.blue ? rhs.red : rhs.blue) : (rhs.green < rhs.blue ? rhs.green : rhs.blue); - rgbMax = rhs.red > rhs.green ? (rhs.red > rhs.blue ? rhs.red : rhs.blue) : (rhs.green > rhs.blue ? rhs.green : rhs.blue); - HSVColor hsv; - - hsv.val = rgbMax; - if (hsv.val == 0) { - hsv.hue = 0; - hsv.sat = 0; - return hsv; - } - - hsv.sat = 255 * (long)(rgbMax - rgbMin) / hsv.val; - if (hsv.sat == 0) { - hsv.hue = 0; - return hsv; - } - - if (rgbMax == rhs.red) { - hsv.hue = 0 + 43 * (rhs.green - rhs.blue) / (rgbMax - rgbMin); - } else if (rgbMax == rhs.green) { - hsv.hue = 85 + 43 * (rhs.blue - rhs.red) / (rgbMax - rgbMin); - } else { - hsv.hue = 171 + 43 * (rhs.red - rhs.green) / (rgbMax - rgbMin); - } - return hsv; -} diff --git a/Colortypes.h b/Colortypes.h deleted file mode 100644 index bd088cc6..00000000 --- a/Colortypes.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef COLOR_H -#define COLOR_H - -#include - -#include "HeliosConfig.h" -#include "ColorConstants.h" - -#if ALTERNATIVE_HSV_RGB == 1 -enum hsv_to_rgb_algorithm : uint8_t -{ - HSV_TO_RGB_GENERIC, - HSV_TO_RGB_RAINBOW -}; - -// global hsv to rgb algorithm selector, switch this to control -// all hsv to rgb conversions -extern hsv_to_rgb_algorithm g_hsv_rgb_alg; -#endif - -class ByteStream; -class RGBColor; - -class HSVColor -{ -public: - HSVColor(); - HSVColor(uint8_t hue, uint8_t sat, uint8_t val); - - // assignment from uint32_t - HSVColor(uint32_t dwVal); - HSVColor &operator=(const uint32_t &rhs); - - // construction/assignment from RGB - HSVColor(const RGBColor &rhs); - HSVColor &operator=(const RGBColor &rhs); - - // equality operators - bool operator==(const HSVColor &other) const; - bool operator!=(const HSVColor &other) const; - - bool empty() const; - void clear(); - - uint32_t raw() const { return ((uint32_t)hue << 16) | ((uint32_t)sat << 8) | (uint32_t)val; } - - // public members - uint8_t hue; - uint8_t sat; - uint8_t val; -}; - -class RGBColor -{ -public: - RGBColor(); - RGBColor(uint8_t red, uint8_t green, uint8_t blue); - - // assignment from uint32_t - RGBColor(uint32_t dwVal); - RGBColor &operator=(const uint32_t &rhs); - - RGBColor(const HSVColor &rhs); - RGBColor &operator=(const HSVColor &rhs); - - // equality operators - bool operator==(const RGBColor &other) const; - bool operator!=(const RGBColor &other) const; - - bool empty() const; - void clear(); - - RGBColor adjustBrightness(uint8_t fadeBy); - - uint32_t raw() const { return ((uint32_t)red << 16) | ((uint32_t)green << 8) | (uint32_t)blue; } - - // public members - uint8_t red; - uint8_t green; - uint8_t blue; -}; - -// Stolen from FastLED hsv to rgb full rainbox where all colours -// are given equal weight, this makes for-example yellow larger -// best to use this function as it is the legacy choice -RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs); -// generic hsv to rgb conversion nothing special -RGBColor hsv_to_rgb_generic(const HSVColor &rhs); - -// Convert rgb to hsv with generic fast method -HSVColor rgb_to_hsv_generic(const RGBColor &rhs); - -#endif diff --git a/Helios.cpp b/Helios.cpp deleted file mode 100644 index 8e4ffc4f..00000000 --- a/Helios.cpp +++ /dev/null @@ -1,782 +0,0 @@ -#include - -#include "Helios.h" - -#include "ColorConstants.h" -#include "TimeControl.h" -#include "Storage.h" -#include "Pattern.h" -#include "Random.h" -#include "Button.h" -#include "Led.h" - -#ifdef HELIOS_EMBEDDED -#include -#include -#include -#endif - -#ifdef HELIOS_CLI -#include -#endif - -#include - -Helios::State Helios::cur_state; -Helios::Flags Helios::global_flags; -uint8_t Helios::menu_selection; -uint8_t Helios::cur_mode; -uint8_t Helios::selected_slot; -uint8_t Helios::selected_base_quad; -uint8_t Helios::selected_hue; -uint8_t Helios::selected_sat; -Pattern Helios::pat; -bool Helios::keepgoing; - -#ifdef HELIOS_CLI -bool Helios::sleeping; -#endif - -bool Helios::init() -{ - // initialize the time control and led control - if (!Time::init()) { - return false; - } - if (!Led::init()) { - return false; - } - if (!Storage::init()) { - return false; - } - if (!Button::init()) { - return false; - } - - // initialize globals - cur_state = STATE_MODES; - menu_selection = 0; - cur_mode = 0; - selected_slot = 0; - selected_base_quad = 0; - selected_hue = 0; - selected_sat = 0; - keepgoing = true; -#ifdef HELIOS_CLI - sleeping = false; -#endif - - // read the global flags from index 0 config - global_flags = (Flags)Storage::read_config(0); - if (has_flag(FLAG_CONJURE)) { - // if conjure is enabled then load the current mode index from storage - cur_mode = Storage::read_config(1); - } - - // load the current mode from store and initialize it - load_cur_mode(); - -#ifdef HELIOS_EMBEDDED - // Timer0 setup for 1 kHz interrupt - TCCR0A |= (1 << WGM01); -#if F_CPU == 16000000L - // 1ms at 16MHz clock with prescaler of 64 - OCR0A = 249; -#elif F_CPU == 8000000L - // 1ms at 8mhz clock with prescaler of 64 - OCR0A = 124; -#elif F_CPU == 1000000L - // 1ms at 1mhz clock with prescaler of 64 - OCR0A = 15; // Adjusted value for 1 MHz clock -#endif - TIMSK |= (1 << OCIE0A); - // Start timer with prescaler of 64 - TCCR0B |= (1 << CS01) | (1 << CS00); - // enable interrupts - sei(); -#endif - - return true; -} - -#ifdef HELIOS_EMBEDDED -ISR(TIM0_COMPA_vect) { - // 1 kHz system tick - Helios::tick(); -} -#endif - -void Helios::tick() -{ - // sample the button and re-calculate all button globals - // the button globals should not change anywhere else - Button::update(); - - // handle the current state of the system, ie whatever state - // we're in we check for the appropriate input events for that - // state by checking button globals, then run the appropriate logic - handle_state(); - - // NOTE: Do not update the LED here anymore, instead we call Led::update() - // in the tight loop inside main() where it can perform software PWM - // on the LED pins at a much higher frequency - - // finally tick the clock forward and then sleep till the entire - // tick duration has been consumed - Time::tickClock(); -} - -void Helios::enter_sleep() -{ -#ifdef HELIOS_EMBEDDED - // Enable wake on interrupt for the button - Button::enableWake(); - // Set sleep mode to POWER DOWN mode - set_sleep_mode(SLEEP_MODE_PWR_DOWN); - // enter sleep - sleep_mode(); - // ... interrupt will make us wake here - // wakeup here, re-init - init(); -#else - cur_state = STATE_SLEEP; - // enable the sleep bool - sleeping = true; -#endif -} - -void Helios::wakeup() { -#ifdef HELIOS_EMBEDDED - // nothing needed here, this interrupt firing will make the mainthread resume -#else - // re-initialize helios? - init(); - // turn off the sleeping flag that only CLI has - sleeping = false; -#endif -} - -void Helios::handle_state() -{ - // check for the force sleep button hold regardless of which state we're in - if (Button::holdDuration() > FORCE_SLEEP_TIME) { - // when released the device will just sleep - if (Button::onRelease()) { - enter_sleep(); - // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! - return; - } - // but as long as it's held past the sleep time it just turns off the led - if (Button::isPressed()) { - Led::clear(); - return; - } - } - // otherwise just handle the state like normal - switch (cur_state) { - case STATE_MODES: - handle_state_modes(); - break; - case STATE_COLOR_SELECT_SLOT: - case STATE_COLOR_SELECT_QUADRANT: - case STATE_COLOR_SELECT_HUE: - case STATE_COLOR_SELECT_SAT: - case STATE_COLOR_SELECT_VAL: - handle_state_col_select(); - break; - case STATE_PATTERN_SELECT: - handle_state_pat_select(); - break; - case STATE_TOGGLE_CONJURE: - handle_state_toggle_flag(FLAG_CONJURE); - break; - case STATE_TOGGLE_LOCK: - handle_state_toggle_flag(FLAG_LOCKED); - break; - case STATE_SET_DEFAULTS: - handle_state_set_defaults(); - break; - case STATE_SET_GLOBAL_BRIGHTNESS: - handle_state_set_global_brightness(); - break; - case STATE_SHIFT_MODE: - handle_state_shift_mode(); - break; - case STATE_RANDOMIZE: - handle_state_randomize(); - break; -#ifdef HELIOS_CLI - case STATE_SLEEP: - // simulate sleep in helios CLI - if (Button::onPress()) { - wakeup(); - } - break; -#endif - } -} - -void Helios::load_next_mode() -{ - // increment current mode and wrap around - cur_mode = (uint8_t)(cur_mode + 1) % NUM_MODE_SLOTS; - // now load current mode again - load_cur_mode(); -} - -void Helios::load_cur_mode() -{ - // read pattern from storage at cur mode index - if (!Storage::read_pattern(cur_mode, pat)) { - // and just initialize default if it cannot be read - Patterns::make_default(cur_mode, pat); - // try to write it out because storage was corrupt - Storage::write_pattern(cur_mode, pat); - } - // then re-initialize the pattern - pat.init(); -} - -void Helios::save_cur_mode() -{ - Storage::write_pattern(cur_mode, pat); -} - -void Helios::handle_state_modes() -{ - // whether they have released the button since turning on - bool hasReleased = (Button::releaseCount() > 0); - - if (Button::releaseCount() > 1 && Button::onShortClick()) { - if (has_flag(FLAG_CONJURE)) { - enter_sleep(); - } else { - load_next_mode(); - } - return; - } - - // check for lock and go back to sleep - if (has_flag(FLAG_LOCKED) && hasReleased && !Button::onRelease()) { - enter_sleep(); - // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! - return; - } - - if (!has_flag(FLAG_LOCKED) && hasReleased) { - // just play the current mode - pat.play(); - } - // check how long the button is held - uint32_t holdDur = Button::holdDuration(); - // calculate a magnitude which corresponds to how many times past the MENU_HOLD_TIME - // the user has held the button, so 0 means haven't held fully past one yet, etc - uint8_t magnitude = (uint8_t)(holdDur / MENU_HOLD_TIME); - // whether the user has held the button longer than a short click - bool heldPast = (holdDur > SHORT_CLICK_THRESHOLD); - // if the button is held for at least 1 second - if (Button::isPressed() && heldPast) { - // if the button has been released before then show the on menu - if (hasReleased) { - switch (magnitude) { - default: - case 0: Led::clear(); break; // Turn off - case 1: Led::set(RGB_TURQUOISE_BRI_LOW); break; // Color Selection - case 2: Led::set(RGB_MAGENTA_BRI_LOW); break; // Pattern Selection - case 3: Led::set(RGB_YELLOW_BRI_LOW); break; // Conjure Mode - case 4: Led::set(RGB_WHITE_BRI_LOW); break; // Shift Mode - case 5: Led::set(HSVColor(Time::getCurtime(), 255, 180)); break; // Randomizer - } - } else { - if (has_flag(FLAG_LOCKED)) { - switch (magnitude) { - default: - case 0: Led::clear(); break; - case 5: Led::set(RGB_RED_BRI_LOW); break; // Exit Lock - } - } else { - switch (magnitude) { - default: - case 0: Led::clear(); break; // nothing - case 1: Led::set(RGB_RED_BRI_LOW); break; // Enter Glow Lock - case 2: Led::set(RGB_BLUE_BRI_LOW); break; // Master Reset - case 3: Led::set(RGB_GREEN_BRI_LOW); break; // Global Brightness - } - } - } - } - // if this isn't a release tick there's nothing more to do - if (Button::onRelease()) { - if (heldPast && Button::releaseCount() == 1) { - handle_off_menu(magnitude, heldPast); - return; - } - // otherwise if we have released it then we are in the 'on' menu - handle_on_menu(magnitude, heldPast); - } -} - -void Helios::handle_off_menu(uint8_t mag, bool past) -{ - // if still locked then handle the unlocking menu which is just if mag == 5 - if (has_flag(FLAG_LOCKED)) { - switch (mag) { - case 5: // red lock - cur_state = STATE_TOGGLE_LOCK; - break; - default: - // just go back to sleep in hold-past off menu - enter_sleep(); - // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! - } - // in this case we return either way, since we're locked - return; - } - - // otherwise if not locked handle the off menu - switch (mag) { - case 1: // red lock - cur_state = STATE_TOGGLE_LOCK; - Led::clear(); - return; // RETURN HERE - case 2: // blue reset defaults - cur_state = STATE_SET_DEFAULTS; - return; // RETURN HERE - case 3: // green global brightness - cur_state = STATE_SET_GLOBAL_BRIGHTNESS; - return; // RETURN HERE - default: - // just go back to sleep in hold-past off menu - enter_sleep(); - // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! - return; - } -} - -void Helios::handle_on_menu(uint8_t mag, bool past) -{ - switch (mag) { - case 0: // off - // but only if we held for more than a short click - if (past) { - enter_sleep(); - // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! - return; - } - break; - case 1: // color select - cur_state = STATE_COLOR_SELECT_SLOT; - // reset the menu selection - menu_selection = 0; -#if ALTERNATIVE_HSV_RGB == 1 - // use the nice hue to rgb rainbow - g_hsv_rgb_alg = HSV_TO_RGB_RAINBOW; -#endif - break; - case 2: // pat select - cur_state = STATE_PATTERN_SELECT; - // reset the menu selection - menu_selection = 0; - break; - case 3: // conjure mode - cur_state = STATE_TOGGLE_CONJURE; - Led::clear(); - break; - case 4: // shift mode down - cur_state = STATE_SHIFT_MODE; - break; - case 5: // ??? - cur_state = STATE_RANDOMIZE; - break; - default: // hold past - break; - } -} - -void Helios::handle_state_col_select() -{ - uint8_t num_cols = pat.colorset().numColors(); - if (Button::onShortClick()) { - // next hue/sat/val selection - uint8_t num_menus = 4; - if (cur_state == STATE_COLOR_SELECT_SLOT) { - // menus = all colors + exit - num_menus = num_cols + 1; - // but if the num cols is less than total color slots - if (num_cols < NUM_COLOR_SLOTS) { - // then we have another menu: add color - num_menus++; - } - } else if (cur_state == STATE_COLOR_SELECT_QUADRANT) { - num_menus = 7; - } - menu_selection = (menu_selection + 1) % num_menus; - } - ColorSelectOption slot_option = OPTION_NONE; - bool check_longclick = true; - switch (cur_state) { - default: - case STATE_COLOR_SELECT_SLOT: - // pick the target colorset slot - check_longclick = handle_state_col_select_slot(slot_option); - break; - case STATE_COLOR_SELECT_QUADRANT: - // pick the hue quadrant - check_longclick = handle_state_col_select_quadrant(); - break; - case STATE_COLOR_SELECT_HUE: - // target hue for changes - check_longclick = handle_state_col_select_hue(); - break; - case STATE_COLOR_SELECT_SAT: - // target sat for changes - check_longclick = handle_state_col_select_sat(); - break; - case STATE_COLOR_SELECT_VAL: - // target val for changes - check_longclick = handle_state_col_select_val(); - break; - } - if (check_longclick && Button::onLongClick()) { - if (cur_state == STATE_COLOR_SELECT_VAL) { - cur_state = STATE_COLOR_SELECT_SLOT; - // Return to the slot you were editing - menu_selection = selected_slot; - } else { - cur_state = (State)(cur_state + 1); - // reset the menu selection - menu_selection = 0; - } - } - // get the current color - RGBColor cur = Led::get(); - cur.red /= 2; - cur.green /= 2; - cur.blue /= 2; - // this is a stupid override for when we're exiting color select - // show a white selection instead - if (slot_option != OPTION_NONE) { - cur = RGB_WHITE; - } - // show selection in all of these menus - show_selection(cur); -} - -bool Helios::handle_state_col_select_slot(ColorSelectOption &out_option) -{ - Colorset &set = pat.colorset(); - uint8_t num_cols = set.numColors(); - - bool long_click = Button::onLongClick(); - - if (num_cols < NUM_COLOR_SLOTS && menu_selection == num_cols) { - // add color - out_option = SELECTED_ADD; - Led::strobe(100, 100, RGB_WHITE_BRI_LOW, RGB_OFF); - if (long_click) { - selected_slot = menu_selection; - } - } else if (menu_selection == num_cols + 1 || (num_cols == NUM_COLOR_SLOTS && menu_selection == num_cols)) { - // exit - out_option = SELECTED_EXIT; - Led::strobe(60, 40, RGB_RED_BRI_LOW, RGB_OFF); - if (long_click) { -#if ALTERNATIVE_HSV_RGB == 1 - // restore hsv to rgb algorithm type, done color selection - g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; -#endif - save_cur_mode(); - cur_state = STATE_MODES; - return false; - } - } else { - out_option = SELECTED_SLOT; - selected_slot = menu_selection; - // render current selection - RGBColor col = set.get(selected_slot); - if (col.empty()) { - Led::strobe(1, 30, RGB_OFF, RGB_WHITE_BRI_LOW); - } else { - Led::set(col); - } - uint16_t mod_dur = (uint16_t)(Button::holdDuration() % (DELETE_COLOR_TIME * 2)); - bool deleting = (mod_dur > DELETE_COLOR_TIME); - if (deleting) { - if (Button::isPressed()) { - // flash red - Led::strobe(150, 150, RGB_RED_BRI_LOW, col); - } - if (long_click) { - set.removeColor(selected_slot); - return false; - } - } - } - return true; -} - -struct ColorsMenuData { - uint8_t hues[4]; -}; -// array of hues for selection -static const ColorsMenuData color_menu_data[4] = { - // hue0 hue1 hue2 hue3 - // ================================================================================== - { HUE_RED, HUE_CORAL_ORANGE, HUE_ORANGE, HUE_YELLOW }, - { HUE_LIME_GREEN, HUE_GREEN, HUE_SEAFOAM, HUE_TURQUOISE }, - { HUE_ICE_BLUE, HUE_LIGHT_BLUE, HUE_BLUE, HUE_ROYAL_BLUE }, - { HUE_PURPLE, HUE_PINK, HUE_HOT_PINK, HUE_MAGENTA }, -}; - -bool Helios::handle_state_col_select_quadrant() -{ - uint8_t hue_quad = (menu_selection - 2) % 4; - - if (menu_selection > 5) { - menu_selection = 0; - } - - if (Button::onLongClick()) { - // select hue/sat/val - switch (menu_selection) { - case 0: // selected blank - // add blank to set - pat.colorset().set(selected_slot, RGB_OFF); - // Return to the slot you were editing - menu_selection = selected_slot; - // go to slot selection - 1 because we will increment outside here - cur_state = STATE_COLOR_SELECT_SLOT; - return false; - case 1: // selected white - // adds white, skip hue/sat to brightness - selected_hue = 0; - selected_sat = 0; - cur_state = STATE_COLOR_SELECT_VAL; - return false; - default: // 2-5 - selected_base_quad = hue_quad; - break; - } - } - - // default col1/col2 to off and white for the first two options - RGBColor col1 = RGB_OFF; - RGBColor col2; - uint16_t on_dur, off_dur; - - switch (menu_selection) { - case 0: // Blank Option - col2 = RGB_WHITE_BRI_LOW; - on_dur = 1; - off_dur = 30; - break; - case 1: // White Option - col2 = RGB_WHITE; - on_dur = 9; - off_dur = 0; - break; - default: // Color options - col1 = HSVColor(color_menu_data[hue_quad].hues[0], 255, 255); - col2 = HSVColor(color_menu_data[hue_quad].hues[2], 255, 255); - on_dur = 500; - off_dur = 500; - break; - } - Led::strobe(on_dur, off_dur, col1, col2); - return true; -} - - - -bool Helios::handle_state_col_select_hue() -{ - uint8_t hue = color_menu_data[selected_base_quad].hues[menu_selection]; - if (Button::onLongClick()) { - // select hue/sat/val - selected_hue = hue; - } - // render current selection - Led::set(HSVColor(hue, 255, 255)); - return true; -} - -bool Helios::handle_state_col_select_sat() -{ - if (menu_selection > 3) { - menu_selection = 3; - } - static const uint8_t saturation_values[4] = {HSV_SAT_HIGH, HSV_SAT_MEDIUM, HSV_SAT_LOW, HSV_SAT_LOWEST}; - uint8_t sat = saturation_values[menu_selection]; - - // use the nice hue to rgb rainbow - if (Button::onLongClick()) { - // select hue/sat/val - selected_sat = sat; - } - // render current selection - Led::set(HSVColor(selected_hue, sat, 255)); - return true; -} - -bool Helios::handle_state_col_select_val() -{ - if (menu_selection > 3) { - menu_selection = 3; - } - static const uint8_t hsv_values[4] = {HSV_VAL_HIGH, HSV_VAL_MEDIUM, HSV_VAL_LOW, HSV_VAL_LOWEST}; - uint8_t val = hsv_values[menu_selection]; - - RGBColor targetCol = HSVColor(selected_hue, selected_sat, val); - // use the nice hue to rgb rainbow - if (Button::onLongClick()) { - // change the current patterns color - pat.colorset().set(selected_slot, targetCol); - pat.init(); - save_cur_mode(); - } - // render current selection - Led::set(targetCol); - return true; -} - -void Helios::handle_state_pat_select() -{ - if (Button::onLongClick()) { - save_cur_mode(); - cur_state = STATE_MODES; - } - if (Button::onShortClick()) { - menu_selection = (menu_selection + 1) % PATTERN_COUNT; - Patterns::make_pattern((PatternID)menu_selection, pat); - pat.init(); - } - pat.play(); - show_selection(RGB_MAGENTA_BRI_LOW); -} - -void Helios::handle_state_toggle_flag(Flags flag) -{ - // toggle the conjure flag - toggle_flag(flag); - // write out the new global flags and the current mode - save_global_flags(); - // switch back to modes - cur_state = STATE_MODES; -} - -void Helios::handle_state_set_defaults() -{ - if (Button::onShortClick()) { - menu_selection = !menu_selection; - } - // show low white for exit or red for select - if (menu_selection) { - Led::strobe(20, 10, RGB_RED_BRI_LOW, RGB_OFF); - } else { - Led::strobe(60, 20, RGB_WHITE_BRI_LOWEST, RGB_OFF); - } - // when the user long clicks a selection - if (Button::onLongClick()) { - // if the user actually selected 'yes' - if (menu_selection == 1) { - for (uint8_t i = 0; i < NUM_MODE_SLOTS; ++i) { - Patterns::make_default(i, pat); - Storage::write_pattern(i, pat); - } - // reset global flags - global_flags = FLAG_NONE; - cur_mode = 0; - // save global flags - save_global_flags(); - // re-load current mode - load_cur_mode(); - } - cur_state = STATE_MODES; - } - show_selection(RGB_WHITE_BRI_LOW); -} - -void Helios::handle_state_set_global_brightness() -{ - if (Button::onShortClick()) { - menu_selection = (menu_selection + 1) % 3; - } - // show different levels of green for each selection - switch (menu_selection) { - case 0: - Led::set(RGB_GREEN); - break; - case 1: - Led::set(RGB_GREEN_BRI_MEDIUM); - break; - case 2: - Led::set(RGB_GREEN_BRI_LOW); - break; - } - // when the user long clicks a selection - if (Button::onLongClick()) { - // set the brightness based on the selection * the brightness step amount - Led::setBrightness(menu_selection * BRIGHTNESS_STEP); - cur_state = STATE_MODES; - } - show_selection(RGB_WHITE_BRI_LOW); -} - -inline uint32_t crc32(const uint8_t *data, uint8_t size) -{ - uint32_t hash = 5381; - for (uint8_t i = 0; i < size; ++i) { - hash = ((hash << 5) + hash) + data[i]; - } - return hash; -} - -void Helios::handle_state_shift_mode() -{ - uint8_t new_mode = cur_mode ? (uint8_t)(cur_mode - 1) : (uint8_t)(NUM_MODE_SLOTS - 1); - Storage::swap_pattern(cur_mode, new_mode); - cur_mode = new_mode; - cur_state = STATE_MODES; -} - -void Helios::handle_state_randomize() -{ - if (Button::onShortClick()) { - uint32_t seed = crc32((const uint8_t *)&pat, PATTERN_SIZE); - Random ctx(seed); - Colorset &cur_set = pat.colorset(); - uint8_t num_cols = (ctx.next8() + 1) % NUM_COLOR_SLOTS; - - cur_set.randomizeColors(ctx, num_cols); - Patterns::make_pattern((PatternID)(ctx.next8() % PATTERN_COUNT), pat); - pat.init(); - } - if (Button::onLongClick()) { - save_cur_mode(); - cur_state = STATE_MODES; - } - pat.play(); - show_selection(RGB_WHITE_BRI_LOW); -} - -void Helios::save_global_flags() -{ - Storage::write_config(0, global_flags); - Storage::write_config(1, cur_mode); -} - -void Helios::show_selection(RGBColor color) -{ - // only show selection while pressing the button - if (!Button::isPressed()) { - return; - } - uint16_t holdDur = Button::holdDuration(); - // if the hold duration is outside the flashing range do nothing - if (holdDur < SHORT_CLICK_THRESHOLD || - holdDur > (SHORT_CLICK_THRESHOLD + SELECTION_FLASH_DURATION)) { - return; - } - // set the selection color - Led::set(color); -} diff --git a/Helios.h b/Helios.h deleted file mode 100644 index d22a0fdf..00000000 --- a/Helios.h +++ /dev/null @@ -1,106 +0,0 @@ -#include - -#include "HeliosConfig.h" -#include "Colorset.h" -#include "Pattern.h" - -class Helios -{ -public: - static bool init(); - static void tick(); - - static void enter_sleep(); - static void wakeup(); - - static bool keep_going() { return keepgoing; } - static void terminate() { keepgoing = false; } - -#ifdef HELIOS_CLI - static bool is_asleep() { return sleeping; } -#endif - -private: - enum Flags : uint8_t { - FLAG_NONE = 0, - FLAG_LOCKED = (1 << 0), - FLAG_CONJURE = (1 << 1), - }; - - // get/set global flags - static void set_flag(Flags flag) { global_flags = (Flags)(global_flags | flag); } - static bool has_flag(Flags flag) { return (global_flags & flag) == flag; } - static void clear_flag(Flags flag) { global_flags = (Flags)(global_flags & ~flag); } - static void toggle_flag(Flags flag) { global_flags = (Flags)(global_flags ^ flag); } - - static void handle_state(); - static void handle_state_modes(); - - // the slot selection returns this info for internal menu logic - enum ColorSelectOption { - OPTION_NONE = 0, - - SELECTED_ADD, - SELECTED_EXIT, - SELECTED_SLOT - }; - - static void handle_off_menu(uint8_t mag, bool past); - static void handle_on_menu(uint8_t mag, bool past); - static void handle_state_col_select(); - static bool handle_state_col_select_slot(ColorSelectOption &out_option); - static bool handle_state_col_select_quadrant(); - static bool handle_state_col_select_hue(); - static bool handle_state_col_select_sat(); - static bool handle_state_col_select_val(); - static void handle_state_pat_select(); - static void handle_state_toggle_flag(Flags flag); - static void handle_state_set_defaults(); - static void handle_state_set_global_brightness(); - static void handle_state_shift_mode(); - static void handle_state_randomize(); - static void load_next_mode(); - static void load_cur_mode(); - static void save_cur_mode(); - static void save_global_flags(); - static void show_selection(RGBColor color); - - enum State : uint8_t { - STATE_MODES, - STATE_COLOR_SELECT_SLOT, - STATE_COLOR_SELECT_QUADRANT, - STATE_COLOR_SELECT_HUE, - STATE_COLOR_SELECT_SAT, - STATE_COLOR_SELECT_VAL, - STATE_PATTERN_SELECT, - STATE_TOGGLE_CONJURE, - STATE_TOGGLE_LOCK, - STATE_SET_DEFAULTS, - STATE_SET_GLOBAL_BRIGHTNESS, - STATE_SHIFT_MODE, - STATE_RANDOMIZE, -#ifdef HELIOS_CLI - STATE_SLEEP, -#endif - }; - - // the current state of the system - static State cur_state; - // global flags for the entire system - static Flags global_flags; - static uint8_t menu_selection; - static uint8_t cur_mode; - // the quadrant that was selected in color select - static uint8_t selected_slot; - static uint8_t selected_base_quad; - static uint8_t selected_hue; - static uint8_t selected_sat; - static PatternArgs default_args[6]; - static Colorset default_colorsets[6]; - static Pattern pat; - static bool keepgoing; - -#ifdef HELIOS_CLI - static bool sleeping; -#endif -}; diff --git a/HeliosCLI/Makefile b/HeliosCLI/Makefile index f543c824..27c07559 100644 --- a/HeliosCLI/Makefile +++ b/HeliosCLI/Makefile @@ -20,7 +20,7 @@ DEFINES=\ # compiler include paths INCLUDES=\ - -I ../ \ + -I ../Helios \ # only set them if they're not empty to prevent unnecessary whitespace ifneq ($(DEFINES),) @@ -48,9 +48,9 @@ LIBS=\ # source files # local source files first, other sources after ifeq ($(OS),Windows_NT) - SRC = $(shell find ../ -type f -name \\*.cpp) + SRC = $(shell find ../Helios -type f -name \\*.cpp) cli_main.cpp else - SRC = $(shell find ../ -type f -name '*.cpp') + SRC = $(shell find ../Helios -type f -name '*.cpp') cli_main.cpp endif # object files are source files with .c replaced with .o diff --git a/HeliosConfig.h b/HeliosConfig.h deleted file mode 100644 index b0ea6060..00000000 --- a/HeliosConfig.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef HELIOS_CONFIG_H -#define HELIOS_CONFIG_H - -// Short Click Threshold -// -// The length of time in milliseconds for a click to -// be considered either a short or long click -#define SHORT_CLICK_THRESHOLD 400 - -// Selection Flash Duration -// -// How long the led flashes when selecting something -#define SELECTION_FLASH_DURATION 500 - -// Max Color Slots -// -// The number of slots in a colorset -#define NUM_COLOR_SLOTS 6 - -// Mode Slots -// -// The number of modes on the device -#define NUM_MODE_SLOTS 6 - -// Default Brightness -// -// The default brightness of the led -#define DEFAULT_BRIGHTNESS 255 - -// Tickrate -// -// The number of engine ticks per second -#define TICKRATE 1000 - -// Menu Hold Time -// -// How long the button must be held for the menus to open and cycle -// note this is a measure of ticks, but if the tickrate is 1000 then -// it is a measure of milliseconds -#define MENU_HOLD_TIME 1000 - -// Force Sleep Time -// -// The duration in ms/ticks to hold the button to force the chip to -// sleep at any location in the menus -#define FORCE_SLEEP_TIME 7000 - -// Delete Color Time -// -// How long to hold button on a color to start the delete color flash -// begins and starts flashes. Also how long the cycling flash is for the -// delete color selection, ie how long the user has to release to delete -// the color before it cycles back -#define DELETE_COLOR_TIME 1500 - -// Alternative HSV to RGB -// -// This enabled the alternative HSV to RGB algorithm to be used in the -// color selection menu and provide a slightly different range of colors -#define ALTERNATIVE_HSV_RGB 0 - -// Brightness Options -// -// There are three brightness options, the lowest is equal to this value, -// the middle is 2x this value, and the highest is 3x this value -#define BRIGHTNESS_STEP 85 - -// Pre-defined saturation values -#define HSV_SAT_HIGH 255 -#define HSV_SAT_MEDIUM 220 -#define HSV_SAT_LOW 190 -#define HSV_SAT_LOWEST 160 - -// Pre-defined brightness values -#define HSV_VAL_HIGH 255 -#define HSV_VAL_MEDIUM 120 -#define HSV_VAL_LOW 60 -#define HSV_VAL_LOWEST 10 - -// ============================================================================ -// Storage Constants -// -// These are various storage sizes of data and some math to -// help calculate sizes or space requirements - -// Colorset Size -// -// the colorset is just an array of colors but it also has a num colors val -#define COLORSET_SIZE ((sizeof(RGBColor) * NUM_COLOR_SLOTS) + 1) - -// Pattern Size -// -// The actual pattern storage size is the size of the colorset + 7 params + 1 pat flags -#define PATTERN_SIZE (COLORSET_SIZE + 7 + 1) - -// Slot Size -// -// the slot stores the pattern + 1 byte CRC -#define SLOT_SIZE (PATTERN_SIZE + 1) - -// Some math to calculate storage sizes: -// 3 * 6 = 18 for the colorset -// 1 + 7 + 1 + 1 = 10 for the rest -// = 28 bytes total for a pattern including CRC -// -> 8 slots = 8 * 28 = 224 -// = 31 bytes left -// -> 9 slots = 9 * 28 = 252 -// = 3 bytes left - -// forbidden constant: -// #define HELIOS_ARDUINO 1 - - - -#endif diff --git a/Makefile b/HeliosEmbedded/Makefile similarity index 93% rename from Makefile rename to HeliosEmbedded/Makefile index 9a8d4bc5..4ad4ebc2 100644 --- a/Makefile +++ b/HeliosEmbedded/Makefile @@ -88,17 +88,17 @@ endif INCLUDES=\ -I $(INCLUDE_DIR) \ - -I ./ + -I ../Helios CFLAGS+=$(INCLUDES) # Source files ifeq ($(OS),Windows_NT) # Windows SRCS = \ - $(shell find . -maxdepth 1 -type f -name '\*.cpp') + $(shell find ../Helios -maxdepth 1 -type f -name '\*.cpp') main.cpp else # linux SRCS = \ - $(shell find . -maxdepth 1 -type f -name \*.cpp) + $(shell find ../Helios -maxdepth 1 -type f -name \*.cpp) main.cpp endif OBJS = $(SRCS:.cpp=.o) diff --git a/avrdude.conf b/HeliosEmbedded/avrdude.conf similarity index 100% rename from avrdude.conf rename to HeliosEmbedded/avrdude.conf diff --git a/avrsize.sh b/HeliosEmbedded/avrsize.sh similarity index 100% rename from avrsize.sh rename to HeliosEmbedded/avrsize.sh diff --git a/main.cpp b/HeliosEmbedded/main.cpp similarity index 100% rename from main.cpp rename to HeliosEmbedded/main.cpp diff --git a/HeliosVortex.atsln b/HeliosVortex.atsln deleted file mode 100644 index 7ee7542b..00000000 --- a/HeliosVortex.atsln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Atmel Studio Solution File, Format Version 11.00 -VisualStudioVersion = 14.0.23107.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "HeliosVortex", "HeliosVortex.cppproj", "{DCE6C7E3-EE26-4D79-826B-08594B9AD897}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|AVR = Debug|AVR - Release|AVR = Release|AVR - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.ActiveCfg = Debug|AVR - {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.Build.0 = Debug|AVR - {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.ActiveCfg = Release|AVR - {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.Build.0 = Release|AVR - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/HeliosVortex.componentinfo.xml b/HeliosVortex.componentinfo.xml deleted file mode 100644 index cf69ec3f..00000000 --- a/HeliosVortex.componentinfo.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - Device - Startup - - - Atmel - 1.10.0 - C:/Program Files (x86)\Atmel\Studio\7.0\Packs - - - - - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\include\ - - include - C - - - include/ - - - - - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\include\avr\iotn85.h - - header - C - T0lnJZ6iliUJCzU7ZHCMPQ== - - include/avr/iotn85.h - - - - - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\templates\main.c - template - source - C Exe - KjvOcFWd++tbnsEMfVPd/w== - - templates/main.c - Main file (.c) - - - - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\templates\main.cpp - template - source - C Exe - CInN1gYxR5z9bF9u3OpSqQ== - - templates/main.cpp - Main file (.cpp) - - - - C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.10.348\gcc\dev\attiny85 - - libraryPrefix - GCC - - - gcc/dev/attiny85 - - - - - ATtiny_DFP - C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATtiny_DFP/1.10.348/Atmel.ATtiny_DFP.pdsc - 1.10.348 - true - ATtiny85 - - - - Resolved - Fixed - true - - - \ No newline at end of file diff --git a/HeliosVortex.cppproj b/HeliosVortex.cppproj deleted file mode 100644 index 00841589..00000000 --- a/HeliosVortex.cppproj +++ /dev/null @@ -1,260 +0,0 @@ - - - - 2.0 - 7.0 - com.Atmel.AVRGCC8.CPP - dce6c7e3-ee26-4d79-826b-08594b9ad897 - ATtiny85 - none - Executable - CPP - $(MSBuildProjectName) - .elf - $(MSBuildProjectDirectory)\$(Configuration) - HeliosVortex - HeliosVortex - HeliosVortex - Native - true - false - true - true - 0x20000000 - - false - exception_table - 2 - 0 - 0 - - com.atmel.avrdbg.tool.atmelice - J42700066736 - 0x1E930B - - - - 1000000 - - ISP - - com.atmel.avrdbg.tool.atmelice - J42700066736 - Atmel-ICE - - ISP - 1000000 - - - - 8000 - - - - - custom - - - Custom Programming Tool - - - - - - -mmcu=attiny85 -B "%24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\gcc\dev\attiny85" - True - True - True - True - False - True - True - - - NDEBUG - HELIOS_EMBEDDED - - - - - %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\ - - - Optimize for size (-Os) - True - True - True - -flto -mrelax -std=gnu++17 -fno-threadsafe-statics -fno-exceptions - True - True - - - NDEBUG - HELIOS_EMBEDDED - - - - - %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\ - - - Optimize for size (-Os) - True - True - True - -flto -mrelax -std=gnu++17 -fno-threadsafe-statics -fno-exceptions - True - - - libm - - - -flto -mrelax -std=gnu++17 -fno-threadsafe-statics -fno-exceptions - - - %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\ - - - - - - - - - -mmcu=attiny85 -B "%24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\gcc\dev\attiny85" - True - True - True - True - False - True - True - - - %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\ - - - Optimize for size (-Os) - True - True - True - -std=c++11 - True - True - - - %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\ - - - True - True - True - - - libm - - - - - %24(PackRepoDir)\atmel\ATtiny_DFP\1.10.348\include\ - - - - - DEBUG - - - Default (-g2) - - - DEBUG - - - Optimize debugging experience (-Og) - Default (-g2) - Default (-Wa,-g) - - - - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - compile - - - - \ No newline at end of file diff --git a/HeliosVortex.ino b/HeliosVortex.ino deleted file mode 100644 index 7b604b84..00000000 --- a/HeliosVortex.ino +++ /dev/null @@ -1,11 +0,0 @@ -#include "Helios.h" - -void setup() -{ - Helios::init(); -} - -void loop() -{ - //Helios::tick(); -} diff --git a/Led.cpp b/Led.cpp deleted file mode 100644 index db77e1c8..00000000 --- a/Led.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include - -#include "Led.h" - -#include "TimeControl.h" - -#include "HeliosConfig.h" -#include "Helios.h" - -#ifdef HELIOS_EMBEDDED -#ifdef HELIOS_ARDUINO -#include -#else -#include -#include -#include -#endif -#endif - -#define PWM_PIN_R PB0 // Red channel (pin 5) -#define PWM_PIN_G PB1 // Green channel (pin 6) -#define PWM_PIN_B PB4 // Blue channel (pin 3) - -#define SCALE8(i, scale) (((uint16_t)i * (uint16_t)(scale)) >> 8) - -// array of led color values -RGBColor Led::m_ledColor = RGB_OFF; -RGBColor Led::m_realColor = RGB_OFF; -// global brightness -uint8_t Led::m_brightness = DEFAULT_BRIGHTNESS; - -bool Led::init() -{ - // clear the led colors - m_ledColor = RGB_OFF; - m_realColor = RGB_OFF; -#ifdef HELIOS_EMBEDDED -#ifdef HELIOS_ARDUINO - pinMode(0, OUTPUT); - pinMode(1, OUTPUT); - pinMode(4, OUTPUT); -#else - // Set pins as outputs - DDRB |= (1 << 0) | (1 << 1) | (1 << 4); - // Timer/Counter0 in Fast PWM mode - //TCCR0A |= (1 << WGM01) | (1 << WGM00); - // Clear OC0A and OC0B on compare match, set at BOTTOM (non-inverting mode) - //TCCR0A |= (1 << COM0A1) | (1 << COM0B1); - // Use clk/8 prescaler (adjust as needed) - //TCCR0B |= (1 << CS01); - - //TCCR1 |= (1 << PWM1A) | (1 << COM1A1) | (1 << PWM1B) | (1 << COM1B1); - //GTCCR |= (1 << PWM1B); -#endif -#endif - return true; -} - -void Led::cleanup() -{ -} - -void Led::set(RGBColor col) -{ - m_ledColor = col; - m_realColor.red = SCALE8(m_ledColor.red, m_brightness); - m_realColor.green = SCALE8(m_ledColor.green, m_brightness); - m_realColor.blue = SCALE8(m_ledColor.blue, m_brightness); -} - -void Led::adjustBrightness(uint8_t fadeBy) -{ - m_ledColor.adjustBrightness(fadeBy); -} - -void Led::strobe(uint16_t on_time, uint16_t off_time, RGBColor off_col, RGBColor on_col) -{ - set(((Time::getCurtime() % (on_time + off_time)) > on_time) ? off_col : on_col); -} - -void Led::breath(uint8_t hue, uint32_t duration, uint8_t magnitude, uint8_t sat, uint8_t val) -{ - if (!duration) { - // don't divide by 0 - return; - } - // Determine the phase in the cycle - uint32_t phase = Time::getCurtime() % (2 * duration); - // Calculate hue shift - int32_t hueShift; - if (phase < duration) { - // Ascending phase - from hue to hue + magnitude - hueShift = (phase * magnitude) / duration; - } else { - // Descending phase - from hue + magnitude to hue - hueShift = ((2 * duration - phase) * magnitude) / duration; - } - // Apply hue shift - ensure hue stays within valid range - uint8_t shiftedHue = hue + hueShift; - // Apply the hsv color as a strobing hue shift - strobe(2, 13, RGB_OFF, HSVColor(shiftedHue, sat, val)); -} - -void Led::hold(RGBColor col) -{ - set(col); - update(); - Time::delayMilliseconds(250); -} - -void Led::update() -{ -#ifdef HELIOS_EMBEDDED - // write out the rgb values to analog pins -#ifdef HELIOS_ARDUINO - analogWrite(PWM_PIN_R, m_realColor.red); - analogWrite(PWM_PIN_G, m_realColor.green); - analogWrite(PWM_PIN_B, m_realColor.blue); -#else - // a counter to keep track of milliseconds for the PWM - static uint8_t counter = 0; - counter++; - // run the software PWM on each pin - if (counter < m_realColor.red) { - PORTB |= (1 << PWM_PIN_R); - } else { - PORTB &= ~(1 << PWM_PIN_R); - } - if (counter < m_realColor.green) { - PORTB |= (1 << PWM_PIN_G); - } else { - PORTB &= ~(1 << PWM_PIN_G); - } - if (counter < m_realColor.blue) { - PORTB |= (1 << PWM_PIN_B); - } else { - PORTB &= ~(1 << PWM_PIN_B); - } -#endif -#endif -} diff --git a/Led.h b/Led.h deleted file mode 100644 index 16d96191..00000000 --- a/Led.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef LED_CONTROL_H -#define LED_CONTROL_H - -#include - -#include "Colortypes.h" - -class Led -{ - // private unimplemented constructor - Led(); - -public: - // opting for static class here because there should only ever be one - // Led control object and I don't like singletons - static bool init(); - static void cleanup(); - - // control individual LED, these are appropriate to use in internal pattern logic - static void set(RGBColor col); - - // Turn off individual LEDs, these are appropriate to use in internal pattern logic - static void clear() { set(RGB_OFF); } - - // Dim individual LEDs, these are appropriate to use in internal pattern logic - static void adjustBrightness(uint8_t fadeBy); - - // strobe between two colors with a simple on/off timing - static void strobe(uint16_t on_time, uint16_t off_time, RGBColor col1, RGBColor col2); - - // breath the hue on an index - // warning: these use hsv to rgb in realtime! - static void breath(uint8_t hue, uint32_t duration = 1000, uint8_t magnitude = 60, - uint8_t sat = 255, uint8_t val = 255); - - // a very specialized api to hold all leds on a color for 250ms - static void hold(RGBColor col); - - // get the RGBColor of an Led index - static RGBColor get() { return m_ledColor; } - - // global brightness - static uint8_t getBrightness() { return m_brightness; } - static void setBrightness(uint8_t brightness) { m_brightness = brightness; } - - // actually update the LEDs and show the changes - static void update(); - -private: - // the global brightness - static uint8_t m_brightness; - // led color - static RGBColor m_ledColor; - static RGBColor m_realColor; -}; - -#endif diff --git a/Pattern.cpp b/Pattern.cpp deleted file mode 100644 index 1fd6a72e..00000000 --- a/Pattern.cpp +++ /dev/null @@ -1,294 +0,0 @@ -#include "Pattern.h" - -//#include "../Patterns/PatternBuilder.h" -#include "TimeControl.h" -#include "Colorset.h" - -#include "HeliosConfig.h" -#include "Led.h" - -#include // for memcpy - -// uncomment me to print debug labels on the pattern states, this is useful if you -// are debugging a pattern strip from the command line and want to see what state -// the pattern is in each tick of the pattern -//#define DEBUG_BASIC_PATTERN - -#ifdef DEBUG_BASIC_PATTERN -#include "../../Time/TimeControl.h" -#include -// print out the current state of the pattern -#define PRINT_STATE(state) printState(state) -static void printState(PatternState state) -{ - static uint64_t lastPrint = 0; - if (lastPrint == Time::getCurtime()) return; - switch (m_state) { - case STATE_ON: printf("on "); break; - case STATE_OFF: printf("off "); break; - case STATE_IN_GAP: printf("gap1"); break; - case STATE_IN_DASH: printf("dash"); break; - case STATE_IN_GAP2: printf("gap2"); break; - default: return; - } - lastPrint = Time::getCurtime(); -} -#else -#define PRINT_STATE(state) // do nothing -#endif - -Pattern::Pattern(uint8_t onDur, uint8_t offDur, uint8_t gap, - uint8_t dash, uint8_t group, uint8_t blend, uint8_t flips) : - m_onDuration(onDur), - m_offDuration(offDur), - m_gapDuration(gap), - m_dashDuration(dash), - m_groupSize(group), - m_blendSpeed(blend), - m_numFlips(flips), - m_patternFlags(0), - m_colorset(), - m_groupCounter(0), - m_state(STATE_BLINK_ON), - m_blinkTimer(), - m_cur(), - m_next(), - m_flip(0) -{ -} - -Pattern::Pattern(const PatternArgs &args) : - Pattern(args.on_dur, args.off_dur, args.gap_dur, - args.dash_dur, args.group_size, args.blend_speed, args.num_flips) -{ -} - -Pattern::~Pattern() -{ -} - -void Pattern::init() -{ - m_colorset.resetIndex(); - - // the default state to begin with - m_state = STATE_BLINK_ON; - // if a dash is present then always start with the dash because - // it consumes the first color in the colorset - if (m_dashDuration > 0) { - m_state = STATE_BEGIN_DASH; - } - // if there's no on duration or dash duration the led is just disabled - if ((!m_onDuration && !m_dashDuration) || !m_colorset.numColors()) { - m_state = STATE_DISABLED; - } - m_groupCounter = m_groupSize ? m_groupSize : (m_colorset.numColors() - (m_dashDuration != 0)); - - if (m_blendSpeed > 0) { - // convert current/next colors to HSV but only if we are doing a blend - m_cur = m_colorset.getNext(); - m_next = m_colorset.getNext(); - } - // reset the flip count - m_flip = 0; -} - -void Pattern::play() -{ - // Sometimes the pattern needs to cycle multiple states in a single frame so - // instead of using a loop or recursion I have just used a simple goto -replay: - - // its kinda evolving as i go - switch (m_state) { - case STATE_DISABLED: - return; - case STATE_BLINK_ON: - if (m_onDuration > 0) { - onBlinkOn(); - --m_groupCounter; - nextState(m_onDuration); - return; - } - m_state = STATE_BLINK_OFF; - case STATE_BLINK_OFF: - // the whole 'should blink off' situation is tricky because we might need - // to go back to blinking on if our colorset isn't at the end yet - if (m_groupCounter > 0 || (!m_gapDuration && !m_dashDuration)) { - if (m_offDuration > 0) { - onBlinkOff(); - nextState(m_offDuration); - return; - } - if (m_groupCounter > 0 && m_onDuration > 0) { - m_state = STATE_BLINK_ON; - goto replay; - } - } - m_state = STATE_BEGIN_GAP; - case STATE_BEGIN_GAP: - m_groupCounter = m_groupSize ? m_groupSize : (m_colorset.numColors() - (m_dashDuration != 0)); - if (m_gapDuration > 0) { - beginGap(); - nextState(m_gapDuration); - return; - } - m_state = STATE_BEGIN_DASH; - case STATE_BEGIN_DASH: - if (m_dashDuration > 0) { - beginDash(); - nextState(m_dashDuration); - return; - } - m_state = STATE_BEGIN_GAP2; - case STATE_BEGIN_GAP2: - if (m_dashDuration > 0 && m_gapDuration > 0) { - beginGap(); - nextState(m_gapDuration); - return; - } - m_state = STATE_BLINK_ON; - goto replay; - default: - break; - } - - if (!m_blinkTimer.alarm()) { - // no alarm triggered just stay in current state, return and don't transition states - PRINT_STATE(m_state); - return; - } - - // this just transitions the state into the next state, with some edge conditions for - // transitioning to different states under certain circumstances. Honestly this is - // a nightmare to read now and idk how to fix it - if (m_state == STATE_IN_GAP2 || (m_state == STATE_OFF && m_groupCounter > 0)) { - // this is an edge condition for when in the second gap or off in the non-last off blink - // then the state actually needs to jump backwards rather than iterate - m_state = m_onDuration ? STATE_BLINK_ON : (m_dashDuration ? STATE_BEGIN_DASH : STATE_BEGIN_GAP); - } else if (m_state == STATE_OFF && (!m_groupCounter || m_colorset.numColors() == 1)) { - // this is an edge condition when the state is off but this is the last off blink in the - // group or there's literally only one color in the group then if there is more blinks - // left in the group we need to cycle back to blink on instead of to the next state - m_state = (m_groupCounter > 0) ? STATE_BLINK_ON : STATE_BEGIN_GAP; - } else { - // this is the standard case, iterate to the next state - m_state = (PatternState)(m_state + 1); - } - // poor-mans recurse with the new state change (this transitions to a new state within the same tick) - goto replay; -} - -// set args -void Pattern::setArgs(const PatternArgs &args) -{ - memcpy(&m_onDuration, &(args.on_dur), 7); -} - -void Pattern::onBlinkOn() -{ - PRINT_STATE(STATE_ON); - if (isBlend()) { - blendBlinkOn(); - return; - } - Led::set(m_colorset.getNext()); -} - -void Pattern::onBlinkOff() -{ - PRINT_STATE(STATE_OFF); - Led::clear(); -} - -void Pattern::beginGap() -{ - PRINT_STATE(STATE_IN_GAP); - Led::clear(); -} - -void Pattern::beginDash() -{ - PRINT_STATE(STATE_IN_DASH); - Led::set(m_colorset.getNext()); -} - -void Pattern::nextState(uint8_t timing) -{ - m_blinkTimer.init(timing); - m_state = (PatternState)(m_state + 1); -} - -bool Pattern::equals(const Pattern *other) -{ - if (!other) { - return false; - } - // compare the colorset - if (!m_colorset.equals(&other->m_colorset)) { - return false; - } - // then compare each arg - if (m_onDuration != other->m_onDuration || - m_offDuration != other->m_offDuration || - m_gapDuration != other->m_gapDuration || - m_dashDuration != other->m_dashDuration || - m_groupSize != other->m_groupSize || - m_blendSpeed != other->m_blendSpeed || - m_numFlips != other->m_numFlips) { - return false; - } - // if those match then it's effectively the same - // pattern even if anything else is different - return true; -} - -// change the colorset -void Pattern::setColorset(const Colorset &set) -{ - m_colorset = set; -} - -void Pattern::clearColorset() -{ - m_colorset.clear(); -} - -void Pattern::blendBlinkOn() -{ - if (m_cur == m_next) { - m_next = m_colorset.getNext(); - } - interpolate(m_cur.red, m_next.red); - interpolate(m_cur.green, m_next.green); - interpolate(m_cur.blue, m_next.blue); - RGBColor col = m_cur; - if (m_flip) { - // convert to hsv - HSVColor hsvCol = m_cur; - // shift the hue by a flip size - hsvCol.hue += (m_flip * (127 / m_numFlips)); - // convert the hsv color back to RGB - col = hsvCol; - } - // set the color - Led::set(col); - // increment the flip count - m_flip++; - // modulate the flip count DO NOT USE MODULO OPERATOR BECAUSE - // THE FLIP COUNT COULD BE 0 THAT WILL DIVIDE BY ZERO - if (m_flip > m_numFlips) { - m_flip = 0; - } -} - -void Pattern::interpolate(uint8_t ¤t, const uint8_t next) -{ - if (current < next) { - uint8_t step = (next - current) > m_blendSpeed ? m_blendSpeed : (next - current); - current += step; - } else if (current > next) { - uint8_t step = (current - next) > m_blendSpeed ? m_blendSpeed : (current - next); - current -= step; - } -} diff --git a/Pattern.h b/Pattern.h deleted file mode 100644 index 1b323e77..00000000 --- a/Pattern.h +++ /dev/null @@ -1,140 +0,0 @@ -#ifndef PATTERN_H -#define PATTERN_H - -#include "Colorset.h" - -#include "Timer.h" -#include "Patterns.h" - -// for specifying things like default args -struct PatternArgs { - PatternArgs() : on_dur(0), off_dur(0), gap_dur(0), dash_dur(0), - group_size(0), blend_speed(0), num_flips(0) - {} - uint8_t on_dur; - uint8_t off_dur; - uint8_t gap_dur; - uint8_t dash_dur; - uint8_t group_size; - uint8_t blend_speed; - uint8_t num_flips; -}; - -class Pattern -{ -public: - // try to not set on duration to 0 - Pattern(uint8_t onDur = 1, uint8_t offDur = 0, uint8_t gap = 0, - uint8_t dash = 0, uint8_t group = 0, uint8_t blend = 0, - uint8_t flips = 0); - Pattern(const PatternArgs &args); - ~Pattern(); - - // init the pattern to initial state - void init(); - - // pure virtual must override the play function - void play(); - - // set args - void setArgs(const PatternArgs &args); - - // comparison to other pattern - // NOTE: That may cause problems because the parameter is still a Pattern * - // which means comparison would need to cast the other upwards first - // NOTE2: Removing virtual because this probably shouldn't be overridden - bool equals(const Pattern *other); - - // change the colorset - const Colorset getColorset() const { return m_colorset; } - Colorset getColorset() { return m_colorset; } - Colorset &colorset() { return m_colorset; } - void setColorset(const Colorset &set); - void clearColorset(); - - // get the pattern flags - uint32_t getFlags() const { return m_patternFlags; } - bool hasFlags(uint32_t flags) const { return (m_patternFlags & flags) != 0; } - - // whether blend speed is non 0 - bool isBlend() const { return m_blendSpeed > 0; } - -protected: - // ================================== - // Pattern Parameters - uint8_t m_onDuration; - uint8_t m_offDuration; - uint8_t m_gapDuration; - uint8_t m_dashDuration; - uint8_t m_groupSize; - uint8_t m_blendSpeed; - uint8_t m_numFlips; - - // ================================== - // Pattern Members - - // any flags the pattern has - uint8_t m_patternFlags; - // a copy of the colorset that this pattern is initialized with - Colorset m_colorset; - - // ================================== - // Blink Members - uint8_t m_groupCounter; - - // apis for blink - void onBlinkOn(); - void onBlinkOff(); - void beginGap(); - void beginDash(); - void nextState(uint8_t timing); - - // the various different blinking states the pattern can be in - enum PatternState : uint8_t - { - // the led is disabled (there is no on or dash) - STATE_DISABLED, - - // the pattern is blinking on the next color in the set - STATE_BLINK_ON, - STATE_ON, - - // the pattern is blinking off - STATE_BLINK_OFF, - STATE_OFF, - - // the pattern is starting a gap after a colorset - STATE_BEGIN_GAP, - STATE_IN_GAP, - - // the pattern is beginning a dash after a colorset or gap - STATE_BEGIN_DASH, - STATE_IN_DASH, - - // the pattern is starting a gap after a dash - STATE_BEGIN_GAP2, - STATE_IN_GAP2, - }; - - // the state of the current pattern - PatternState m_state; - - // the blink timer used to measure blink timings - Timer m_blinkTimer; - - // ================================== - // Blend Members - - // current color and target blend color - RGBColor m_cur; - RGBColor m_next; - - // the current flip counter - uint8_t m_flip; - - // apis for blend - void blendBlinkOn(); - void interpolate(uint8_t ¤t, const uint8_t next); -}; - -#endif diff --git a/Patterns.cpp b/Patterns.cpp deleted file mode 100644 index 62eb9b10..00000000 --- a/Patterns.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "Patterns.h" - -#include "Storage.h" -#include "Pattern.h" - -// define arrays of colors, you can reuse these if you have multiple -// modes that use the same colorset -- these demonstrate the max amount -// of colors in each set but you can absolutely list a lesser amount -static const uint32_t color_codes0[] = {RGB_RED, RGB_ORANGE, RGB_YELLOW, RGB_TURQUOISE, RGB_BLUE, RGB_PINK}; -static const uint32_t color_codes1[] = {RGB_RED, RGB_CORAL_ORANGE_SAT_MEDIUM, RGB_ORANGE, RGB_YELLOW_SAT_LOW}; -static const uint32_t color_codes2[] = {RGB_PURPLE_SAT_MEDIUM, RGB_RED_BRI_LOWEST, RGB_MAGENTA_BRI_LOWEST, RGB_BLUE_BRI_LOWEST}; -static const uint32_t color_codes3[] = {RGB_MAGENTA, RGB_YELLOW, RGB_TURQUOISE, RGB_PINK_SAT_LOW, RGB_RED, RGB_YELLOW}; -static const uint32_t color_codes4[] = {RGB_MAGENTA_BRI_LOWEST, RGB_ROYAL_BLUE_BRI_LOW, RGB_TURQUOISE, RGB_ROYAL_BLUE_BRI_LOW, RGB_MAGENTA_BRI_LOWEST, RGB_OFF}; -static const uint32_t color_codes5[] = {RGB_RED, RGB_HOT_PINK, RGB_ROYAL_BLUE, RGB_BLUE, RGB_GREEN, RGB_YELLOW}; - -// Define Colorset configurations for each slot -struct default_colorset { - uint8_t num_cols; - const uint32_t *cols; -}; - -// the array of colorset entries, make sure the number on the left reflects -// the number of colors in the array on the right -static const default_colorset default_colorsets[] = { - { 6, color_codes0 }, // 0 Lightside - { 4, color_codes1 }, // 1 Sauna - { 4, color_codes2 }, // 2 UltraViolet - { 6, color_codes3 }, // 3 Space Carnival - { 6, color_codes4 }, // 4 Ice Blade - { 6, color_codes5 }, // 5 Rainbow Glitter -}; - -void Patterns::make_default(uint8_t index, Pattern &pat) -{ - if (index >= NUM_MODE_SLOTS) { - return; - } - PatternArgs args; - switch (index) { - case 0: // Lightside - args.on_dur = 2; - args.gap_dur = 40; - break; - case 1: // Sauna - args.on_dur = 1; - args.off_dur = 9; - break; - case 2: // UltraViolet - args.on_dur = 9; - break; - case 3: // Space Carnival - args.on_dur = 3; - args.off_dur = 23; - break; - case 4: // Ice Blade - args.on_dur = 3; - args.off_dur = 1; - break; - case 5: // Rainbow Glitter - args.on_dur = 1; - args.off_dur = 50; - break; - } - // assign default args - pat.setArgs(args); - // build the set out of the defaults - Colorset set(default_colorsets[index].num_cols, default_colorsets[index].cols); - // assign default colorset - pat.setColorset(set); -} - -void Patterns::make_pattern(PatternID id, Pattern &pat) -{ - PatternArgs args; - switch (id) { - default: - case PATTERN_STROBEGAP: - args.gap_dur = 25; - case PATTERN_STROBE: - args.on_dur = 5; - args.off_dur = 8; - break; - case PATTERN_FLARE: - args.on_dur = 1; - args.off_dur = 30; - break; - case PATTERN_GLOW: - args.on_dur = 2; - args.gap_dur = 40; - break; - case PATTERN_FLICKER: - args.on_dur = 1; - args.off_dur = 50; - break; - case INOVA_BLINK: - args.on_dur = 10; - args.off_dur = 250; - break; - case PATTERN_HYPERGAP: - args.gap_dur = 218; - case PATTERN_HYPERSTROBE: - args.on_dur = 16; - args.off_dur = 20; - break; - case PATTERN_ULTRA_DOPS: - args.on_dur = 3; - args.off_dur = 1; - break; - case PATTERN_STROBIEGAP: - args.gap_dur = 100; - case PATTERN_STROBIE: - args.on_dur = 3; - args.off_dur = 23; - break; - case PATTERN_DOPSGAP: - args.gap_dur = 40; - case PATTERN_DOPS: - args.on_dur = 1; - args.off_dur = 9; - break; - case PATTERN_BLINKIE: - args.on_dur = 3; - args.off_dur = 1; - args.gap_dur = 150; - break; - case PATTERN_GHOSTCRUSH: - args.on_dur = 3; - args.off_dur = 1; - args.gap_dur = 18; - break; - case PATTERN_DOUBLEDOPS: - args.on_dur = 1; - args.off_dur = 1; - args.gap_dur = 10; - args.group_size = 2; - break; - case PATTERN_CHOPPER: - args.on_dur = 1; - args.off_dur = 1; - args.gap_dur = 10; - args.group_size = 2; - break; - case PATTERN_DASHGAP: - args.on_dur = 1; - args.off_dur = 1; - args.gap_dur = 20; - args.dash_dur = 20; - break; - case PATTERN_DASHDOPS: - args.on_dur = 1; - args.off_dur = 10; - args.gap_dur = 10; - args.dash_dur = 18; - break; - case PATTERN_DASHCRUSH: - args.on_dur = 4; - args.off_dur = 1; - args.gap_dur = 10; - args.dash_dur = 18; - break; - case PATTERN_ULTRADASH: - args.on_dur = 1; - args.off_dur = 3; - args.gap_dur = 3; - args.dash_dur = 14; - break; - case PATTERN_GAPCYCLE: - args.on_dur = 2; - args.off_dur = 6; - args.gap_dur = 12; - args.dash_dur = 25; - args.group_size = 2; - break; - case PATTERN_DASHCYCLE: - args.on_dur = 1; - args.off_dur = 3; - args.gap_dur = 3; - args.dash_dur = 30; - args.group_size = 2; - break; - case PATTERN_TRACER: - args.on_dur = 3; - args.dash_dur = 20; - args.group_size = 1; - break; - case PATTERN_RIBBON: - args.on_dur = 9; - break; - case PATTERN_MINIRIBBON: - args.on_dur = 1; - break; - case PATTERN_COMPLEMENTARY_BLEND: - args.num_flips = 1; - case PATTERN_BLEND: - args.on_dur = 2; - args.off_dur = 13; - args.blend_speed = 5; - break; - case PATTERN_COMPLEMENTARY_BLENDSTROBE: - args.num_flips = 1; - case PATTERN_BLEND_STROBE: - args.on_dur = 5; - args.off_dur = 8; - args.blend_speed = 10; - break; - case PATTERN_BLEND_STROBIE: - args.on_dur = 3; - args.off_dur = 23; - args.blend_speed = 10; - break; - case PATTERN_COMPLEMENTARY_BLENDSTROBEGAP: - args.num_flips = 1; - case PATTERN_BLENDSTROBEGAP: - args.on_dur = 6; - args.off_dur = 6; - args.gap_dur = 25; - args.blend_speed = 10; - break; - } - pat.setArgs(args); -} diff --git a/Patterns.h b/Patterns.h deleted file mode 100644 index 49781948..00000000 --- a/Patterns.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef PATTERNS_H -#define PATTERNS_H - -#include - -// List of patterns that can be built, both single and multi-led patterns are found in this list. -// Within both single and multi LED pattern lists there are 'core' patterns which are associated -// with a class, and there are 'shell' patterns which are simply wrapperns around another pattern -// with different parameters passed to the constructor. There is no way to know which patterns -// are 'core' patterns, except by looking at PatternBuilder::generate to see which classes exist -enum PatternID : int8_t { - // no pattern at all, use this sparingly and default to - // PATTERN_FIRST when possible - PATTERN_NONE = (PatternID)-1, - - // first pattern of all - PATTERN_FIRST = 0, - // ===================================== - - // all 'single led' patterns below - - PATTERN_RIBBON = PATTERN_FIRST, - PATTERN_ULTRA_DOPS, - PATTERN_DOPS, - PATTERN_STROBE, - PATTERN_STROBIE, - PATTERN_HYPERSTROBE, - PATTERN_FLARE, - PATTERN_GLOW, - PATTERN_FLICKER, - PATTERN_BLINKIE, - INOVA_BLINK, - PATTERN_BLEND, - PATTERN_BLEND_STROBE, - PATTERN_BLEND_STROBIE, - PATTERN_DASHCYCLE, - PATTERN_ULTRADASH, - PATTERN_DASHDOPS, - PATTERN_TRACER, - PATTERN_MINIRIBBON, - - PATTERN_BLENDSTROBEGAP, - - PATTERN_STROBEGAP, - PATTERN_HYPERGAP, - PATTERN_STROBIEGAP, - PATTERN_DOPSGAP, - PATTERN_GHOSTCRUSH, - PATTERN_DOUBLEDOPS, - PATTERN_CHOPPER, - PATTERN_DASHGAP, - - PATTERN_DASHCRUSH, - - PATTERN_GAPCYCLE, - - PATTERN_COMPLEMENTARY_BLEND, - PATTERN_COMPLEMENTARY_BLENDSTROBE, - PATTERN_COMPLEMENTARY_BLENDSTROBEGAP, - PATTERN_SOLID, - - // ADD NEW PATTERNS HERE - - // Meta pattern constants - INTERNAL_PATTERNS_END, - PATTERN_LAST = (INTERNAL_PATTERNS_END - 1), - PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, // total number of patterns -}; - -class Pattern; - -class Patterns { - public: - static void make_default(uint8_t index, Pattern &pat); - static void make_pattern(PatternID id, Pattern &pat); -}; - -#endif diff --git a/Random.cpp b/Random.cpp deleted file mode 100644 index fb10f201..00000000 --- a/Random.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "Random.h" - -Random::Random() : - m_seed(0) -{ -} - -Random::Random(uint32_t newseed) : - Random() -{ - seed(newseed); -} - -Random::~Random() -{ -} - -void Random::seed(uint32_t newseed) -{ - if (!newseed) { - m_seed = 42; - } - m_seed = newseed; -} - -uint16_t Random::next16(uint16_t minValue, uint16_t maxValue) -{ - // walk the LCG forward to the next step - m_seed = (m_seed * 1103515245 + 12345) & 0x7FFFFFFF; - uint32_t range = maxValue - minValue; - if (range != 0xFFFFFFFF) { - // shift the seed 16 bits to the right because the lower 16 bits - // of this LCG are apparently not uniform whatsoever, where as the - // upper 16 bits appear to be quite uniform as per tests. We don't - // really need 32bit random values so we offer max 16bits of entropy - return ((m_seed >> 16) % (range + 1)) + minValue; - } - return (m_seed >> 16); -} - -uint8_t Random::next8(uint8_t minValue, uint8_t maxValue) -{ - uint32_t result = next16(minValue, maxValue); - return static_cast(result); -} diff --git a/Random.h b/Random.h deleted file mode 100644 index 1f556f42..00000000 --- a/Random.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -class Random -{ -public: - Random(); - Random(uint32_t newseed); - ~Random(); - - void seed(uint32_t newseed); - - uint8_t next8(uint8_t minValue = 0, uint8_t maxValue = 0xFF); - uint16_t next16(uint16_t minValue = 0, uint16_t maxValue = 0xFFFF); - -private: - uint32_t m_seed; -}; - diff --git a/Storage.cpp b/Storage.cpp deleted file mode 100644 index 4ddd18c4..00000000 --- a/Storage.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#include "Storage.h" - -#include "Colorset.h" - -#ifdef HELIOS_EMBEDDED -#include -#endif - -#ifdef HELIOS_CLI -#include -#include -#include -#include -#include -#define STORAGE_FILENAME "Helios.storage" -#endif - -// the index of the crc of the config bytes -#define CONFIG_CRC_INDEX 255 -// the index of the last config byte (or first counting down) -#define CONFIG_START_INDEX 254 - -#ifdef HELIOS_CLI -// whether storage is enabled, default enabled -bool Storage::m_enableStorage = true; -#endif - -bool Storage::init() -{ -#ifdef HELIOS_CLI - // may want this for cli tool - //unlink(STORAGE_FILENAME); -#endif - return true; -} - -bool Storage::read_pattern(uint8_t slot, Pattern &pat) -{ - uint8_t pos = slot * SLOT_SIZE; - if (!check_crc(pos)) { - return false; - } - for (uint8_t i = 0; i < PATTERN_SIZE; ++i) { - ((uint8_t *)&pat)[i] = read_byte(pos + i); - } - return true; -} - -void Storage::write_pattern(uint8_t slot, const Pattern &pat) -{ - uint8_t pos = slot * SLOT_SIZE; - for (uint8_t i = 0; i < PATTERN_SIZE; ++i) { - uint8_t val = ((uint8_t *)&pat)[i]; - uint8_t target = pos + i; - // reads out the byte of the eeprom first to see if it's different - // before writing out the byte -- this is faster than always writing - if (val != read_byte(target)) { - write_byte(target, val); - } - } - write_crc(pos); -} - -void Storage::swap_pattern(uint8_t slot1, uint8_t slot2) -{ - uint8_t pos1 = slot1 * SLOT_SIZE; - uint8_t pos2 = slot2 * SLOT_SIZE; - for (uint8_t i = 0; i < SLOT_SIZE; ++i) { - uint8_t b1 = read_byte(pos1 + i); - uint8_t b2 = read_byte(pos2 + i); - write_byte(pos1 + i, b2); - write_byte(pos2 + i, b1); - } -} - -uint8_t Storage::read_config(uint8_t index) -{ - return read_byte(CONFIG_START_INDEX - index); -} - -void Storage::write_config(uint8_t index, uint8_t val) -{ - write_byte(CONFIG_START_INDEX - index, val); -} - -uint8_t Storage::crc8(uint8_t pos, uint8_t size) -{ - uint8_t hash = 33; // A non-zero initial value - for (uint8_t i = 0; i < size; ++i) { - hash = ((hash << 5) + hash) + read_byte(pos); - } - return hash; -} - -uint8_t Storage::crc_pos(uint8_t pos) -{ - // crc the entire slot except last byte - return crc8(pos, PATTERN_SIZE); -} - -uint8_t Storage::read_crc(uint8_t pos) -{ - // read the last byte of the slot - return read_byte(pos + PATTERN_SIZE); -} - -bool Storage::check_crc(uint8_t pos) -{ - // compare the last byte to the calculated crc - return (read_crc(pos) == crc_pos(pos)); -} - -void Storage::write_crc(uint8_t pos) -{ - // compare the last byte to the calculated crc - write_byte(pos + PATTERN_SIZE, crc_pos(pos)); -} - -void Storage::write_byte(uint8_t address, uint8_t data) -{ -#ifdef HELIOS_EMBEDDED - /* Wait for completion of previous write */ - while(EECR & (1< -#include "HeliosConfig.h" - -class Pattern; - -class Storage -{ -public: - - static bool init(); - - static bool read_pattern(uint8_t slot, Pattern &pat); - static void write_pattern(uint8_t slot, const Pattern &pat); - - static void swap_pattern(uint8_t slot1, uint8_t slot2); - - static uint8_t read_config(uint8_t index); - static void write_config(uint8_t index, uint8_t val); - -#ifdef HELIOS_CLI - // toggle storage on/off - static void enableStorage(bool enabled) { m_enableStorage = enabled; } -#endif -private: - static uint8_t crc8(uint8_t pos, uint8_t size); - static uint8_t crc_pos(uint8_t pos); - static uint8_t read_crc(uint8_t pos); - static bool check_crc(uint8_t pos); - static void write_crc(uint8_t pos); - static void write_byte(uint8_t address, uint8_t data); - static uint8_t read_byte(uint8_t address); - -#ifdef HELIOS_CLI - // whether storage is enabled - static bool m_enableStorage; -#endif -}; - -#endif diff --git a/TimeControl.cpp b/TimeControl.cpp deleted file mode 100644 index 04c98a87..00000000 --- a/TimeControl.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include "TimeControl.h" - -#include - -#include "Timings.h" - -#include "Led.h" - -#ifdef HELIOS_EMBEDDED -#include -#include -#ifdef HELIOS_ARDUINO -#include -#endif -#endif - -#ifdef HELIOS_CLI -#include -#include -uint64_t start = 0; -// convert seconds and nanoseconds to microseconds -#define SEC_TO_US(sec) ((sec)*1000000) -#define NS_TO_US(ns) ((ns)/1000) -#endif - -// static members -uint32_t Time::m_curTick = 0; -// the last frame timestamp -uint32_t Time::m_prevTime = 0; - -#ifdef HELIOS_CLI -// whether timestep is enabled, default enabled -bool Time::m_enableTimestep = true; -#endif - -bool Time::init() -{ - m_prevTime = microseconds(); - m_curTick = 0; - return true; -} - -void Time::cleanup() -{ -} - -void Time::tickClock() -{ - // tick clock forward - m_curTick++; - -#ifdef HELIOS_CLI - if (!m_enableTimestep) { - return; - } - // the rest of this only runs inside vortexlib because on the duo the tick runs in the - // tcb timer callback instead of in a busy loop constantly checking microseconds() - // perform timestep - uint32_t elapsed_us; - uint32_t us; - do { - us = microseconds(); - // detect rollover of microsecond counter - if (us < m_prevTime) { - // calculate wrapped around difference - elapsed_us = (uint32_t)((UINT32_MAX - m_prevTime) + us); - } else { - // otherwise calculate regular difference - elapsed_us = (uint32_t)(us - m_prevTime); - } - // if building anywhere except visual studio then we can run alternate sleep code - // because in visual studio + windows it's better to just spin and check the high - // resolution clock instead of trying to sleep for microseconds. - // 1000us per ms, divided by tickrate gives - // the number of microseconds per tick - } while (elapsed_us < (1000000 / TICKRATE)); - - // store current time - m_prevTime = microseconds(); -#endif -} - -uint32_t Time::microseconds() -{ -#ifdef HELIOS_CLI - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC_RAW, &ts); - uint64_t us = SEC_TO_US((uint64_t)ts.tv_sec) + NS_TO_US((uint64_t)ts.tv_nsec); - return (unsigned long)us; -#else -#ifdef HELIOS_ARDUINO - return micros(); -#else - // TODO: microseconds on attiny85 - return 0; -#endif -#endif -} - -#ifdef HELIOS_EMBEDDED -__attribute__((noinline)) -#endif -void -Time::delayMicroseconds(uint32_t us) -{ -#ifdef HELIOS_EMBEDDED -#if F_CPU >= 16000000L - // For the ATtiny85 running at 16MHz - - // The loop takes 3 cycles per iteration - us *= 2; // 0.5us per iteration - - // Subtract the overhead of the function call and loop setup - // Assuming approximately 5 cycles overhead - us -= 5; // Simplified subtraction - - // Assembly loop for delay - __asm__ __volatile__( - "1: sbiw %0, 1" - "\n\t" // 2 cycles - "nop" - "\n\t" // 1 cycle - "brne 1b" : "=w"(us) : "0"(us) // 2 cycles - ); - -#elif F_CPU >= 8000000L - // For the ATtiny85 running at 8MHz - - // The loop takes 4 cycles per iteration - us <<= 1; // 1us per iteration - - // Subtract the overhead of the function call and loop setup - // Assuming approximately 6 cycles overhead - us -= 6; // Simplified subtraction - - // Assembly loop for delay - __asm__ __volatile__( - "1: sbiw %0, 1" - "\n\t" // 2 cycles - "rjmp .+0" - "\n\t" // 2 cycles - "brne 1b" : "=w"(us) : "0"(us) // 2 cycles - ); -#endif - -#else - uint32_t newtime = microseconds() + us; - while (microseconds() < newtime) - { - // busy loop - } -#endif -} - -void Time::delayMilliseconds(uint32_t ms) -{ -#ifdef HELIOS_CLI - usleep(ms * 1000); -#else - // not very accurate - for (uint16_t i = 0; i < ms; ++i) { - delayMicroseconds(1000); - } -#endif -} diff --git a/TimeControl.h b/TimeControl.h deleted file mode 100644 index b5f316d8..00000000 --- a/TimeControl.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef TIME_CONTROL_H -#define TIME_CONTROL_H - -#include - -#include "HeliosConfig.h" - -// macros to convert milliseconds and seconds to measures of ticks -#define MS_TO_TICKS(ms) (uint32_t)(((uint32_t)(ms) * TICKRATE) / 1000) -#define SEC_TO_TICKS(s) (uint32_t)((uint32_t)(s) * TICKRATE) - -class Time -{ - // private unimplemented constructor - Time(); - -public: - // opting for static class here because there should only ever be one - // Settings control object and I don't like singletons - static bool init(); - static void cleanup(); - - // tick the clock forward to millis() - static void tickClock(); - - // get the current tick, offset by any active simulation (simulation only exists in vortexlib) - // Exposing this in the header seems to save on space a non negligible amount, it is used a lot - // and exposing in the header probably allows the compiler to optimize away repititive calls - static uint32_t getCurtime() { return m_curTick; } - - // Current microseconds since startup, only use this for things like measuring rapid data transfer timings. - // If you just need to perform regular time checks for a pattern or some logic then use getCurtime() and measure - // time in ticks, use the SEC_TO_TICKS() or MS_TO_TICKS() macros to convert timings to measures of ticks for - // purpose of comparing against getCurtime() - static uint32_t microseconds(); - - // delay for some number of microseconds or milliseconds, these are bad - static void delayMicroseconds(uint32_t us); - static void delayMilliseconds(uint32_t ms); - -#ifdef HELIOS_CLI - // toggle timestep on/off - static void enableTimestep(bool enabled) { m_enableTimestep = enabled; } -#endif - -private: - // global tick counter - static uint32_t m_curTick; - // the last frame timestamp - static uint32_t m_prevTime; - -#ifdef HELIOS_CLI - // whether timestep is enabled - static bool m_enableTimestep; -#endif -}; - -#endif - diff --git a/Timer.cpp b/Timer.cpp deleted file mode 100644 index 48dd685b..00000000 --- a/Timer.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include - -#include "Timer.h" - -#include "TimeControl.h" - -Timer::Timer() : - m_alarm(0), - m_startTime(0) -{ -} - -Timer::~Timer() -{ -} - -void Timer::init(uint8_t alarm) -{ - reset(); - m_alarm = alarm; - start(); -} - -void Timer::start(uint32_t offset) -{ - // reset the start time - m_startTime = Time::getCurtime() + offset; -} - -void Timer::reset() -{ - m_alarm = 0; - m_startTime = 0; -} - -bool Timer::alarm() -{ - if (!m_alarm) { - return false; - } - uint32_t now = Time::getCurtime(); - // time since start (forward or backwards) - int32_t timeDiff = (int32_t)(int64_t)(now - m_startTime); - if (timeDiff < 0) { - return false; - } - // if no time passed it's first alarm that is starting - if (timeDiff == 0) { - return true; - } - // if the current alarm duration is not a multiple of the current tick - if (m_alarm && (timeDiff % m_alarm) != 0) { - // then the alarm was not hit - return false; - } - // update the start time of the timer - m_startTime = now; - return true; -} diff --git a/Timer.h b/Timer.h deleted file mode 100644 index 92a19a52..00000000 --- a/Timer.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef TIMER_H -#define TIMER_H - -#include - -class Timer -{ -public: - Timer(); - ~Timer(); - - // init a timer with a number of alarms and optionally start it - void init(uint8_t alarm); - - // start the timer but don't change current alarm, this shifts - // the timer startTime but does not reset it's alarm state - void start(uint32_t offset = 0); - // delete all alarms from the timer and reset - void reset(); - // Will return the true if the timer hit - bool alarm(); - -private: - // the alarm - uint32_t m_alarm; - // start time in microseconds - uint32_t m_startTime; -}; - -#endif diff --git a/Timings.h b/Timings.h deleted file mode 100644 index 989ec6dc..00000000 --- a/Timings.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef TIMINGS_H -#define TIMINGS_H - -// Strobe Timings -// -// Below are timings for all different kinds of standard -// strobes as defined by the gloving community. All times -// are in milliseconds. - -// Basic Strobe -#define STROBE_ON_DURATION 6 -#define STROBE_OFF_DURATION 6 - -// Hyperstrobe -#define HYPERSTROBE_ON_DURATION 16 -#define HYPERSTROBE_OFF_DURATION 20 - -// Dops -#define PICOSTROBE_ON_DURATION 6 -#define PICOSTROBE_OFF_DURATION 40 - -// Dopy -#define DOPS_ON_DURATION 1 -#define DOPS_OFF_DURATION 10 - -// Ultradops -#define ULTRADOPS_ON_DURATION 1 -#define ULTRADOPS_OFF_DURATION 3 - -// Strobie -#define STROBIE_ON_DURATION 2 -#define STROBIE_OFF_DURATION 28 - -// Signal -#define SIGNAL_ON_DURATION 10 -#define SIGNAL_OFF_DURATION 296 - -// A blink speed good for blends -#define BLEND_ON_DURATION 2 -#define BLEND_OFF_DURATION 13 - -// Ribbon -#define RIBBON_DURATION 6 - -#endif From 3bb2d3eeb9223c16267358ebc7274672230bd19d Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 19 Jan 2024 15:58:34 -0800 Subject: [PATCH 2/3] oops --- Helios/Button.cpp | 250 +++++++++++++ Helios/Button.h | 111 ++++++ Helios/ColorConstants.h | 161 +++++++++ Helios/Colorset.cpp | 358 ++++++++++++++++++ Helios/Colorset.h | 132 +++++++ Helios/Colortypes.cpp | 409 +++++++++++++++++++++ Helios/Colortypes.h | 93 +++++ Helios/Helios.cpp | 782 ++++++++++++++++++++++++++++++++++++++++ Helios/Helios.h | 106 ++++++ Helios/HeliosConfig.h | 115 ++++++ Helios/Led.cpp | 141 ++++++++ Helios/Led.h | 57 +++ Helios/Pattern.cpp | 294 +++++++++++++++ Helios/Pattern.h | 140 +++++++ Helios/Patterns.cpp | 221 ++++++++++++ Helios/Patterns.h | 78 ++++ Helios/Random.cpp | 45 +++ Helios/Random.h | 20 + Helios/Storage.cpp | 204 +++++++++++ Helios/Storage.h | 42 +++ Helios/TimeControl.cpp | 165 +++++++++ Helios/TimeControl.h | 59 +++ Helios/Timer.cpp | 59 +++ Helios/Timer.h | 30 ++ Helios/Timings.h | 45 +++ 25 files changed, 4117 insertions(+) create mode 100644 Helios/Button.cpp create mode 100644 Helios/Button.h create mode 100644 Helios/ColorConstants.h create mode 100644 Helios/Colorset.cpp create mode 100644 Helios/Colorset.h create mode 100644 Helios/Colortypes.cpp create mode 100644 Helios/Colortypes.h create mode 100644 Helios/Helios.cpp create mode 100644 Helios/Helios.h create mode 100644 Helios/HeliosConfig.h create mode 100644 Helios/Led.cpp create mode 100644 Helios/Led.h create mode 100644 Helios/Pattern.cpp create mode 100644 Helios/Pattern.h create mode 100644 Helios/Patterns.cpp create mode 100644 Helios/Patterns.h create mode 100644 Helios/Random.cpp create mode 100644 Helios/Random.h create mode 100644 Helios/Storage.cpp create mode 100644 Helios/Storage.h create mode 100644 Helios/TimeControl.cpp create mode 100644 Helios/TimeControl.h create mode 100644 Helios/Timer.cpp create mode 100644 Helios/Timer.h create mode 100644 Helios/Timings.h diff --git a/Helios/Button.cpp b/Helios/Button.cpp new file mode 100644 index 00000000..5ee6c4e1 --- /dev/null +++ b/Helios/Button.cpp @@ -0,0 +1,250 @@ +#include "Button.h" +#include "TimeControl.h" + +#ifdef HELIOS_EMBEDDED +#include +#include +#ifdef HELIOS_ARDUINO +#include +#endif +#define BUTTON_PIN 3 +#define BUTTON_PORT 2 +#endif + +#include "Helios.h" + +// static members of Button +uint32_t Button::m_pressTime = 0; +uint32_t Button::m_releaseTime = 0; +uint32_t Button::m_holdDuration = 0; +uint32_t Button::m_releaseDuration = 0; +uint8_t Button::m_releaseCount = 0; +bool Button::m_buttonState = false; +bool Button::m_newPress = false; +bool Button::m_newRelease = false; +bool Button::m_isPressed = false; +bool Button::m_shortClick = false; +bool Button::m_longClick = false; + +#ifdef HELIOS_CLI +// an input queue for the button, each tick one even is processed +// out of this queue and used to produce input +std::queue Button::m_inputQueue; +// the virtual pin state +bool Button::m_pinState = false; +// whether the button is waiting to wake the device +bool Button::m_enableWake = false; +#endif + +// initialize a new button object with a pin number +bool Button::init() +{ + m_pressTime = 0; + m_releaseTime = 0; + m_holdDuration = 0; + m_releaseDuration = 0; + m_newPress = false; + m_newRelease = false; + m_shortClick = false; + m_longClick = false; + m_buttonState = check(); + m_releaseCount = !m_buttonState; + m_isPressed = m_buttonState; +#ifdef HELIOS_CLI + m_pinState = false; + m_enableWake = false; +#endif +#ifdef HELIOS_EMBEDDED +#ifdef HELIOS_ARDUINO + pinMode(3, INPUT); +#else + // turn off wake + PCMSK &= ~(1 << PCINT3); + GIMSK &= ~(1 << PCIE); +#endif +#endif + return true; +} + +// enable wake on press +void Button::enableWake() +{ +#ifdef HELIOS_EMBEDDED + // Configure INT0 to trigger on falling edge + PCMSK |= (1 << PCINT3); + GIMSK |= (1 << PCIE); + sei(); +#else // HELIOS_CLI + m_enableWake = false; +#endif +} + +#ifdef HELIOS_EMBEDDED +ISR(PCINT0_vect) { + PCMSK &= ~(1 << PCINT3); + GIMSK &= ~(1 << PCIE); + Helios::wakeup(); +} +#endif + +// directly poll the pin for whether it's pressed right now +bool Button::check() +{ +#ifdef HELIOS_EMBEDDED +#ifdef HELIOS_ARDUINO + return digitalRead(3) == HIGH; +#else + return (PINB & (1 << 3)) != 0; +#endif +#elif defined(HELIOS_CLI) + // then just return the pin state as-is, the input event may have + // adjusted this value + return m_pinState; +#endif +} + +// poll the button pin and update the state of the button object +void Button::update() +{ +#ifdef HELIOS_CLI + // process any pre-input events in the queue + bool processed_pre = processPreInput(); +#endif + + bool newButtonState = check(); + m_newPress = false; + m_newRelease = false; + if (newButtonState != m_buttonState) { + m_buttonState = newButtonState; + m_isPressed = m_buttonState; + if (m_isPressed) { + m_pressTime = Time::getCurtime(); + m_newPress = true; + } else { + m_releaseTime = Time::getCurtime(); + m_newRelease = true; + m_releaseCount++; + } + } + if (m_isPressed) { + m_holdDuration = (Time::getCurtime() >= m_pressTime) ? (uint32_t)(Time::getCurtime() - m_pressTime) : 0; + } else { + m_releaseDuration = (Time::getCurtime() >= m_releaseTime) ? (uint32_t)(Time::getCurtime() - m_releaseTime) : 0; + } + m_shortClick = (m_newRelease && (m_holdDuration <= SHORT_CLICK_THRESHOLD)); + m_longClick = (m_newRelease && (m_holdDuration > SHORT_CLICK_THRESHOLD)); + +#ifdef HELIOS_CLI + // if there was no pre-input event this tick, process a post input event + // to ensure there is only one event per tick processed + if (!processed_pre) { + processPostInput(); + } + + if (m_enableWake) { + if (m_isPressed || m_shortClick || m_longClick) { + Helios::wakeup(); + } + } +#endif +} + +#ifdef HELIOS_CLI +bool Button::processPreInput() +{ + if (!m_inputQueue.size()) { + return false; + } + char command = m_inputQueue.front(); + switch (command) { + case 'p': // press + Button::doPress(); + break; + case 'r': // release + Button::doRelease(); + break; + case 't': // toggle + Button::doToggle(); + break; + case 'q': // quit + Helios::terminate(); + break; + case 'w': // wait + // wait is pre input I guess + break; + default: + // return here! do not pop the queue + // do not process post input events + return false; + } + // now pop whatever pre-input command was processed + m_inputQueue.pop(); + return true; +} + +bool Button::processPostInput() +{ + if (!m_inputQueue.size()) { + // probably processed the pre-input event already + return false; + } + // process input queue from the command line + char command = m_inputQueue.front(); + switch (command) { + case 'c': // click button + Button::doShortClick(); + break; + case 'l': // long click button + Button::doLongClick(); + break; + default: + // should never happen + return false; + } + m_inputQueue.pop(); + return true; +} + +void Button::doShortClick() +{ + m_shortClick = true; + m_releaseCount++; +} + +void Button::doLongClick() +{ + m_longClick = true; + m_releaseCount++; +} + +// this will actually press down the button, it's your responsibility to wait +// for the appropriate number of ticks and then release the button +void Button::doPress() +{ + m_pinState = true; +} + +void Button::doRelease() +{ + m_pinState = false; +} + +void Button::doToggle() +{ + m_pinState = !m_pinState; +} + +// queue up an input event for the button +void Button::queueInput(char input) +{ + m_inputQueue.push(input); +} + +uint32_t Button::inputQueueSize() +{ + return m_inputQueue.size(); +} +#endif + +// global button +Button button; diff --git a/Helios/Button.h b/Helios/Button.h new file mode 100644 index 00000000..600b9748 --- /dev/null +++ b/Helios/Button.h @@ -0,0 +1,111 @@ +#include + +#ifdef HELIOS_CLI +#include +#endif + +class Button +{ +public: + // initialize a new button object with a pin number + static bool init(); + // directly poll the pin for whether it's pressed right now + static bool check(); + // poll the button pin and update the state of the button object + static void update(); + + // whether the button was pressed this tick + static bool onPress() { return m_newPress; } + // whether the button was released this tick + static bool onRelease() { return m_newRelease; } + // whether the button is currently pressed + static bool isPressed() { return m_isPressed; } + + // whether the button was shortclicked this tick + static bool onShortClick() { return m_shortClick; } + // whether the button was long clicked this tick + static bool onLongClick() { return m_longClick; } + + // when the button was last pressed + static uint32_t pressTime() { return m_pressTime; } + // when the button was last released + static uint32_t releaseTime() { return m_releaseTime; } + + // how long the button is currently or was last held down (in ticks) + static uint32_t holdDuration() { return m_holdDuration; } + // how long the button is currently or was last released for (in ticks) + static uint32_t releaseDuration() { return m_releaseDuration; } + + // the number of releases + static uint8_t releaseCount() { return m_releaseCount; } + + // enable wake on press + static void enableWake(); + +#ifdef HELIOS_CLI + // these will 'inject' a short/long click without actually touching the + // button state, it's important that code uses 'onShortClick' or + // 'onLongClick' to capture this injected input event. Code that uses + // for example: 'button.holdDuration() >= threshold && button.onRelease()' + // will never trigger because the injected input event doesn't actually + // press the button or change the button state it just sets the 'shortClick' + // or 'longClick' values accordingly + static void doShortClick(); + static void doLongClick(); + + // this will actually press down the button, it's your responsibility to wait + // for the appropriate number of ticks and then release the button + static void doPress(); + static void doRelease(); + static void doToggle(); + + // queue up an input event for the button + static void queueInput(char input); + static uint32_t inputQueueSize(); +#endif + +private: + // ======================================== + // state data that is populated each check + + // the timestamp of when the button was pressed + static uint32_t m_pressTime; + // the timestamp of when the button was released + static uint32_t m_releaseTime; + + // the last hold duration + static uint32_t m_holdDuration; + // the last release duration + static uint32_t m_releaseDuration; + + // the number of times released, will overflow at 255 + static uint8_t m_releaseCount; + + // the active state of the button + static bool m_buttonState; + + // whether pressed this tick + static bool m_newPress; + // whether released this tick + static bool m_newRelease; + // whether currently pressed + static bool m_isPressed; + // whether a short click occurred + static bool m_shortClick; + // whether a long click occurred + static bool m_longClick; + +#ifdef HELIOS_CLI + // process pre or post input events from the queue + static bool processPreInput(); + static bool processPostInput(); + + // an input queue for the button, each tick one even is processed + // out of this queue and used to produce input + static std::queue m_inputQueue; + // the virtual pin state that is polled instead of a digital pin + static bool m_pinState; + // whether the button is waiting to wake the device + static bool m_enableWake; +#endif +}; diff --git a/Helios/ColorConstants.h b/Helios/ColorConstants.h new file mode 100644 index 00000000..8801d4ac --- /dev/null +++ b/Helios/ColorConstants.h @@ -0,0 +1,161 @@ +#pragma once + +#include + +// ==================================================================================================== +// Color Constants +// +// This file defines constants for colors as we best see them for the Helios +// Engine + +// Pre-defined hue values +#define HUE_RED 0 +#define HUE_CORAL_ORANGE 5 +#define HUE_ORANGE 10 +#define HUE_YELLOW 20 + +#define HUE_LIME_GREEN 70 +#define HUE_GREEN 85 +#define HUE_SEAFOAM 95 +#define HUE_TURQUOISE 120 + +#define HUE_ICE_BLUE 142 +#define HUE_LIGHT_BLUE 158 +#define HUE_BLUE 170 +#define HUE_ROYAL_BLUE 175 + +#define HUE_PURPLE 192 +#define HUE_PINK 205 +#define HUE_HOT_PINK 225 +#define HUE_MAGENTA 245 + +// Helios Colors +#define RGB_OFF (uint32_t)0x000000 // 0 0 0 +#define RGB_WHITE (uint32_t)0xFFFFFF // 255 255 255 +#define RGB_RED (uint32_t)0xFF0000 // 255, 0, 0 +#define RGB_CORAL_ORANGE (uint32_t)0xFF1E00 // 255, 30, 0 +#define RGB_ORANGE (uint32_t)0xFF3C00 // 255, 60, 0 +#define RGB_YELLOW (uint32_t)0xFF7800 // 255, 120, 0 +#define RGB_LIME_GREEN (uint32_t)0x59FF00 // 89, 255, 0 +#define RGB_GREEN (uint32_t)0x00FF00 // 0, 255, 0 +#define RGB_SEAFOAM (uint32_t)0x00FF3C // 0, 255, 60 +#define RGB_TURQUOISE (uint32_t)0x00FFD1 // 0, 255, 209 +#define RGB_ICE_BLUE (uint32_t)0x00A7FF // 0, 167, 255 +#define RGB_LIGHT_BLUE (uint32_t)0x0047FF // 0, 71, 255 +#define RGB_BLUE (uint32_t)0x0000FF // 0, 0, 255 +#define RGB_ROYAL_BLUE (uint32_t)0x1D00FF // 29, 0, 255 +#define RGB_PURPLE (uint32_t)0x8300FF // 131, 0, 255 +#define RGB_PINK (uint32_t)0xD200FF // 210, 0, 255 +#define RGB_HOT_PINK (uint32_t)0xFF00B4 // 255, 0, 180 +#define RGB_MAGENTA (uint32_t)0xFF003C // 255, 0, 60 + +// Helios Medium Brightness Colors +#define RGB_WHITE_BRI_MEDIUM (uint32_t)0x787878 // 120 120 120 +#define RGB_RED_BRI_MEDIUM (uint32_t)0x780000 // 120, 0, 0 +#define RGB_CORAL_ORANGE_BRI_MEDIUM (uint32_t)0x780E00 // 120, 14, 0 +#define RGB_ORANGE_BRI_MEDIUM (uint32_t)0x781C00 // 120, 28, 0 +#define RGB_YELLOW_BRI_MEDIUM (uint32_t)0x783800 // 120, 56, 0 +#define RGB_LIME_GREEN_BRI_MEDIUM (uint32_t)0x297800 // 41, 120, 0 +#define RGB_GREEN_BRI_MEDIUM (uint32_t)0x007800 // 0, 120, 0 +#define RGB_SEAFOAM_BRI_MEDIUM (uint32_t)0x00781C // 0, 120, 28 +#define RGB_TURQUOISE_BRI_MEDIUM (uint32_t)0x007862 // 0, 120, 98 +#define RGB_ICE_BLUE_BRI_MEDIUM (uint32_t)0x004E78 // 0, 78, 120 +#define RGB_LIGHT_BLUE_BRI_MEDIUM (uint32_t)0x002178 // 0, 33, 120 +#define RGB_BLUE_BRI_MEDIUM (uint32_t)0x000078 // 0, 0, 120 +#define RGB_ROYAL_BLUE_BRI_MEDIUM (uint32_t)0x0D0078 // 13, 0, 120 +#define RGB_PURPLE_BRI_MEDIUM (uint32_t)0x3D0078 // 61, 0, 120 +#define RGB_PINK_BRI_MEDIUM (uint32_t)0x620078 // 98, 0, 120 +#define RGB_HOT_PINK_BRI_MEDIUM (uint32_t)0x780054 // 120, 0, 84 +#define RGB_MAGENTA_BRI_MEDIUM (uint32_t)0x78001C // 120, 0, 28 + +// Helios Low Brightness Colors +#define RGB_WHITE_BRI_LOW (uint32_t)0x3C3C3C // 60 60 60 +#define RGB_RED_BRI_LOW (uint32_t)0x3C0000 // 60, 0, 0 +#define RGB_CORAL_ORANGE_BRI_LOW (uint32_t)0x3C0700 // 60, 7, 0 +#define RGB_ORANGE_BRI_LOW (uint32_t)0x3C0E00 // 60, 14, 0 +#define RGB_YELLOW_BRI_LOW (uint32_t)0x3C1C00 // 60, 28, 0 +#define RGB_LIME_GREEN_BRI_LOW (uint32_t)0x143C00 // 20, 60, 0 +#define RGB_GREEN_BRI_LOW (uint32_t)0x003C00 // 0, 60, 0 +#define RGB_SEAFOAM_BRI_LOW (uint32_t)0x003C0E // 0, 60, 14 +#define RGB_TURQUOISE_BRI_LOW (uint32_t)0x003C31 // 0, 60, 49 +#define RGB_ICE_BLUE_BRI_LOW (uint32_t)0x00273C // 0, 39, 60 +#define RGB_LIGHT_BLUE_BRI_LOW (uint32_t)0x00103C // 0, 16, 60 +#define RGB_BLUE_BRI_LOW (uint32_t)0x00003C // 0, 0, 60 +#define RGB_ROYAL_BLUE_BRI_LOW (uint32_t)0x06003C // 6, 0, 60 +#define RGB_PURPLE_BRI_LOW (uint32_t)0x1E003C // 30, 0, 60 +#define RGB_PINK_BRI_LOW (uint32_t)0x31003C // 49, 0, 60 +#define RGB_HOT_PINK_BRI_LOW (uint32_t)0x3C002A // 60, 0, 42 +#define RGB_MAGENTA_BRI_LOW (uint32_t)0x3C000E // 60, 0, 14 + +// Helios Lowest Brightness Colors +#define RGB_WHITE_BRI_LOWEST (uint32_t)0x0A0A0A // 10 10 10 +#define RGB_RED_BRI_LOWEST (uint32_t)0x0A0000 // 10, 0, 0 +#define RGB_CORAL_ORANGE_BRI_LOWEST (uint32_t)0x0A0100 // 10, 1, 0 +#define RGB_ORANGE_BRI_LOWEST (uint32_t)0x0A0200 // 10, 2, 0 +#define RGB_YELLOW_BRI_LOWEST (uint32_t)0x0A0400 // 10, 4, 0 +#define RGB_LIME_GREEN_BRI_LOWEST (uint32_t)0x030A00 // 3, 10, 0 +#define RGB_GREEN_BRI_LOWEST (uint32_t)0x000A00 // 0, 10, 0 +#define RGB_SEAFOAM_BRI_LOWEST (uint32_t)0x000A02 // 0, 10, 2 +#define RGB_TURQUOISE_BRI_LOWEST (uint32_t)0x000A08 // 0, 10, 8 +#define RGB_ICE_BLUE_BRI_LOWEST (uint32_t)0x00060A // 0, 6, 10 +#define RGB_LIGHT_BLUE_BRI_LOWEST (uint32_t)0x00020A // 0, 2, 10 +#define RGB_BLUE_BRI_LOWEST (uint32_t)0x00000A // 0, 0, 10 +#define RGB_ROYAL_BLUE_BRI_LOWEST (uint32_t)0x01000A // 1, 0, 10 +#define RGB_PURPLE_BRI_LOWEST (uint32_t)0x05000A // 5, 0, 10 +#define RGB_PINK_BRI_LOWEST (uint32_t)0x08000A // 8, 0, 10 +#define RGB_HOT_PINK_BRI_LOWEST (uint32_t)0x0A0007 // 10, 0, 7 +#define RGB_MAGENTA_BRI_LOWEST (uint32_t)0x0A0002 // 10, 0, 2 + +// Helios Medium Saturation Colors +#define RGB_RED_SAT_MEDIUM (uint32_t)0xFF2222 // 255, 34, 34 +#define RGB_CORAL_ORANGE_SAT_MEDIUM (uint32_t)0xFF3C22 // 255, 60, 34 +#define RGB_ORANGE_SAT_MEDIUM (uint32_t)0xFF5622 // 255, 86, 34 +#define RGB_YELLOW_SAT_MEDIUM (uint32_t)0xFF8A22 // 255, 138, 34 +#define RGB_LIME_GREEN_SAT_MEDIUM (uint32_t)0x6FFF22 // 111, 255, 34 +#define RGB_GREEN_SAT_MEDIUM (uint32_t)0x22FF22 // 34, 255, 34 +#define RGB_SEAFOAM_SAT_MEDIUM (uint32_t)0x22FF56 // 34, 255, 86 +#define RGB_TURQUOISE_SAT_MEDIUM (uint32_t)0x22FFD7 // 34, 255, 215 +#define RGB_ICE_BLUE_SAT_MEDIUM (uint32_t)0x22B3FF // 34, 179, 255 +#define RGB_LIGHT_BLUE_SAT_MEDIUM (uint32_t)0x2260FF // 34, 96, 255 +#define RGB_BLUE_SAT_MEDIUM (uint32_t)0x2222FF // 34, 34, 255 +#define RGB_ROYAL_BLUE_SAT_MEDIUM (uint32_t)0x3C22FF // 60, 34, 255 +#define RGB_PURPLE_SAT_MEDIUM (uint32_t)0x9422FF // 148, 34, 255 +#define RGB_PINK_SAT_MEDIUM (uint32_t)0xD822FF // 216, 34, 255 +#define RGB_HOT_PINK_SAT_MEDIUM (uint32_t)0xFF22BE // 255, 34, 190 +#define RGB_MAGENTA_SAT_MEDIUM (uint32_t)0xFF2256 // 255, 34, 86 + +// Helios Low Saturation Colors +#define RGB_RED_SAT_LOW (uint32_t)0xFF5555 // 255, 85, 85 +#define RGB_CORAL_ORANGE_SAT_LOW (uint32_t)0xFF6955 // 255, 105, 85 +#define RGB_ORANGE_SAT_LOW (uint32_t)0xFF7D55 // 255, 125, 85 +#define RGB_YELLOW_SAT_LOW (uint32_t)0xFFA555 // 255, 165, 85 +#define RGB_LIME_GREEN_SAT_LOW (uint32_t)0x90FF55 // 144, 255, 85 +#define RGB_GREEN_SAT_LOW (uint32_t)0x55FF55 // 85, 255, 85 +#define RGB_SEAFOAM_SAT_LOW (uint32_t)0x55FF7D // 85, 255, 125 +#define RGB_TURQUOISE_SAT_LOW (uint32_t)0x55FFE0 // 85, 255, 224 +#define RGB_ICE_BLUE_SAT_LOW (uint32_t)0x55C4FF // 85, 196, 255 +#define RGB_LIGHT_BLUE_SAT_LOW (uint32_t)0x5584FF // 85, 132, 255 +#define RGB_BLUE_SAT_LOW (uint32_t)0x5555FF // 85, 85, 255 +#define RGB_ROYAL_BLUE_SAT_LOW (uint32_t)0x6855FF // 104, 85, 255 +#define RGB_PURPLE_SAT_LOW (uint32_t)0xAC55FF // 172, 85, 255 +#define RGB_PINK_SAT_LOW (uint32_t)0xE055FF // 224, 85, 255 +#define RGB_HOT_PINK_SAT_LOW (uint32_t)0xFF55CD // 255, 85, 205 +#define RGB_MAGENTA_SAT_LOW (uint32_t)0xFF557D // 255, 85, 125 + +// Helios Lowest Saturation Colors +#define RGB_RED_SAT_LOWEST (uint32_t)0xFF7D7D // 255, 125, 125 +#define RGB_CORAL_ORANGE_SAT_LOWEST (uint32_t)0xFF8C7D // 255, 140, 125 +#define RGB_ORANGE_SAT_LOWEST (uint32_t)0xFF9B7D // 255, 155, 125 +#define RGB_YELLOW_SAT_LOWEST (uint32_t)0xFFBA7D // 255, 186, 125 +#define RGB_LIME_GREEN_SAT_LOWEST (uint32_t)0xAAFF7D // 170, 255, 125 +#define RGB_GREEN_SAT_LOWEST (uint32_t)0x7DFF7D // 125, 255, 125 +#define RGB_SEAFOAM_SAT_LOWEST (uint32_t)0x7DFF9B // 125, 255, 155 +#define RGB_TURQUOISE_SAT_LOWEST (uint32_t)0x7DFFE7 // 125, 255, 231 +#define RGB_ICE_BLUE_SAT_LOWEST (uint32_t)0x7DD2FF // 125, 210, 255 +#define RGB_LIGHT_BLUE_SAT_LOWEST (uint32_t)0x7DA1FF // 125, 161, 255 +#define RGB_BLUE_SAT_LOWEST (uint32_t)0x7D7DFF // 125, 125, 255 +#define RGB_ROYAL_BLUE_SAT_LOWEST (uint32_t)0x8B7DFF // 139, 125, 255 +#define RGB_PURPLE_SAT_LOWEST (uint32_t)0xBF7DFF // 191, 125, 255 +#define RGB_PINK_SAT_LOWEST (uint32_t)0xE87DFF // 232, 125, 255 +#define RGB_HOT_PINK_SAT_LOWEST (uint32_t)0xFF7DD8 // 255, 125, 216 +#define RGB_MAGENTA_SAT_LOWEST (uint32_t)0xFF7D9B // 255, 125, 155 diff --git a/Helios/Colorset.cpp b/Helios/Colorset.cpp new file mode 100644 index 00000000..e61e83ec --- /dev/null +++ b/Helios/Colorset.cpp @@ -0,0 +1,358 @@ +#include "Colorset.h" + +#include "Random.h" + +#include + +// when no color is selected in the colorset the index is this +// then when you call getNext() for the first time it returns +// the 0th color in the colorset and after the index will be 0 +#define INDEX_NONE 255 + +Colorset::Colorset() : + m_palette(), + m_numColors(0), + m_curIndex(INDEX_NONE) +{ + init(); +} + +Colorset::Colorset(RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, + RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8) : + Colorset() +{ + init(c1, c2, c3, c4, c5, c6, c7, c8); +} + +Colorset::Colorset(uint8_t numCols, const uint32_t *cols) : + Colorset() +{ + if (numCols > NUM_COLOR_SLOTS) { + numCols = NUM_COLOR_SLOTS; + } + for (uint8_t i = 0; i < numCols; ++i) { + addColor(RGBColor(cols[i])); + } +} + +Colorset::Colorset(const Colorset &other) : + Colorset() +{ + // invoke = operator + *this = other; +} + +Colorset::~Colorset() +{ + clear(); +} + +bool Colorset::operator==(const Colorset &other) const +{ + // only compare the palettes for equality + return (m_numColors == other.m_numColors) && + (memcmp(m_palette, other.m_palette, m_numColors * sizeof(RGBColor)) == 0); +} + +bool Colorset::operator!=(const Colorset &other) const +{ + return !operator==(other); +} + +void Colorset::init(RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, + RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8) +{ + // clear any existing colors + clear(); + // would be nice if we could do this another way + if (!c1.empty()) addColor(c1); + if (!c2.empty()) addColor(c2); + if (!c3.empty()) addColor(c3); + if (!c4.empty()) addColor(c4); + if (!c5.empty()) addColor(c5); + if (!c6.empty()) addColor(c6); + if (!c7.empty()) addColor(c7); + if (!c8.empty()) addColor(c8); +} + +void Colorset::clear() +{ + memset((void *)m_palette, 0, sizeof(m_palette)); + m_numColors = 0; + resetIndex(); +} + +bool Colorset::equals(const Colorset &set) const +{ + return operator==(set); +} + +bool Colorset::equals(const Colorset *set) const +{ + if (!set) { + return false; + } + return operator==(*set); +} + +RGBColor Colorset::operator[](int index) const +{ + return get(index); +} + +// add a single color +bool Colorset::addColor(RGBColor col) +{ + if (m_numColors >= NUM_COLOR_SLOTS) { + return false; + } + // insert new color and increment number of colors + m_palette[m_numColors] = col; + m_numColors++; + return true; +} + +bool Colorset::addColorHSV(uint8_t hue, uint8_t sat, uint8_t val) +{ + return addColor(HSVColor(hue, sat, val)); +} + +void Colorset::addColorWithValueStyle(Random &ctx, uint8_t hue, uint8_t sat, ValueStyle valStyle, uint8_t numColors, uint8_t colorPos) +{ + if (numColors == 1) { + addColorHSV(hue, sat, ctx.next8(16, 255)); + return; + } + switch (valStyle) { + default: + case VAL_STYLE_RANDOM: + addColorHSV(hue, sat, 85 * ctx.next8(1, 4)); + break; + case VAL_STYLE_LOW_FIRST_COLOR: + if (m_numColors == 0) { + addColorHSV(hue, sat, ctx.next8(0, 86)); + } else { + addColorHSV(hue, sat, 85 * ctx.next8(1, 4)); + } + break; + case VAL_STYLE_HIGH_FIRST_COLOR: + if (m_numColors == 0) { + addColorHSV(hue, sat, 255); + } else { + addColorHSV(hue, sat, ctx.next8(0, 86)); + } + break; + case VAL_STYLE_ALTERNATING: + if (m_numColors % 2 == 0) { + addColorHSV(hue, sat, 255); + } else { + addColorHSV(hue, sat, 85); + } + break; + case VAL_STYLE_ASCENDING: + addColorHSV(hue, sat, (colorPos + 1) * (255 / numColors)); + break; + case VAL_STYLE_DESCENDING: + addColorHSV(hue, sat, 255 - (colorPos * (255 / numColors))); + break; + case VAL_STYLE_CONSTANT: + addColorHSV(hue, sat, 255); + } +} + +void Colorset::removeColor(uint8_t index) +{ + if (index >= m_numColors) { + return; + } + for (uint8_t i = index; i < (m_numColors - 1); ++i) { + m_palette[i] = m_palette[i + 1]; + } + m_palette[--m_numColors].clear(); +} + +uint8_t current_color_mode = Colorset::THEORY; + +void Colorset::randomizeColors(Random &ctx, uint8_t numColors) +{ + current_color_mode = (current_color_mode + 1) % COLOR_MODE_COUNT; + ColorMode mode = (ColorMode)current_color_mode; + clear(); + if (!numColors) { + numColors = ctx.next8(mode == MONOCHROMATIC ? 2 : 1, 9); + } + uint8_t randomizedHue = ctx.next8(); + uint8_t colorGap = 0; + if (mode == THEORY && numColors > 1) { + colorGap = ctx.next8(16, 256 / (numColors - 1)); + } + ValueStyle valStyle = (ValueStyle)ctx.next8(0, VAL_STYLE_COUNT); + // the doubleStyle decides if some colors are added to the set twice + uint8_t doubleStyle = 0; + if (numColors <= 7) { + doubleStyle = (ctx.next8(0, 1)); + } + if (numColors <= 4) { + doubleStyle = (ctx.next8(0, 2)); + } + for (uint8_t i = 0; i < numColors; i++) { + uint8_t hueToUse; + uint8_t valueToUse = 255; + if (mode == THEORY) { + hueToUse = (randomizedHue + (i * colorGap)); + } else if (mode == MONOCHROMATIC) { + hueToUse = randomizedHue; + valueToUse = 255 - (i * (256 / numColors)); + } else { // EVENLY_SPACED + hueToUse = (randomizedHue + (256 / numColors) * i); + } + addColorWithValueStyle(ctx, hueToUse, valueToUse, valStyle, numColors, i); + // double all colors or only first color + if (doubleStyle == 2 || (doubleStyle == 1 && !i)) { + addColorWithValueStyle(ctx, hueToUse, valueToUse, valStyle, numColors, i); + } + } +} + +void Colorset::adjustBrightness(uint8_t fadeby) +{ + for (uint8_t i = 0; i < m_numColors; ++i) { + m_palette[i].adjustBrightness(fadeby); + } +} + +// get a color from the colorset +RGBColor Colorset::get(uint8_t index) const +{ + if (index >= m_numColors) { + return RGBColor(0, 0, 0); + } + return m_palette[index]; +} + +// set an rgb color in a slot, or add a new color if you specify +// a slot higher than the number of colors in the colorset +void Colorset::set(uint8_t index, RGBColor col) +{ + // special case for 'setting' a color at the edge of the palette, + // ie adding a new color when you set an index higher than the max + if (index >= m_numColors) { + if (!addColor(col)) { + //ERROR_LOGF("Failed to add new color at index %u", index); + } + return; + } + m_palette[index] = col; +} + +// skip some amount of colors +void Colorset::skip(int32_t amount) +{ + if (!m_numColors) { + return; + } + // if the colorset hasn't started yet + if (m_curIndex == INDEX_NONE) { + m_curIndex = 0; + } + + // first modulate the amount to skip to be within +/- the number of colors + amount %= (int32_t)m_numColors; + + // max = 3 + // m_curIndex = 2 + // amount = -10 + m_curIndex = ((int32_t)m_curIndex + (int32_t)amount) % (int32_t)m_numColors; + if (m_curIndex > m_numColors) { // must have wrapped + // simply wrap it back + m_curIndex += m_numColors; + } +} + +RGBColor Colorset::cur() +{ + if (m_curIndex >= m_numColors) { + return RGBColor(0, 0, 0); + } + if (m_curIndex == INDEX_NONE) { + return m_palette[0]; + } + return m_palette[m_curIndex]; +} + +void Colorset::setCurIndex(uint8_t index) +{ + if (!m_numColors) { + return; + } + if (index > (m_numColors - 1)) { + return; + } + m_curIndex = index; +} + +void Colorset::resetIndex() +{ + m_curIndex = INDEX_NONE; +} + +RGBColor Colorset::getPrev() +{ + if (!m_numColors) { + return RGB_OFF; + } + // handle wrapping at 0 + if (m_curIndex == 0 || m_curIndex == INDEX_NONE) { + m_curIndex = numColors() - 1; + } else { + m_curIndex--; + } + // return the color + return m_palette[m_curIndex]; +} + +RGBColor Colorset::getNext() +{ + if (!m_numColors) { + return RGB_OFF; + } + // iterate current index, let it wrap at max uint8 + m_curIndex++; + // then modulate the result within max colors + m_curIndex %= numColors(); + // return the color + return m_palette[m_curIndex]; +} + +// peek at the next color but don't iterate +RGBColor Colorset::peek(int32_t offset) const +{ + if (!m_numColors) { + return RGB_OFF; + } + uint8_t nextIndex = 0; + // get index of the next color + if (offset >= 0) { + nextIndex = (m_curIndex + offset) % numColors(); + } else { + if (offset < -1 * (int32_t)(numColors())) { + return RGB_OFF; + } + nextIndex = ((m_curIndex + numColors()) + (int)offset) % numColors(); + } + // return the color + return m_palette[nextIndex]; +} + +bool Colorset::onStart() const +{ + return (m_curIndex == 0); +} + +bool Colorset::onEnd() const +{ + if (!m_numColors) { + return false; + } + return (m_curIndex == m_numColors - 1); +} diff --git a/Helios/Colorset.h b/Helios/Colorset.h new file mode 100644 index 00000000..0cab1c0c --- /dev/null +++ b/Helios/Colorset.h @@ -0,0 +1,132 @@ +#ifndef COLORSET_H +#define COLORSET_H + +#include "Colortypes.h" + +#include "HeliosConfig.h" + +class Random; + +class Colorset +{ +public: + // empty colorset + Colorset(); + // constructor for 1-8 color slots + Colorset(RGBColor c1, RGBColor c2 = RGB_OFF, RGBColor c3 = RGB_OFF, + RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, RGBColor c6 = RGB_OFF, + RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); + Colorset(uint8_t numCols, const uint32_t *cols); + ~Colorset(); + + // copy and assignment operators + Colorset(const Colorset &other); + + // equality operators + bool operator==(const Colorset &other) const; + bool operator!=(const Colorset &other) const; + + // initialize the colorset + void init(RGBColor c1 = RGB_OFF, RGBColor c2 = RGB_OFF, RGBColor c3 = RGB_OFF, + RGBColor c4 = RGB_OFF, RGBColor c5 = RGB_OFF, RGBColor c6 = RGB_OFF, + RGBColor c7 = RGB_OFF, RGBColor c8 = RGB_OFF); + + // clear the colorset + void clear(); + + // pointer comparison + bool equals(const Colorset &set) const; + bool equals(const Colorset *set) const; + + // index operator to access color index + RGBColor operator[](int index) const; + + enum ValueStyle : uint8_t + { + // Random values + VAL_STYLE_RANDOM = 0, + // First color low value, the rest are random + VAL_STYLE_LOW_FIRST_COLOR, + // First color high value, the rest are low + VAL_STYLE_HIGH_FIRST_COLOR, + // Alternat between high and low value + VAL_STYLE_ALTERNATING, + // Ascending values from low to high + VAL_STYLE_ASCENDING, + // Descending values from high to low + VAL_STYLE_DESCENDING, + // Constant value + VAL_STYLE_CONSTANT, + // Total number of value styles + VAL_STYLE_COUNT + }; + + // add a single color + bool addColor(RGBColor col); + bool addColorHSV(uint8_t hue, uint8_t sat, uint8_t val); + void addColorWithValueStyle(Random &ctx, uint8_t hue, uint8_t sat, + ValueStyle valStyle, uint8_t numColors, uint8_t colorPos); + void removeColor(uint8_t index); + + + // function to randomize the colors with various different modes of randomization + enum ColorMode { + THEORY, + MONOCHROMATIC, + EVENLY_SPACED, + COLOR_MODE_COUNT + }; + void randomizeColors(Random &ctx, uint8_t numColors); + + // fade all of the colors in the set + void adjustBrightness(uint8_t fadeby); + + // get a color from the colorset + RGBColor get(uint8_t index = 0) const; + + // set an rgb color in a slot, or add a new color if you specify + // a slot higher than the number of colors in the colorset + void set(uint8_t index, RGBColor col); + + // skip some amount of colors + void skip(int32_t amount = 1); + + // get current color in cycle + RGBColor cur(); + + // set the current index of the colorset + void setCurIndex(uint8_t index); + void resetIndex(); + + // the current index + uint8_t curIndex() const { return m_curIndex; } + + // get the prev color in cycle + RGBColor getPrev(); + + // get the next color in cycle + RGBColor getNext(); + + // peek at the color indexes from current but don't iterate + RGBColor peek(int32_t offset) const; + + // better wording for peek 1 ahead + RGBColor peekNext() const { return peek(1); } + + // the number of colors in the palette + uint8_t numColors() const { return m_numColors; } + + // whether the colorset is currently on the first color or last color + bool onStart() const; + bool onEnd() const; +private: + // palette of colors + RGBColor m_palette[NUM_COLOR_SLOTS]; + // the actual number of colors in the set + uint8_t m_numColors; + // the current index, starts at UINT8_MAX so that + // the very first call to getNext will iterate to 0 + uint8_t m_curIndex; +}; + +#endif diff --git a/Helios/Colortypes.cpp b/Helios/Colortypes.cpp new file mode 100644 index 00000000..6c965ca5 --- /dev/null +++ b/Helios/Colortypes.cpp @@ -0,0 +1,409 @@ +#include "Colortypes.h" + +#if ALTERNATIVE_HSV_RGB == 1 +// global hsv to rgb algorithm selector +hsv_to_rgb_algorithm g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; +#endif + +HSVColor::HSVColor() : + hue(0), + sat(0), + val(0) +{ +} + +HSVColor::HSVColor(uint8_t hue, uint8_t sat, uint8_t val) : + hue(hue), sat(sat), val(val) +{ +} + +HSVColor::HSVColor(uint32_t dwVal) : + HSVColor() +{ + *this = dwVal; +} + +// assignment from uint32_t +HSVColor &HSVColor::operator=(const uint32_t &rhs) +{ + hue = ((rhs >> 16) & 0xFF); + sat = ((rhs >> 8) & 0xFF); + val = (rhs & 0xFF); + return *this; +} + +// construction/assignment from RGB +HSVColor::HSVColor(const RGBColor &rhs) +{ + *this = rhs; +} + +HSVColor &HSVColor::operator=(const RGBColor &rhs) +{ + // always use generic + *this = rgb_to_hsv_generic(rhs); + return *this; +} + +bool HSVColor::operator==(const HSVColor &other) const +{ + return (other.raw() == raw()); +} + +bool HSVColor::operator!=(const HSVColor &other) const +{ + return (other.raw() != raw()); +} + +bool HSVColor::empty() const +{ + return !hue && !sat && !val; +} + +void HSVColor::clear() +{ + hue = 0; + sat = 0; + val = 0; +} + +// ========== +// RGBColor + +RGBColor::RGBColor() : + red(0), + green(0), + blue(0) +{ +} + +RGBColor::RGBColor(uint8_t red, uint8_t green, uint8_t blue) : + red(red), green(green), blue(blue) +{ +} + +RGBColor::RGBColor(uint32_t dwVal) : + RGBColor() +{ + *this = dwVal; +} + +// assignment from uint32_t +RGBColor &RGBColor::operator=(const uint32_t &rhs) +{ + red = ((rhs >> 16) & 0xFF); + green = ((rhs >> 8) & 0xFF); + blue = (rhs & 0xFF); + return *this; +} + +RGBColor::RGBColor(const HSVColor &rhs) +{ + *this = rhs; +} + +RGBColor &RGBColor::operator=(const HSVColor &rhs) +{ +#if ALTERNATIVE_HSV_RGB == 1 + switch (g_hsv_rgb_alg) { + case HSV_TO_RGB_RAINBOW: + *this = hsv_to_rgb_rainbow(rhs); + break; + case HSV_TO_RGB_GENERIC: + *this = hsv_to_rgb_generic(rhs); + break; + } +#else + *this = hsv_to_rgb_generic(rhs); +#endif + return *this; +} + +bool RGBColor::operator==(const RGBColor &other) const +{ + return (other.raw() == raw()); +} + +bool RGBColor::operator!=(const RGBColor &other) const +{ + return (other.raw() != raw()); +} + +bool RGBColor::empty() const +{ + return !red && !green && !blue; +} + +void RGBColor::clear() +{ + red = 0; + green = 0; + blue = 0; +} + +RGBColor RGBColor::adjustBrightness(uint8_t fadeBy) +{ + red = (((int)red) * (int)(256 - fadeBy)) >> 8; + green = (((int)green) * (int)(256 - fadeBy)) >> 8; + blue = (((int)blue) * (int)(256 - fadeBy)) >> 8; + return *this; +} + +// ======================================================== +// Below are various functions for converting hsv <-> rgb + +#if ALTERNATIVE_HSV_RGB == 1 +#define SCALE8(i, scale) (((uint16_t)i * (uint16_t)(scale)) >> 8) +#define FIXFRAC8(N,D) (((N)*256)/(D)) + +// Stolen from FastLED hsv to rgb full rainbox where all colours +// are given equal weight, this makes for-example yellow larger +// best to use this function as it is the legacy choice +RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs) +{ + RGBColor col; + // Yellow has a higher inherent brightness than + // any other color; 'pure' yellow is perceived to + // be 93% as bright as white. In order to make + // yellow appear the correct relative brightness, + // it has to be rendered brighter than all other + // colors. + // Level Y1 is a moderate boost, the default. + // Level Y2 is a strong boost. + const uint8_t Y1 = 1; + const uint8_t Y2 = 0; + + // G2: Whether to divide all greens by two. + // Depends GREATLY on your particular LEDs + const uint8_t G2 = 0; + + // Gscale: what to scale green down by. + // Depends GREATLY on your particular LEDs + const uint8_t Gscale = 185; + + + uint8_t hue = rhs.hue; + uint8_t sat = rhs.sat; + uint8_t val = rhs.val; + + uint8_t offset = hue & 0x1F; // 0..31 + + // offset8 = offset * 8 + uint8_t offset8 = offset; + offset8 <<= 3; + + uint8_t third = SCALE8(offset8, (256 / 3)); // max = 85 + uint8_t r, g, b; + if (!(hue & 0x80)) { + // 0XX + if (!(hue & 0x40)) { + // 00X + //section 0-1 + if (!(hue & 0x20)) { + // 000 + //case 0: // R -> O + r = 255 - third; + g = third; + b = 0; + } else { + // 001 + //case 1: // O -> Y + if (Y1) { + r = 171; + g = 85 + third; + b = 0; + } + if (Y2) { + r = 170 + third; + //uint8_t twothirds = (third << 1); + uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); // max=170 + g = 85 + twothirds; + b = 0; + } + } + } else { + //01X + // section 2-3 + if (!(hue & 0x20)) { + // 010 + //case 2: // Y -> G + if (Y1) { + //uint8_t twothirds = (third << 1); + uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); // max=170 + r = 171 - twothirds; + g = 170 + third; + b = 0; + } + if (Y2) { + r = 255 - offset8; + g = 255; + b = 0; + } + } else { + // 011 + // case 3: // G -> A + r = 0; + g = 255 - third; + b = third; + } + } + } else { + // section 4-7 + // 1XX + if (!(hue & 0x40)) { + // 10X + if (!(hue & 0x20)) { + // 100 + //case 4: // A -> B + r = 0; + //uint8_t twothirds = (third << 1); + uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); // max=170 + g = 171 - twothirds; //170? + b = 85 + twothirds; + } else { + // 101 + //case 5: // B -> P + r = third; + g = 0; + b = 255 - third; + } + } else { + if (!(hue & 0x20)) { + // 110 + //case 6: // P -- K + r = 85 + third; + g = 0; + b = 171 - third; + } else { + // 111 + //case 7: // K -> R + r = 170 + third; + g = 0; + b = 85 - third; + } + } + } + + // This is one of the good places to scale the green down, + // although the client can scale green down as well. + if (G2) g = g >> 1; + if (Gscale) g = SCALE8(g, Gscale); + + // Scale down colors if we're desaturated at all + // and add the brightness_floor to r, g, and b. + if (sat != 255) { + if (sat == 0) { + r = 255; b = 255; g = 255; + } else { + if (r) r = SCALE8(r, sat) + 1; + if (g) g = SCALE8(g, sat) + 1; + if (b) b = SCALE8(b, sat) + 1; + + uint8_t desat = 255 - sat; + desat = SCALE8(desat, desat); + + uint8_t brightness_floor = desat; + r += brightness_floor; + g += brightness_floor; + b += brightness_floor; + } + } + + // Now scale everything down if we're at value < 255. + if (val != 255) { + val = SCALE8(val, val); + if (val == 0) { + r = 0; g = 0; b = 0; + } else { + // nSCALE8x3_video( r, g, b, val); + if (r) r = SCALE8(r, val) + 1; + if (g) g = SCALE8(g, val) + 1; + if (b) b = SCALE8(b, val) + 1; + } + } + + // Here we have the old AVR "missing std X+n" problem again + // It turns out that fixing it winds up costing more than + // not fixing it. + // To paraphrase Dr Bronner, profile! profile! profile! + col.red = r; + col.green = g; + col.blue = b; + return col; +} +#endif + +// generic hsv to rgb conversion nothing special +RGBColor hsv_to_rgb_generic(const HSVColor &rhs) +{ + unsigned char region, remainder, p, q, t; + RGBColor col; + + if (rhs.sat == 0) { + col.red = rhs.val; + col.green = rhs.val; + col.blue = rhs.val; + return col; + } + + region = rhs.hue / 43; + remainder = ((rhs.hue - (region * 43)) * 6); + + // extraneous casts to uint16_t are to prevent overflow + p = (uint8_t)(((uint16_t)(rhs.val) * (255 - rhs.sat)) >> 8); + q = (uint8_t)(((uint16_t)(rhs.val) * (255 - (((uint16_t)(rhs.sat) * remainder) >> 8))) >> 8); + t = (uint8_t)(((uint16_t)(rhs.val) * (255 - (((uint16_t)(rhs.sat) * (255 - remainder)) >> 8))) >> 8); + + switch (region) { + case 0: + col.red = rhs.val; col.green = t; col.blue = p; + break; + case 1: + col.red = q; col.green = rhs.val; col.blue = p; + break; + case 2: + col.red = p; col.green = rhs.val; col.blue = t; + break; + case 3: + col.red = p; col.green = q; col.blue = rhs.val; + break; + case 4: + col.red = t; col.green = p; col.blue = rhs.val; + break; + default: + col.red = rhs.val; col.green = p; col.blue = q; + break; + } + return col; +} + +// Convert rgb to hsv with generic fast method +HSVColor rgb_to_hsv_generic(const RGBColor &rhs) +{ + unsigned char rgbMin, rgbMax; + rgbMin = rhs.red < rhs.green ? (rhs.red < rhs.blue ? rhs.red : rhs.blue) : (rhs.green < rhs.blue ? rhs.green : rhs.blue); + rgbMax = rhs.red > rhs.green ? (rhs.red > rhs.blue ? rhs.red : rhs.blue) : (rhs.green > rhs.blue ? rhs.green : rhs.blue); + HSVColor hsv; + + hsv.val = rgbMax; + if (hsv.val == 0) { + hsv.hue = 0; + hsv.sat = 0; + return hsv; + } + + hsv.sat = 255 * (long)(rgbMax - rgbMin) / hsv.val; + if (hsv.sat == 0) { + hsv.hue = 0; + return hsv; + } + + if (rgbMax == rhs.red) { + hsv.hue = 0 + 43 * (rhs.green - rhs.blue) / (rgbMax - rgbMin); + } else if (rgbMax == rhs.green) { + hsv.hue = 85 + 43 * (rhs.blue - rhs.red) / (rgbMax - rgbMin); + } else { + hsv.hue = 171 + 43 * (rhs.red - rhs.green) / (rgbMax - rgbMin); + } + return hsv; +} diff --git a/Helios/Colortypes.h b/Helios/Colortypes.h new file mode 100644 index 00000000..bd088cc6 --- /dev/null +++ b/Helios/Colortypes.h @@ -0,0 +1,93 @@ +#ifndef COLOR_H +#define COLOR_H + +#include + +#include "HeliosConfig.h" +#include "ColorConstants.h" + +#if ALTERNATIVE_HSV_RGB == 1 +enum hsv_to_rgb_algorithm : uint8_t +{ + HSV_TO_RGB_GENERIC, + HSV_TO_RGB_RAINBOW +}; + +// global hsv to rgb algorithm selector, switch this to control +// all hsv to rgb conversions +extern hsv_to_rgb_algorithm g_hsv_rgb_alg; +#endif + +class ByteStream; +class RGBColor; + +class HSVColor +{ +public: + HSVColor(); + HSVColor(uint8_t hue, uint8_t sat, uint8_t val); + + // assignment from uint32_t + HSVColor(uint32_t dwVal); + HSVColor &operator=(const uint32_t &rhs); + + // construction/assignment from RGB + HSVColor(const RGBColor &rhs); + HSVColor &operator=(const RGBColor &rhs); + + // equality operators + bool operator==(const HSVColor &other) const; + bool operator!=(const HSVColor &other) const; + + bool empty() const; + void clear(); + + uint32_t raw() const { return ((uint32_t)hue << 16) | ((uint32_t)sat << 8) | (uint32_t)val; } + + // public members + uint8_t hue; + uint8_t sat; + uint8_t val; +}; + +class RGBColor +{ +public: + RGBColor(); + RGBColor(uint8_t red, uint8_t green, uint8_t blue); + + // assignment from uint32_t + RGBColor(uint32_t dwVal); + RGBColor &operator=(const uint32_t &rhs); + + RGBColor(const HSVColor &rhs); + RGBColor &operator=(const HSVColor &rhs); + + // equality operators + bool operator==(const RGBColor &other) const; + bool operator!=(const RGBColor &other) const; + + bool empty() const; + void clear(); + + RGBColor adjustBrightness(uint8_t fadeBy); + + uint32_t raw() const { return ((uint32_t)red << 16) | ((uint32_t)green << 8) | (uint32_t)blue; } + + // public members + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +// Stolen from FastLED hsv to rgb full rainbox where all colours +// are given equal weight, this makes for-example yellow larger +// best to use this function as it is the legacy choice +RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs); +// generic hsv to rgb conversion nothing special +RGBColor hsv_to_rgb_generic(const HSVColor &rhs); + +// Convert rgb to hsv with generic fast method +HSVColor rgb_to_hsv_generic(const RGBColor &rhs); + +#endif diff --git a/Helios/Helios.cpp b/Helios/Helios.cpp new file mode 100644 index 00000000..8e4ffc4f --- /dev/null +++ b/Helios/Helios.cpp @@ -0,0 +1,782 @@ +#include + +#include "Helios.h" + +#include "ColorConstants.h" +#include "TimeControl.h" +#include "Storage.h" +#include "Pattern.h" +#include "Random.h" +#include "Button.h" +#include "Led.h" + +#ifdef HELIOS_EMBEDDED +#include +#include +#include +#endif + +#ifdef HELIOS_CLI +#include +#endif + +#include + +Helios::State Helios::cur_state; +Helios::Flags Helios::global_flags; +uint8_t Helios::menu_selection; +uint8_t Helios::cur_mode; +uint8_t Helios::selected_slot; +uint8_t Helios::selected_base_quad; +uint8_t Helios::selected_hue; +uint8_t Helios::selected_sat; +Pattern Helios::pat; +bool Helios::keepgoing; + +#ifdef HELIOS_CLI +bool Helios::sleeping; +#endif + +bool Helios::init() +{ + // initialize the time control and led control + if (!Time::init()) { + return false; + } + if (!Led::init()) { + return false; + } + if (!Storage::init()) { + return false; + } + if (!Button::init()) { + return false; + } + + // initialize globals + cur_state = STATE_MODES; + menu_selection = 0; + cur_mode = 0; + selected_slot = 0; + selected_base_quad = 0; + selected_hue = 0; + selected_sat = 0; + keepgoing = true; +#ifdef HELIOS_CLI + sleeping = false; +#endif + + // read the global flags from index 0 config + global_flags = (Flags)Storage::read_config(0); + if (has_flag(FLAG_CONJURE)) { + // if conjure is enabled then load the current mode index from storage + cur_mode = Storage::read_config(1); + } + + // load the current mode from store and initialize it + load_cur_mode(); + +#ifdef HELIOS_EMBEDDED + // Timer0 setup for 1 kHz interrupt + TCCR0A |= (1 << WGM01); +#if F_CPU == 16000000L + // 1ms at 16MHz clock with prescaler of 64 + OCR0A = 249; +#elif F_CPU == 8000000L + // 1ms at 8mhz clock with prescaler of 64 + OCR0A = 124; +#elif F_CPU == 1000000L + // 1ms at 1mhz clock with prescaler of 64 + OCR0A = 15; // Adjusted value for 1 MHz clock +#endif + TIMSK |= (1 << OCIE0A); + // Start timer with prescaler of 64 + TCCR0B |= (1 << CS01) | (1 << CS00); + // enable interrupts + sei(); +#endif + + return true; +} + +#ifdef HELIOS_EMBEDDED +ISR(TIM0_COMPA_vect) { + // 1 kHz system tick + Helios::tick(); +} +#endif + +void Helios::tick() +{ + // sample the button and re-calculate all button globals + // the button globals should not change anywhere else + Button::update(); + + // handle the current state of the system, ie whatever state + // we're in we check for the appropriate input events for that + // state by checking button globals, then run the appropriate logic + handle_state(); + + // NOTE: Do not update the LED here anymore, instead we call Led::update() + // in the tight loop inside main() where it can perform software PWM + // on the LED pins at a much higher frequency + + // finally tick the clock forward and then sleep till the entire + // tick duration has been consumed + Time::tickClock(); +} + +void Helios::enter_sleep() +{ +#ifdef HELIOS_EMBEDDED + // Enable wake on interrupt for the button + Button::enableWake(); + // Set sleep mode to POWER DOWN mode + set_sleep_mode(SLEEP_MODE_PWR_DOWN); + // enter sleep + sleep_mode(); + // ... interrupt will make us wake here + // wakeup here, re-init + init(); +#else + cur_state = STATE_SLEEP; + // enable the sleep bool + sleeping = true; +#endif +} + +void Helios::wakeup() { +#ifdef HELIOS_EMBEDDED + // nothing needed here, this interrupt firing will make the mainthread resume +#else + // re-initialize helios? + init(); + // turn off the sleeping flag that only CLI has + sleeping = false; +#endif +} + +void Helios::handle_state() +{ + // check for the force sleep button hold regardless of which state we're in + if (Button::holdDuration() > FORCE_SLEEP_TIME) { + // when released the device will just sleep + if (Button::onRelease()) { + enter_sleep(); + // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! + return; + } + // but as long as it's held past the sleep time it just turns off the led + if (Button::isPressed()) { + Led::clear(); + return; + } + } + // otherwise just handle the state like normal + switch (cur_state) { + case STATE_MODES: + handle_state_modes(); + break; + case STATE_COLOR_SELECT_SLOT: + case STATE_COLOR_SELECT_QUADRANT: + case STATE_COLOR_SELECT_HUE: + case STATE_COLOR_SELECT_SAT: + case STATE_COLOR_SELECT_VAL: + handle_state_col_select(); + break; + case STATE_PATTERN_SELECT: + handle_state_pat_select(); + break; + case STATE_TOGGLE_CONJURE: + handle_state_toggle_flag(FLAG_CONJURE); + break; + case STATE_TOGGLE_LOCK: + handle_state_toggle_flag(FLAG_LOCKED); + break; + case STATE_SET_DEFAULTS: + handle_state_set_defaults(); + break; + case STATE_SET_GLOBAL_BRIGHTNESS: + handle_state_set_global_brightness(); + break; + case STATE_SHIFT_MODE: + handle_state_shift_mode(); + break; + case STATE_RANDOMIZE: + handle_state_randomize(); + break; +#ifdef HELIOS_CLI + case STATE_SLEEP: + // simulate sleep in helios CLI + if (Button::onPress()) { + wakeup(); + } + break; +#endif + } +} + +void Helios::load_next_mode() +{ + // increment current mode and wrap around + cur_mode = (uint8_t)(cur_mode + 1) % NUM_MODE_SLOTS; + // now load current mode again + load_cur_mode(); +} + +void Helios::load_cur_mode() +{ + // read pattern from storage at cur mode index + if (!Storage::read_pattern(cur_mode, pat)) { + // and just initialize default if it cannot be read + Patterns::make_default(cur_mode, pat); + // try to write it out because storage was corrupt + Storage::write_pattern(cur_mode, pat); + } + // then re-initialize the pattern + pat.init(); +} + +void Helios::save_cur_mode() +{ + Storage::write_pattern(cur_mode, pat); +} + +void Helios::handle_state_modes() +{ + // whether they have released the button since turning on + bool hasReleased = (Button::releaseCount() > 0); + + if (Button::releaseCount() > 1 && Button::onShortClick()) { + if (has_flag(FLAG_CONJURE)) { + enter_sleep(); + } else { + load_next_mode(); + } + return; + } + + // check for lock and go back to sleep + if (has_flag(FLAG_LOCKED) && hasReleased && !Button::onRelease()) { + enter_sleep(); + // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! + return; + } + + if (!has_flag(FLAG_LOCKED) && hasReleased) { + // just play the current mode + pat.play(); + } + // check how long the button is held + uint32_t holdDur = Button::holdDuration(); + // calculate a magnitude which corresponds to how many times past the MENU_HOLD_TIME + // the user has held the button, so 0 means haven't held fully past one yet, etc + uint8_t magnitude = (uint8_t)(holdDur / MENU_HOLD_TIME); + // whether the user has held the button longer than a short click + bool heldPast = (holdDur > SHORT_CLICK_THRESHOLD); + // if the button is held for at least 1 second + if (Button::isPressed() && heldPast) { + // if the button has been released before then show the on menu + if (hasReleased) { + switch (magnitude) { + default: + case 0: Led::clear(); break; // Turn off + case 1: Led::set(RGB_TURQUOISE_BRI_LOW); break; // Color Selection + case 2: Led::set(RGB_MAGENTA_BRI_LOW); break; // Pattern Selection + case 3: Led::set(RGB_YELLOW_BRI_LOW); break; // Conjure Mode + case 4: Led::set(RGB_WHITE_BRI_LOW); break; // Shift Mode + case 5: Led::set(HSVColor(Time::getCurtime(), 255, 180)); break; // Randomizer + } + } else { + if (has_flag(FLAG_LOCKED)) { + switch (magnitude) { + default: + case 0: Led::clear(); break; + case 5: Led::set(RGB_RED_BRI_LOW); break; // Exit Lock + } + } else { + switch (magnitude) { + default: + case 0: Led::clear(); break; // nothing + case 1: Led::set(RGB_RED_BRI_LOW); break; // Enter Glow Lock + case 2: Led::set(RGB_BLUE_BRI_LOW); break; // Master Reset + case 3: Led::set(RGB_GREEN_BRI_LOW); break; // Global Brightness + } + } + } + } + // if this isn't a release tick there's nothing more to do + if (Button::onRelease()) { + if (heldPast && Button::releaseCount() == 1) { + handle_off_menu(magnitude, heldPast); + return; + } + // otherwise if we have released it then we are in the 'on' menu + handle_on_menu(magnitude, heldPast); + } +} + +void Helios::handle_off_menu(uint8_t mag, bool past) +{ + // if still locked then handle the unlocking menu which is just if mag == 5 + if (has_flag(FLAG_LOCKED)) { + switch (mag) { + case 5: // red lock + cur_state = STATE_TOGGLE_LOCK; + break; + default: + // just go back to sleep in hold-past off menu + enter_sleep(); + // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! + } + // in this case we return either way, since we're locked + return; + } + + // otherwise if not locked handle the off menu + switch (mag) { + case 1: // red lock + cur_state = STATE_TOGGLE_LOCK; + Led::clear(); + return; // RETURN HERE + case 2: // blue reset defaults + cur_state = STATE_SET_DEFAULTS; + return; // RETURN HERE + case 3: // green global brightness + cur_state = STATE_SET_GLOBAL_BRIGHTNESS; + return; // RETURN HERE + default: + // just go back to sleep in hold-past off menu + enter_sleep(); + // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! + return; + } +} + +void Helios::handle_on_menu(uint8_t mag, bool past) +{ + switch (mag) { + case 0: // off + // but only if we held for more than a short click + if (past) { + enter_sleep(); + // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! + return; + } + break; + case 1: // color select + cur_state = STATE_COLOR_SELECT_SLOT; + // reset the menu selection + menu_selection = 0; +#if ALTERNATIVE_HSV_RGB == 1 + // use the nice hue to rgb rainbow + g_hsv_rgb_alg = HSV_TO_RGB_RAINBOW; +#endif + break; + case 2: // pat select + cur_state = STATE_PATTERN_SELECT; + // reset the menu selection + menu_selection = 0; + break; + case 3: // conjure mode + cur_state = STATE_TOGGLE_CONJURE; + Led::clear(); + break; + case 4: // shift mode down + cur_state = STATE_SHIFT_MODE; + break; + case 5: // ??? + cur_state = STATE_RANDOMIZE; + break; + default: // hold past + break; + } +} + +void Helios::handle_state_col_select() +{ + uint8_t num_cols = pat.colorset().numColors(); + if (Button::onShortClick()) { + // next hue/sat/val selection + uint8_t num_menus = 4; + if (cur_state == STATE_COLOR_SELECT_SLOT) { + // menus = all colors + exit + num_menus = num_cols + 1; + // but if the num cols is less than total color slots + if (num_cols < NUM_COLOR_SLOTS) { + // then we have another menu: add color + num_menus++; + } + } else if (cur_state == STATE_COLOR_SELECT_QUADRANT) { + num_menus = 7; + } + menu_selection = (menu_selection + 1) % num_menus; + } + ColorSelectOption slot_option = OPTION_NONE; + bool check_longclick = true; + switch (cur_state) { + default: + case STATE_COLOR_SELECT_SLOT: + // pick the target colorset slot + check_longclick = handle_state_col_select_slot(slot_option); + break; + case STATE_COLOR_SELECT_QUADRANT: + // pick the hue quadrant + check_longclick = handle_state_col_select_quadrant(); + break; + case STATE_COLOR_SELECT_HUE: + // target hue for changes + check_longclick = handle_state_col_select_hue(); + break; + case STATE_COLOR_SELECT_SAT: + // target sat for changes + check_longclick = handle_state_col_select_sat(); + break; + case STATE_COLOR_SELECT_VAL: + // target val for changes + check_longclick = handle_state_col_select_val(); + break; + } + if (check_longclick && Button::onLongClick()) { + if (cur_state == STATE_COLOR_SELECT_VAL) { + cur_state = STATE_COLOR_SELECT_SLOT; + // Return to the slot you were editing + menu_selection = selected_slot; + } else { + cur_state = (State)(cur_state + 1); + // reset the menu selection + menu_selection = 0; + } + } + // get the current color + RGBColor cur = Led::get(); + cur.red /= 2; + cur.green /= 2; + cur.blue /= 2; + // this is a stupid override for when we're exiting color select + // show a white selection instead + if (slot_option != OPTION_NONE) { + cur = RGB_WHITE; + } + // show selection in all of these menus + show_selection(cur); +} + +bool Helios::handle_state_col_select_slot(ColorSelectOption &out_option) +{ + Colorset &set = pat.colorset(); + uint8_t num_cols = set.numColors(); + + bool long_click = Button::onLongClick(); + + if (num_cols < NUM_COLOR_SLOTS && menu_selection == num_cols) { + // add color + out_option = SELECTED_ADD; + Led::strobe(100, 100, RGB_WHITE_BRI_LOW, RGB_OFF); + if (long_click) { + selected_slot = menu_selection; + } + } else if (menu_selection == num_cols + 1 || (num_cols == NUM_COLOR_SLOTS && menu_selection == num_cols)) { + // exit + out_option = SELECTED_EXIT; + Led::strobe(60, 40, RGB_RED_BRI_LOW, RGB_OFF); + if (long_click) { +#if ALTERNATIVE_HSV_RGB == 1 + // restore hsv to rgb algorithm type, done color selection + g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; +#endif + save_cur_mode(); + cur_state = STATE_MODES; + return false; + } + } else { + out_option = SELECTED_SLOT; + selected_slot = menu_selection; + // render current selection + RGBColor col = set.get(selected_slot); + if (col.empty()) { + Led::strobe(1, 30, RGB_OFF, RGB_WHITE_BRI_LOW); + } else { + Led::set(col); + } + uint16_t mod_dur = (uint16_t)(Button::holdDuration() % (DELETE_COLOR_TIME * 2)); + bool deleting = (mod_dur > DELETE_COLOR_TIME); + if (deleting) { + if (Button::isPressed()) { + // flash red + Led::strobe(150, 150, RGB_RED_BRI_LOW, col); + } + if (long_click) { + set.removeColor(selected_slot); + return false; + } + } + } + return true; +} + +struct ColorsMenuData { + uint8_t hues[4]; +}; +// array of hues for selection +static const ColorsMenuData color_menu_data[4] = { + // hue0 hue1 hue2 hue3 + // ================================================================================== + { HUE_RED, HUE_CORAL_ORANGE, HUE_ORANGE, HUE_YELLOW }, + { HUE_LIME_GREEN, HUE_GREEN, HUE_SEAFOAM, HUE_TURQUOISE }, + { HUE_ICE_BLUE, HUE_LIGHT_BLUE, HUE_BLUE, HUE_ROYAL_BLUE }, + { HUE_PURPLE, HUE_PINK, HUE_HOT_PINK, HUE_MAGENTA }, +}; + +bool Helios::handle_state_col_select_quadrant() +{ + uint8_t hue_quad = (menu_selection - 2) % 4; + + if (menu_selection > 5) { + menu_selection = 0; + } + + if (Button::onLongClick()) { + // select hue/sat/val + switch (menu_selection) { + case 0: // selected blank + // add blank to set + pat.colorset().set(selected_slot, RGB_OFF); + // Return to the slot you were editing + menu_selection = selected_slot; + // go to slot selection - 1 because we will increment outside here + cur_state = STATE_COLOR_SELECT_SLOT; + return false; + case 1: // selected white + // adds white, skip hue/sat to brightness + selected_hue = 0; + selected_sat = 0; + cur_state = STATE_COLOR_SELECT_VAL; + return false; + default: // 2-5 + selected_base_quad = hue_quad; + break; + } + } + + // default col1/col2 to off and white for the first two options + RGBColor col1 = RGB_OFF; + RGBColor col2; + uint16_t on_dur, off_dur; + + switch (menu_selection) { + case 0: // Blank Option + col2 = RGB_WHITE_BRI_LOW; + on_dur = 1; + off_dur = 30; + break; + case 1: // White Option + col2 = RGB_WHITE; + on_dur = 9; + off_dur = 0; + break; + default: // Color options + col1 = HSVColor(color_menu_data[hue_quad].hues[0], 255, 255); + col2 = HSVColor(color_menu_data[hue_quad].hues[2], 255, 255); + on_dur = 500; + off_dur = 500; + break; + } + Led::strobe(on_dur, off_dur, col1, col2); + return true; +} + + + +bool Helios::handle_state_col_select_hue() +{ + uint8_t hue = color_menu_data[selected_base_quad].hues[menu_selection]; + if (Button::onLongClick()) { + // select hue/sat/val + selected_hue = hue; + } + // render current selection + Led::set(HSVColor(hue, 255, 255)); + return true; +} + +bool Helios::handle_state_col_select_sat() +{ + if (menu_selection > 3) { + menu_selection = 3; + } + static const uint8_t saturation_values[4] = {HSV_SAT_HIGH, HSV_SAT_MEDIUM, HSV_SAT_LOW, HSV_SAT_LOWEST}; + uint8_t sat = saturation_values[menu_selection]; + + // use the nice hue to rgb rainbow + if (Button::onLongClick()) { + // select hue/sat/val + selected_sat = sat; + } + // render current selection + Led::set(HSVColor(selected_hue, sat, 255)); + return true; +} + +bool Helios::handle_state_col_select_val() +{ + if (menu_selection > 3) { + menu_selection = 3; + } + static const uint8_t hsv_values[4] = {HSV_VAL_HIGH, HSV_VAL_MEDIUM, HSV_VAL_LOW, HSV_VAL_LOWEST}; + uint8_t val = hsv_values[menu_selection]; + + RGBColor targetCol = HSVColor(selected_hue, selected_sat, val); + // use the nice hue to rgb rainbow + if (Button::onLongClick()) { + // change the current patterns color + pat.colorset().set(selected_slot, targetCol); + pat.init(); + save_cur_mode(); + } + // render current selection + Led::set(targetCol); + return true; +} + +void Helios::handle_state_pat_select() +{ + if (Button::onLongClick()) { + save_cur_mode(); + cur_state = STATE_MODES; + } + if (Button::onShortClick()) { + menu_selection = (menu_selection + 1) % PATTERN_COUNT; + Patterns::make_pattern((PatternID)menu_selection, pat); + pat.init(); + } + pat.play(); + show_selection(RGB_MAGENTA_BRI_LOW); +} + +void Helios::handle_state_toggle_flag(Flags flag) +{ + // toggle the conjure flag + toggle_flag(flag); + // write out the new global flags and the current mode + save_global_flags(); + // switch back to modes + cur_state = STATE_MODES; +} + +void Helios::handle_state_set_defaults() +{ + if (Button::onShortClick()) { + menu_selection = !menu_selection; + } + // show low white for exit or red for select + if (menu_selection) { + Led::strobe(20, 10, RGB_RED_BRI_LOW, RGB_OFF); + } else { + Led::strobe(60, 20, RGB_WHITE_BRI_LOWEST, RGB_OFF); + } + // when the user long clicks a selection + if (Button::onLongClick()) { + // if the user actually selected 'yes' + if (menu_selection == 1) { + for (uint8_t i = 0; i < NUM_MODE_SLOTS; ++i) { + Patterns::make_default(i, pat); + Storage::write_pattern(i, pat); + } + // reset global flags + global_flags = FLAG_NONE; + cur_mode = 0; + // save global flags + save_global_flags(); + // re-load current mode + load_cur_mode(); + } + cur_state = STATE_MODES; + } + show_selection(RGB_WHITE_BRI_LOW); +} + +void Helios::handle_state_set_global_brightness() +{ + if (Button::onShortClick()) { + menu_selection = (menu_selection + 1) % 3; + } + // show different levels of green for each selection + switch (menu_selection) { + case 0: + Led::set(RGB_GREEN); + break; + case 1: + Led::set(RGB_GREEN_BRI_MEDIUM); + break; + case 2: + Led::set(RGB_GREEN_BRI_LOW); + break; + } + // when the user long clicks a selection + if (Button::onLongClick()) { + // set the brightness based on the selection * the brightness step amount + Led::setBrightness(menu_selection * BRIGHTNESS_STEP); + cur_state = STATE_MODES; + } + show_selection(RGB_WHITE_BRI_LOW); +} + +inline uint32_t crc32(const uint8_t *data, uint8_t size) +{ + uint32_t hash = 5381; + for (uint8_t i = 0; i < size; ++i) { + hash = ((hash << 5) + hash) + data[i]; + } + return hash; +} + +void Helios::handle_state_shift_mode() +{ + uint8_t new_mode = cur_mode ? (uint8_t)(cur_mode - 1) : (uint8_t)(NUM_MODE_SLOTS - 1); + Storage::swap_pattern(cur_mode, new_mode); + cur_mode = new_mode; + cur_state = STATE_MODES; +} + +void Helios::handle_state_randomize() +{ + if (Button::onShortClick()) { + uint32_t seed = crc32((const uint8_t *)&pat, PATTERN_SIZE); + Random ctx(seed); + Colorset &cur_set = pat.colorset(); + uint8_t num_cols = (ctx.next8() + 1) % NUM_COLOR_SLOTS; + + cur_set.randomizeColors(ctx, num_cols); + Patterns::make_pattern((PatternID)(ctx.next8() % PATTERN_COUNT), pat); + pat.init(); + } + if (Button::onLongClick()) { + save_cur_mode(); + cur_state = STATE_MODES; + } + pat.play(); + show_selection(RGB_WHITE_BRI_LOW); +} + +void Helios::save_global_flags() +{ + Storage::write_config(0, global_flags); + Storage::write_config(1, cur_mode); +} + +void Helios::show_selection(RGBColor color) +{ + // only show selection while pressing the button + if (!Button::isPressed()) { + return; + } + uint16_t holdDur = Button::holdDuration(); + // if the hold duration is outside the flashing range do nothing + if (holdDur < SHORT_CLICK_THRESHOLD || + holdDur > (SHORT_CLICK_THRESHOLD + SELECTION_FLASH_DURATION)) { + return; + } + // set the selection color + Led::set(color); +} diff --git a/Helios/Helios.h b/Helios/Helios.h new file mode 100644 index 00000000..d22a0fdf --- /dev/null +++ b/Helios/Helios.h @@ -0,0 +1,106 @@ +#include + +#include "HeliosConfig.h" +#include "Colorset.h" +#include "Pattern.h" + +class Helios +{ +public: + static bool init(); + static void tick(); + + static void enter_sleep(); + static void wakeup(); + + static bool keep_going() { return keepgoing; } + static void terminate() { keepgoing = false; } + +#ifdef HELIOS_CLI + static bool is_asleep() { return sleeping; } +#endif + +private: + enum Flags : uint8_t { + FLAG_NONE = 0, + FLAG_LOCKED = (1 << 0), + FLAG_CONJURE = (1 << 1), + }; + + // get/set global flags + static void set_flag(Flags flag) { global_flags = (Flags)(global_flags | flag); } + static bool has_flag(Flags flag) { return (global_flags & flag) == flag; } + static void clear_flag(Flags flag) { global_flags = (Flags)(global_flags & ~flag); } + static void toggle_flag(Flags flag) { global_flags = (Flags)(global_flags ^ flag); } + + static void handle_state(); + static void handle_state_modes(); + + // the slot selection returns this info for internal menu logic + enum ColorSelectOption { + OPTION_NONE = 0, + + SELECTED_ADD, + SELECTED_EXIT, + SELECTED_SLOT + }; + + static void handle_off_menu(uint8_t mag, bool past); + static void handle_on_menu(uint8_t mag, bool past); + static void handle_state_col_select(); + static bool handle_state_col_select_slot(ColorSelectOption &out_option); + static bool handle_state_col_select_quadrant(); + static bool handle_state_col_select_hue(); + static bool handle_state_col_select_sat(); + static bool handle_state_col_select_val(); + static void handle_state_pat_select(); + static void handle_state_toggle_flag(Flags flag); + static void handle_state_set_defaults(); + static void handle_state_set_global_brightness(); + static void handle_state_shift_mode(); + static void handle_state_randomize(); + static void load_next_mode(); + static void load_cur_mode(); + static void save_cur_mode(); + static void save_global_flags(); + static void show_selection(RGBColor color); + + enum State : uint8_t { + STATE_MODES, + STATE_COLOR_SELECT_SLOT, + STATE_COLOR_SELECT_QUADRANT, + STATE_COLOR_SELECT_HUE, + STATE_COLOR_SELECT_SAT, + STATE_COLOR_SELECT_VAL, + STATE_PATTERN_SELECT, + STATE_TOGGLE_CONJURE, + STATE_TOGGLE_LOCK, + STATE_SET_DEFAULTS, + STATE_SET_GLOBAL_BRIGHTNESS, + STATE_SHIFT_MODE, + STATE_RANDOMIZE, +#ifdef HELIOS_CLI + STATE_SLEEP, +#endif + }; + + // the current state of the system + static State cur_state; + // global flags for the entire system + static Flags global_flags; + static uint8_t menu_selection; + static uint8_t cur_mode; + // the quadrant that was selected in color select + static uint8_t selected_slot; + static uint8_t selected_base_quad; + static uint8_t selected_hue; + static uint8_t selected_sat; + static PatternArgs default_args[6]; + static Colorset default_colorsets[6]; + static Pattern pat; + static bool keepgoing; + +#ifdef HELIOS_CLI + static bool sleeping; +#endif +}; diff --git a/Helios/HeliosConfig.h b/Helios/HeliosConfig.h new file mode 100644 index 00000000..b0ea6060 --- /dev/null +++ b/Helios/HeliosConfig.h @@ -0,0 +1,115 @@ +#ifndef HELIOS_CONFIG_H +#define HELIOS_CONFIG_H + +// Short Click Threshold +// +// The length of time in milliseconds for a click to +// be considered either a short or long click +#define SHORT_CLICK_THRESHOLD 400 + +// Selection Flash Duration +// +// How long the led flashes when selecting something +#define SELECTION_FLASH_DURATION 500 + +// Max Color Slots +// +// The number of slots in a colorset +#define NUM_COLOR_SLOTS 6 + +// Mode Slots +// +// The number of modes on the device +#define NUM_MODE_SLOTS 6 + +// Default Brightness +// +// The default brightness of the led +#define DEFAULT_BRIGHTNESS 255 + +// Tickrate +// +// The number of engine ticks per second +#define TICKRATE 1000 + +// Menu Hold Time +// +// How long the button must be held for the menus to open and cycle +// note this is a measure of ticks, but if the tickrate is 1000 then +// it is a measure of milliseconds +#define MENU_HOLD_TIME 1000 + +// Force Sleep Time +// +// The duration in ms/ticks to hold the button to force the chip to +// sleep at any location in the menus +#define FORCE_SLEEP_TIME 7000 + +// Delete Color Time +// +// How long to hold button on a color to start the delete color flash +// begins and starts flashes. Also how long the cycling flash is for the +// delete color selection, ie how long the user has to release to delete +// the color before it cycles back +#define DELETE_COLOR_TIME 1500 + +// Alternative HSV to RGB +// +// This enabled the alternative HSV to RGB algorithm to be used in the +// color selection menu and provide a slightly different range of colors +#define ALTERNATIVE_HSV_RGB 0 + +// Brightness Options +// +// There are three brightness options, the lowest is equal to this value, +// the middle is 2x this value, and the highest is 3x this value +#define BRIGHTNESS_STEP 85 + +// Pre-defined saturation values +#define HSV_SAT_HIGH 255 +#define HSV_SAT_MEDIUM 220 +#define HSV_SAT_LOW 190 +#define HSV_SAT_LOWEST 160 + +// Pre-defined brightness values +#define HSV_VAL_HIGH 255 +#define HSV_VAL_MEDIUM 120 +#define HSV_VAL_LOW 60 +#define HSV_VAL_LOWEST 10 + +// ============================================================================ +// Storage Constants +// +// These are various storage sizes of data and some math to +// help calculate sizes or space requirements + +// Colorset Size +// +// the colorset is just an array of colors but it also has a num colors val +#define COLORSET_SIZE ((sizeof(RGBColor) * NUM_COLOR_SLOTS) + 1) + +// Pattern Size +// +// The actual pattern storage size is the size of the colorset + 7 params + 1 pat flags +#define PATTERN_SIZE (COLORSET_SIZE + 7 + 1) + +// Slot Size +// +// the slot stores the pattern + 1 byte CRC +#define SLOT_SIZE (PATTERN_SIZE + 1) + +// Some math to calculate storage sizes: +// 3 * 6 = 18 for the colorset +// 1 + 7 + 1 + 1 = 10 for the rest +// = 28 bytes total for a pattern including CRC +// -> 8 slots = 8 * 28 = 224 +// = 31 bytes left +// -> 9 slots = 9 * 28 = 252 +// = 3 bytes left + +// forbidden constant: +// #define HELIOS_ARDUINO 1 + + + +#endif diff --git a/Helios/Led.cpp b/Helios/Led.cpp new file mode 100644 index 00000000..db77e1c8 --- /dev/null +++ b/Helios/Led.cpp @@ -0,0 +1,141 @@ +#include + +#include "Led.h" + +#include "TimeControl.h" + +#include "HeliosConfig.h" +#include "Helios.h" + +#ifdef HELIOS_EMBEDDED +#ifdef HELIOS_ARDUINO +#include +#else +#include +#include +#include +#endif +#endif + +#define PWM_PIN_R PB0 // Red channel (pin 5) +#define PWM_PIN_G PB1 // Green channel (pin 6) +#define PWM_PIN_B PB4 // Blue channel (pin 3) + +#define SCALE8(i, scale) (((uint16_t)i * (uint16_t)(scale)) >> 8) + +// array of led color values +RGBColor Led::m_ledColor = RGB_OFF; +RGBColor Led::m_realColor = RGB_OFF; +// global brightness +uint8_t Led::m_brightness = DEFAULT_BRIGHTNESS; + +bool Led::init() +{ + // clear the led colors + m_ledColor = RGB_OFF; + m_realColor = RGB_OFF; +#ifdef HELIOS_EMBEDDED +#ifdef HELIOS_ARDUINO + pinMode(0, OUTPUT); + pinMode(1, OUTPUT); + pinMode(4, OUTPUT); +#else + // Set pins as outputs + DDRB |= (1 << 0) | (1 << 1) | (1 << 4); + // Timer/Counter0 in Fast PWM mode + //TCCR0A |= (1 << WGM01) | (1 << WGM00); + // Clear OC0A and OC0B on compare match, set at BOTTOM (non-inverting mode) + //TCCR0A |= (1 << COM0A1) | (1 << COM0B1); + // Use clk/8 prescaler (adjust as needed) + //TCCR0B |= (1 << CS01); + + //TCCR1 |= (1 << PWM1A) | (1 << COM1A1) | (1 << PWM1B) | (1 << COM1B1); + //GTCCR |= (1 << PWM1B); +#endif +#endif + return true; +} + +void Led::cleanup() +{ +} + +void Led::set(RGBColor col) +{ + m_ledColor = col; + m_realColor.red = SCALE8(m_ledColor.red, m_brightness); + m_realColor.green = SCALE8(m_ledColor.green, m_brightness); + m_realColor.blue = SCALE8(m_ledColor.blue, m_brightness); +} + +void Led::adjustBrightness(uint8_t fadeBy) +{ + m_ledColor.adjustBrightness(fadeBy); +} + +void Led::strobe(uint16_t on_time, uint16_t off_time, RGBColor off_col, RGBColor on_col) +{ + set(((Time::getCurtime() % (on_time + off_time)) > on_time) ? off_col : on_col); +} + +void Led::breath(uint8_t hue, uint32_t duration, uint8_t magnitude, uint8_t sat, uint8_t val) +{ + if (!duration) { + // don't divide by 0 + return; + } + // Determine the phase in the cycle + uint32_t phase = Time::getCurtime() % (2 * duration); + // Calculate hue shift + int32_t hueShift; + if (phase < duration) { + // Ascending phase - from hue to hue + magnitude + hueShift = (phase * magnitude) / duration; + } else { + // Descending phase - from hue + magnitude to hue + hueShift = ((2 * duration - phase) * magnitude) / duration; + } + // Apply hue shift - ensure hue stays within valid range + uint8_t shiftedHue = hue + hueShift; + // Apply the hsv color as a strobing hue shift + strobe(2, 13, RGB_OFF, HSVColor(shiftedHue, sat, val)); +} + +void Led::hold(RGBColor col) +{ + set(col); + update(); + Time::delayMilliseconds(250); +} + +void Led::update() +{ +#ifdef HELIOS_EMBEDDED + // write out the rgb values to analog pins +#ifdef HELIOS_ARDUINO + analogWrite(PWM_PIN_R, m_realColor.red); + analogWrite(PWM_PIN_G, m_realColor.green); + analogWrite(PWM_PIN_B, m_realColor.blue); +#else + // a counter to keep track of milliseconds for the PWM + static uint8_t counter = 0; + counter++; + // run the software PWM on each pin + if (counter < m_realColor.red) { + PORTB |= (1 << PWM_PIN_R); + } else { + PORTB &= ~(1 << PWM_PIN_R); + } + if (counter < m_realColor.green) { + PORTB |= (1 << PWM_PIN_G); + } else { + PORTB &= ~(1 << PWM_PIN_G); + } + if (counter < m_realColor.blue) { + PORTB |= (1 << PWM_PIN_B); + } else { + PORTB &= ~(1 << PWM_PIN_B); + } +#endif +#endif +} diff --git a/Helios/Led.h b/Helios/Led.h new file mode 100644 index 00000000..16d96191 --- /dev/null +++ b/Helios/Led.h @@ -0,0 +1,57 @@ +#ifndef LED_CONTROL_H +#define LED_CONTROL_H + +#include + +#include "Colortypes.h" + +class Led +{ + // private unimplemented constructor + Led(); + +public: + // opting for static class here because there should only ever be one + // Led control object and I don't like singletons + static bool init(); + static void cleanup(); + + // control individual LED, these are appropriate to use in internal pattern logic + static void set(RGBColor col); + + // Turn off individual LEDs, these are appropriate to use in internal pattern logic + static void clear() { set(RGB_OFF); } + + // Dim individual LEDs, these are appropriate to use in internal pattern logic + static void adjustBrightness(uint8_t fadeBy); + + // strobe between two colors with a simple on/off timing + static void strobe(uint16_t on_time, uint16_t off_time, RGBColor col1, RGBColor col2); + + // breath the hue on an index + // warning: these use hsv to rgb in realtime! + static void breath(uint8_t hue, uint32_t duration = 1000, uint8_t magnitude = 60, + uint8_t sat = 255, uint8_t val = 255); + + // a very specialized api to hold all leds on a color for 250ms + static void hold(RGBColor col); + + // get the RGBColor of an Led index + static RGBColor get() { return m_ledColor; } + + // global brightness + static uint8_t getBrightness() { return m_brightness; } + static void setBrightness(uint8_t brightness) { m_brightness = brightness; } + + // actually update the LEDs and show the changes + static void update(); + +private: + // the global brightness + static uint8_t m_brightness; + // led color + static RGBColor m_ledColor; + static RGBColor m_realColor; +}; + +#endif diff --git a/Helios/Pattern.cpp b/Helios/Pattern.cpp new file mode 100644 index 00000000..1fd6a72e --- /dev/null +++ b/Helios/Pattern.cpp @@ -0,0 +1,294 @@ +#include "Pattern.h" + +//#include "../Patterns/PatternBuilder.h" +#include "TimeControl.h" +#include "Colorset.h" + +#include "HeliosConfig.h" +#include "Led.h" + +#include // for memcpy + +// uncomment me to print debug labels on the pattern states, this is useful if you +// are debugging a pattern strip from the command line and want to see what state +// the pattern is in each tick of the pattern +//#define DEBUG_BASIC_PATTERN + +#ifdef DEBUG_BASIC_PATTERN +#include "../../Time/TimeControl.h" +#include +// print out the current state of the pattern +#define PRINT_STATE(state) printState(state) +static void printState(PatternState state) +{ + static uint64_t lastPrint = 0; + if (lastPrint == Time::getCurtime()) return; + switch (m_state) { + case STATE_ON: printf("on "); break; + case STATE_OFF: printf("off "); break; + case STATE_IN_GAP: printf("gap1"); break; + case STATE_IN_DASH: printf("dash"); break; + case STATE_IN_GAP2: printf("gap2"); break; + default: return; + } + lastPrint = Time::getCurtime(); +} +#else +#define PRINT_STATE(state) // do nothing +#endif + +Pattern::Pattern(uint8_t onDur, uint8_t offDur, uint8_t gap, + uint8_t dash, uint8_t group, uint8_t blend, uint8_t flips) : + m_onDuration(onDur), + m_offDuration(offDur), + m_gapDuration(gap), + m_dashDuration(dash), + m_groupSize(group), + m_blendSpeed(blend), + m_numFlips(flips), + m_patternFlags(0), + m_colorset(), + m_groupCounter(0), + m_state(STATE_BLINK_ON), + m_blinkTimer(), + m_cur(), + m_next(), + m_flip(0) +{ +} + +Pattern::Pattern(const PatternArgs &args) : + Pattern(args.on_dur, args.off_dur, args.gap_dur, + args.dash_dur, args.group_size, args.blend_speed, args.num_flips) +{ +} + +Pattern::~Pattern() +{ +} + +void Pattern::init() +{ + m_colorset.resetIndex(); + + // the default state to begin with + m_state = STATE_BLINK_ON; + // if a dash is present then always start with the dash because + // it consumes the first color in the colorset + if (m_dashDuration > 0) { + m_state = STATE_BEGIN_DASH; + } + // if there's no on duration or dash duration the led is just disabled + if ((!m_onDuration && !m_dashDuration) || !m_colorset.numColors()) { + m_state = STATE_DISABLED; + } + m_groupCounter = m_groupSize ? m_groupSize : (m_colorset.numColors() - (m_dashDuration != 0)); + + if (m_blendSpeed > 0) { + // convert current/next colors to HSV but only if we are doing a blend + m_cur = m_colorset.getNext(); + m_next = m_colorset.getNext(); + } + // reset the flip count + m_flip = 0; +} + +void Pattern::play() +{ + // Sometimes the pattern needs to cycle multiple states in a single frame so + // instead of using a loop or recursion I have just used a simple goto +replay: + + // its kinda evolving as i go + switch (m_state) { + case STATE_DISABLED: + return; + case STATE_BLINK_ON: + if (m_onDuration > 0) { + onBlinkOn(); + --m_groupCounter; + nextState(m_onDuration); + return; + } + m_state = STATE_BLINK_OFF; + case STATE_BLINK_OFF: + // the whole 'should blink off' situation is tricky because we might need + // to go back to blinking on if our colorset isn't at the end yet + if (m_groupCounter > 0 || (!m_gapDuration && !m_dashDuration)) { + if (m_offDuration > 0) { + onBlinkOff(); + nextState(m_offDuration); + return; + } + if (m_groupCounter > 0 && m_onDuration > 0) { + m_state = STATE_BLINK_ON; + goto replay; + } + } + m_state = STATE_BEGIN_GAP; + case STATE_BEGIN_GAP: + m_groupCounter = m_groupSize ? m_groupSize : (m_colorset.numColors() - (m_dashDuration != 0)); + if (m_gapDuration > 0) { + beginGap(); + nextState(m_gapDuration); + return; + } + m_state = STATE_BEGIN_DASH; + case STATE_BEGIN_DASH: + if (m_dashDuration > 0) { + beginDash(); + nextState(m_dashDuration); + return; + } + m_state = STATE_BEGIN_GAP2; + case STATE_BEGIN_GAP2: + if (m_dashDuration > 0 && m_gapDuration > 0) { + beginGap(); + nextState(m_gapDuration); + return; + } + m_state = STATE_BLINK_ON; + goto replay; + default: + break; + } + + if (!m_blinkTimer.alarm()) { + // no alarm triggered just stay in current state, return and don't transition states + PRINT_STATE(m_state); + return; + } + + // this just transitions the state into the next state, with some edge conditions for + // transitioning to different states under certain circumstances. Honestly this is + // a nightmare to read now and idk how to fix it + if (m_state == STATE_IN_GAP2 || (m_state == STATE_OFF && m_groupCounter > 0)) { + // this is an edge condition for when in the second gap or off in the non-last off blink + // then the state actually needs to jump backwards rather than iterate + m_state = m_onDuration ? STATE_BLINK_ON : (m_dashDuration ? STATE_BEGIN_DASH : STATE_BEGIN_GAP); + } else if (m_state == STATE_OFF && (!m_groupCounter || m_colorset.numColors() == 1)) { + // this is an edge condition when the state is off but this is the last off blink in the + // group or there's literally only one color in the group then if there is more blinks + // left in the group we need to cycle back to blink on instead of to the next state + m_state = (m_groupCounter > 0) ? STATE_BLINK_ON : STATE_BEGIN_GAP; + } else { + // this is the standard case, iterate to the next state + m_state = (PatternState)(m_state + 1); + } + // poor-mans recurse with the new state change (this transitions to a new state within the same tick) + goto replay; +} + +// set args +void Pattern::setArgs(const PatternArgs &args) +{ + memcpy(&m_onDuration, &(args.on_dur), 7); +} + +void Pattern::onBlinkOn() +{ + PRINT_STATE(STATE_ON); + if (isBlend()) { + blendBlinkOn(); + return; + } + Led::set(m_colorset.getNext()); +} + +void Pattern::onBlinkOff() +{ + PRINT_STATE(STATE_OFF); + Led::clear(); +} + +void Pattern::beginGap() +{ + PRINT_STATE(STATE_IN_GAP); + Led::clear(); +} + +void Pattern::beginDash() +{ + PRINT_STATE(STATE_IN_DASH); + Led::set(m_colorset.getNext()); +} + +void Pattern::nextState(uint8_t timing) +{ + m_blinkTimer.init(timing); + m_state = (PatternState)(m_state + 1); +} + +bool Pattern::equals(const Pattern *other) +{ + if (!other) { + return false; + } + // compare the colorset + if (!m_colorset.equals(&other->m_colorset)) { + return false; + } + // then compare each arg + if (m_onDuration != other->m_onDuration || + m_offDuration != other->m_offDuration || + m_gapDuration != other->m_gapDuration || + m_dashDuration != other->m_dashDuration || + m_groupSize != other->m_groupSize || + m_blendSpeed != other->m_blendSpeed || + m_numFlips != other->m_numFlips) { + return false; + } + // if those match then it's effectively the same + // pattern even if anything else is different + return true; +} + +// change the colorset +void Pattern::setColorset(const Colorset &set) +{ + m_colorset = set; +} + +void Pattern::clearColorset() +{ + m_colorset.clear(); +} + +void Pattern::blendBlinkOn() +{ + if (m_cur == m_next) { + m_next = m_colorset.getNext(); + } + interpolate(m_cur.red, m_next.red); + interpolate(m_cur.green, m_next.green); + interpolate(m_cur.blue, m_next.blue); + RGBColor col = m_cur; + if (m_flip) { + // convert to hsv + HSVColor hsvCol = m_cur; + // shift the hue by a flip size + hsvCol.hue += (m_flip * (127 / m_numFlips)); + // convert the hsv color back to RGB + col = hsvCol; + } + // set the color + Led::set(col); + // increment the flip count + m_flip++; + // modulate the flip count DO NOT USE MODULO OPERATOR BECAUSE + // THE FLIP COUNT COULD BE 0 THAT WILL DIVIDE BY ZERO + if (m_flip > m_numFlips) { + m_flip = 0; + } +} + +void Pattern::interpolate(uint8_t ¤t, const uint8_t next) +{ + if (current < next) { + uint8_t step = (next - current) > m_blendSpeed ? m_blendSpeed : (next - current); + current += step; + } else if (current > next) { + uint8_t step = (current - next) > m_blendSpeed ? m_blendSpeed : (current - next); + current -= step; + } +} diff --git a/Helios/Pattern.h b/Helios/Pattern.h new file mode 100644 index 00000000..1b323e77 --- /dev/null +++ b/Helios/Pattern.h @@ -0,0 +1,140 @@ +#ifndef PATTERN_H +#define PATTERN_H + +#include "Colorset.h" + +#include "Timer.h" +#include "Patterns.h" + +// for specifying things like default args +struct PatternArgs { + PatternArgs() : on_dur(0), off_dur(0), gap_dur(0), dash_dur(0), + group_size(0), blend_speed(0), num_flips(0) + {} + uint8_t on_dur; + uint8_t off_dur; + uint8_t gap_dur; + uint8_t dash_dur; + uint8_t group_size; + uint8_t blend_speed; + uint8_t num_flips; +}; + +class Pattern +{ +public: + // try to not set on duration to 0 + Pattern(uint8_t onDur = 1, uint8_t offDur = 0, uint8_t gap = 0, + uint8_t dash = 0, uint8_t group = 0, uint8_t blend = 0, + uint8_t flips = 0); + Pattern(const PatternArgs &args); + ~Pattern(); + + // init the pattern to initial state + void init(); + + // pure virtual must override the play function + void play(); + + // set args + void setArgs(const PatternArgs &args); + + // comparison to other pattern + // NOTE: That may cause problems because the parameter is still a Pattern * + // which means comparison would need to cast the other upwards first + // NOTE2: Removing virtual because this probably shouldn't be overridden + bool equals(const Pattern *other); + + // change the colorset + const Colorset getColorset() const { return m_colorset; } + Colorset getColorset() { return m_colorset; } + Colorset &colorset() { return m_colorset; } + void setColorset(const Colorset &set); + void clearColorset(); + + // get the pattern flags + uint32_t getFlags() const { return m_patternFlags; } + bool hasFlags(uint32_t flags) const { return (m_patternFlags & flags) != 0; } + + // whether blend speed is non 0 + bool isBlend() const { return m_blendSpeed > 0; } + +protected: + // ================================== + // Pattern Parameters + uint8_t m_onDuration; + uint8_t m_offDuration; + uint8_t m_gapDuration; + uint8_t m_dashDuration; + uint8_t m_groupSize; + uint8_t m_blendSpeed; + uint8_t m_numFlips; + + // ================================== + // Pattern Members + + // any flags the pattern has + uint8_t m_patternFlags; + // a copy of the colorset that this pattern is initialized with + Colorset m_colorset; + + // ================================== + // Blink Members + uint8_t m_groupCounter; + + // apis for blink + void onBlinkOn(); + void onBlinkOff(); + void beginGap(); + void beginDash(); + void nextState(uint8_t timing); + + // the various different blinking states the pattern can be in + enum PatternState : uint8_t + { + // the led is disabled (there is no on or dash) + STATE_DISABLED, + + // the pattern is blinking on the next color in the set + STATE_BLINK_ON, + STATE_ON, + + // the pattern is blinking off + STATE_BLINK_OFF, + STATE_OFF, + + // the pattern is starting a gap after a colorset + STATE_BEGIN_GAP, + STATE_IN_GAP, + + // the pattern is beginning a dash after a colorset or gap + STATE_BEGIN_DASH, + STATE_IN_DASH, + + // the pattern is starting a gap after a dash + STATE_BEGIN_GAP2, + STATE_IN_GAP2, + }; + + // the state of the current pattern + PatternState m_state; + + // the blink timer used to measure blink timings + Timer m_blinkTimer; + + // ================================== + // Blend Members + + // current color and target blend color + RGBColor m_cur; + RGBColor m_next; + + // the current flip counter + uint8_t m_flip; + + // apis for blend + void blendBlinkOn(); + void interpolate(uint8_t ¤t, const uint8_t next); +}; + +#endif diff --git a/Helios/Patterns.cpp b/Helios/Patterns.cpp new file mode 100644 index 00000000..62eb9b10 --- /dev/null +++ b/Helios/Patterns.cpp @@ -0,0 +1,221 @@ +#include "Patterns.h" + +#include "Storage.h" +#include "Pattern.h" + +// define arrays of colors, you can reuse these if you have multiple +// modes that use the same colorset -- these demonstrate the max amount +// of colors in each set but you can absolutely list a lesser amount +static const uint32_t color_codes0[] = {RGB_RED, RGB_ORANGE, RGB_YELLOW, RGB_TURQUOISE, RGB_BLUE, RGB_PINK}; +static const uint32_t color_codes1[] = {RGB_RED, RGB_CORAL_ORANGE_SAT_MEDIUM, RGB_ORANGE, RGB_YELLOW_SAT_LOW}; +static const uint32_t color_codes2[] = {RGB_PURPLE_SAT_MEDIUM, RGB_RED_BRI_LOWEST, RGB_MAGENTA_BRI_LOWEST, RGB_BLUE_BRI_LOWEST}; +static const uint32_t color_codes3[] = {RGB_MAGENTA, RGB_YELLOW, RGB_TURQUOISE, RGB_PINK_SAT_LOW, RGB_RED, RGB_YELLOW}; +static const uint32_t color_codes4[] = {RGB_MAGENTA_BRI_LOWEST, RGB_ROYAL_BLUE_BRI_LOW, RGB_TURQUOISE, RGB_ROYAL_BLUE_BRI_LOW, RGB_MAGENTA_BRI_LOWEST, RGB_OFF}; +static const uint32_t color_codes5[] = {RGB_RED, RGB_HOT_PINK, RGB_ROYAL_BLUE, RGB_BLUE, RGB_GREEN, RGB_YELLOW}; + +// Define Colorset configurations for each slot +struct default_colorset { + uint8_t num_cols; + const uint32_t *cols; +}; + +// the array of colorset entries, make sure the number on the left reflects +// the number of colors in the array on the right +static const default_colorset default_colorsets[] = { + { 6, color_codes0 }, // 0 Lightside + { 4, color_codes1 }, // 1 Sauna + { 4, color_codes2 }, // 2 UltraViolet + { 6, color_codes3 }, // 3 Space Carnival + { 6, color_codes4 }, // 4 Ice Blade + { 6, color_codes5 }, // 5 Rainbow Glitter +}; + +void Patterns::make_default(uint8_t index, Pattern &pat) +{ + if (index >= NUM_MODE_SLOTS) { + return; + } + PatternArgs args; + switch (index) { + case 0: // Lightside + args.on_dur = 2; + args.gap_dur = 40; + break; + case 1: // Sauna + args.on_dur = 1; + args.off_dur = 9; + break; + case 2: // UltraViolet + args.on_dur = 9; + break; + case 3: // Space Carnival + args.on_dur = 3; + args.off_dur = 23; + break; + case 4: // Ice Blade + args.on_dur = 3; + args.off_dur = 1; + break; + case 5: // Rainbow Glitter + args.on_dur = 1; + args.off_dur = 50; + break; + } + // assign default args + pat.setArgs(args); + // build the set out of the defaults + Colorset set(default_colorsets[index].num_cols, default_colorsets[index].cols); + // assign default colorset + pat.setColorset(set); +} + +void Patterns::make_pattern(PatternID id, Pattern &pat) +{ + PatternArgs args; + switch (id) { + default: + case PATTERN_STROBEGAP: + args.gap_dur = 25; + case PATTERN_STROBE: + args.on_dur = 5; + args.off_dur = 8; + break; + case PATTERN_FLARE: + args.on_dur = 1; + args.off_dur = 30; + break; + case PATTERN_GLOW: + args.on_dur = 2; + args.gap_dur = 40; + break; + case PATTERN_FLICKER: + args.on_dur = 1; + args.off_dur = 50; + break; + case INOVA_BLINK: + args.on_dur = 10; + args.off_dur = 250; + break; + case PATTERN_HYPERGAP: + args.gap_dur = 218; + case PATTERN_HYPERSTROBE: + args.on_dur = 16; + args.off_dur = 20; + break; + case PATTERN_ULTRA_DOPS: + args.on_dur = 3; + args.off_dur = 1; + break; + case PATTERN_STROBIEGAP: + args.gap_dur = 100; + case PATTERN_STROBIE: + args.on_dur = 3; + args.off_dur = 23; + break; + case PATTERN_DOPSGAP: + args.gap_dur = 40; + case PATTERN_DOPS: + args.on_dur = 1; + args.off_dur = 9; + break; + case PATTERN_BLINKIE: + args.on_dur = 3; + args.off_dur = 1; + args.gap_dur = 150; + break; + case PATTERN_GHOSTCRUSH: + args.on_dur = 3; + args.off_dur = 1; + args.gap_dur = 18; + break; + case PATTERN_DOUBLEDOPS: + args.on_dur = 1; + args.off_dur = 1; + args.gap_dur = 10; + args.group_size = 2; + break; + case PATTERN_CHOPPER: + args.on_dur = 1; + args.off_dur = 1; + args.gap_dur = 10; + args.group_size = 2; + break; + case PATTERN_DASHGAP: + args.on_dur = 1; + args.off_dur = 1; + args.gap_dur = 20; + args.dash_dur = 20; + break; + case PATTERN_DASHDOPS: + args.on_dur = 1; + args.off_dur = 10; + args.gap_dur = 10; + args.dash_dur = 18; + break; + case PATTERN_DASHCRUSH: + args.on_dur = 4; + args.off_dur = 1; + args.gap_dur = 10; + args.dash_dur = 18; + break; + case PATTERN_ULTRADASH: + args.on_dur = 1; + args.off_dur = 3; + args.gap_dur = 3; + args.dash_dur = 14; + break; + case PATTERN_GAPCYCLE: + args.on_dur = 2; + args.off_dur = 6; + args.gap_dur = 12; + args.dash_dur = 25; + args.group_size = 2; + break; + case PATTERN_DASHCYCLE: + args.on_dur = 1; + args.off_dur = 3; + args.gap_dur = 3; + args.dash_dur = 30; + args.group_size = 2; + break; + case PATTERN_TRACER: + args.on_dur = 3; + args.dash_dur = 20; + args.group_size = 1; + break; + case PATTERN_RIBBON: + args.on_dur = 9; + break; + case PATTERN_MINIRIBBON: + args.on_dur = 1; + break; + case PATTERN_COMPLEMENTARY_BLEND: + args.num_flips = 1; + case PATTERN_BLEND: + args.on_dur = 2; + args.off_dur = 13; + args.blend_speed = 5; + break; + case PATTERN_COMPLEMENTARY_BLENDSTROBE: + args.num_flips = 1; + case PATTERN_BLEND_STROBE: + args.on_dur = 5; + args.off_dur = 8; + args.blend_speed = 10; + break; + case PATTERN_BLEND_STROBIE: + args.on_dur = 3; + args.off_dur = 23; + args.blend_speed = 10; + break; + case PATTERN_COMPLEMENTARY_BLENDSTROBEGAP: + args.num_flips = 1; + case PATTERN_BLENDSTROBEGAP: + args.on_dur = 6; + args.off_dur = 6; + args.gap_dur = 25; + args.blend_speed = 10; + break; + } + pat.setArgs(args); +} diff --git a/Helios/Patterns.h b/Helios/Patterns.h new file mode 100644 index 00000000..49781948 --- /dev/null +++ b/Helios/Patterns.h @@ -0,0 +1,78 @@ +#ifndef PATTERNS_H +#define PATTERNS_H + +#include + +// List of patterns that can be built, both single and multi-led patterns are found in this list. +// Within both single and multi LED pattern lists there are 'core' patterns which are associated +// with a class, and there are 'shell' patterns which are simply wrapperns around another pattern +// with different parameters passed to the constructor. There is no way to know which patterns +// are 'core' patterns, except by looking at PatternBuilder::generate to see which classes exist +enum PatternID : int8_t { + // no pattern at all, use this sparingly and default to + // PATTERN_FIRST when possible + PATTERN_NONE = (PatternID)-1, + + // first pattern of all + PATTERN_FIRST = 0, + // ===================================== + + // all 'single led' patterns below + + PATTERN_RIBBON = PATTERN_FIRST, + PATTERN_ULTRA_DOPS, + PATTERN_DOPS, + PATTERN_STROBE, + PATTERN_STROBIE, + PATTERN_HYPERSTROBE, + PATTERN_FLARE, + PATTERN_GLOW, + PATTERN_FLICKER, + PATTERN_BLINKIE, + INOVA_BLINK, + PATTERN_BLEND, + PATTERN_BLEND_STROBE, + PATTERN_BLEND_STROBIE, + PATTERN_DASHCYCLE, + PATTERN_ULTRADASH, + PATTERN_DASHDOPS, + PATTERN_TRACER, + PATTERN_MINIRIBBON, + + PATTERN_BLENDSTROBEGAP, + + PATTERN_STROBEGAP, + PATTERN_HYPERGAP, + PATTERN_STROBIEGAP, + PATTERN_DOPSGAP, + PATTERN_GHOSTCRUSH, + PATTERN_DOUBLEDOPS, + PATTERN_CHOPPER, + PATTERN_DASHGAP, + + PATTERN_DASHCRUSH, + + PATTERN_GAPCYCLE, + + PATTERN_COMPLEMENTARY_BLEND, + PATTERN_COMPLEMENTARY_BLENDSTROBE, + PATTERN_COMPLEMENTARY_BLENDSTROBEGAP, + PATTERN_SOLID, + + // ADD NEW PATTERNS HERE + + // Meta pattern constants + INTERNAL_PATTERNS_END, + PATTERN_LAST = (INTERNAL_PATTERNS_END - 1), + PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, // total number of patterns +}; + +class Pattern; + +class Patterns { + public: + static void make_default(uint8_t index, Pattern &pat); + static void make_pattern(PatternID id, Pattern &pat); +}; + +#endif diff --git a/Helios/Random.cpp b/Helios/Random.cpp new file mode 100644 index 00000000..fb10f201 --- /dev/null +++ b/Helios/Random.cpp @@ -0,0 +1,45 @@ +#include "Random.h" + +Random::Random() : + m_seed(0) +{ +} + +Random::Random(uint32_t newseed) : + Random() +{ + seed(newseed); +} + +Random::~Random() +{ +} + +void Random::seed(uint32_t newseed) +{ + if (!newseed) { + m_seed = 42; + } + m_seed = newseed; +} + +uint16_t Random::next16(uint16_t minValue, uint16_t maxValue) +{ + // walk the LCG forward to the next step + m_seed = (m_seed * 1103515245 + 12345) & 0x7FFFFFFF; + uint32_t range = maxValue - minValue; + if (range != 0xFFFFFFFF) { + // shift the seed 16 bits to the right because the lower 16 bits + // of this LCG are apparently not uniform whatsoever, where as the + // upper 16 bits appear to be quite uniform as per tests. We don't + // really need 32bit random values so we offer max 16bits of entropy + return ((m_seed >> 16) % (range + 1)) + minValue; + } + return (m_seed >> 16); +} + +uint8_t Random::next8(uint8_t minValue, uint8_t maxValue) +{ + uint32_t result = next16(minValue, maxValue); + return static_cast(result); +} diff --git a/Helios/Random.h b/Helios/Random.h new file mode 100644 index 00000000..1f556f42 --- /dev/null +++ b/Helios/Random.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class Random +{ +public: + Random(); + Random(uint32_t newseed); + ~Random(); + + void seed(uint32_t newseed); + + uint8_t next8(uint8_t minValue = 0, uint8_t maxValue = 0xFF); + uint16_t next16(uint16_t minValue = 0, uint16_t maxValue = 0xFFFF); + +private: + uint32_t m_seed; +}; + diff --git a/Helios/Storage.cpp b/Helios/Storage.cpp new file mode 100644 index 00000000..4ddd18c4 --- /dev/null +++ b/Helios/Storage.cpp @@ -0,0 +1,204 @@ +#include "Storage.h" + +#include "Colorset.h" + +#ifdef HELIOS_EMBEDDED +#include +#endif + +#ifdef HELIOS_CLI +#include +#include +#include +#include +#include +#define STORAGE_FILENAME "Helios.storage" +#endif + +// the index of the crc of the config bytes +#define CONFIG_CRC_INDEX 255 +// the index of the last config byte (or first counting down) +#define CONFIG_START_INDEX 254 + +#ifdef HELIOS_CLI +// whether storage is enabled, default enabled +bool Storage::m_enableStorage = true; +#endif + +bool Storage::init() +{ +#ifdef HELIOS_CLI + // may want this for cli tool + //unlink(STORAGE_FILENAME); +#endif + return true; +} + +bool Storage::read_pattern(uint8_t slot, Pattern &pat) +{ + uint8_t pos = slot * SLOT_SIZE; + if (!check_crc(pos)) { + return false; + } + for (uint8_t i = 0; i < PATTERN_SIZE; ++i) { + ((uint8_t *)&pat)[i] = read_byte(pos + i); + } + return true; +} + +void Storage::write_pattern(uint8_t slot, const Pattern &pat) +{ + uint8_t pos = slot * SLOT_SIZE; + for (uint8_t i = 0; i < PATTERN_SIZE; ++i) { + uint8_t val = ((uint8_t *)&pat)[i]; + uint8_t target = pos + i; + // reads out the byte of the eeprom first to see if it's different + // before writing out the byte -- this is faster than always writing + if (val != read_byte(target)) { + write_byte(target, val); + } + } + write_crc(pos); +} + +void Storage::swap_pattern(uint8_t slot1, uint8_t slot2) +{ + uint8_t pos1 = slot1 * SLOT_SIZE; + uint8_t pos2 = slot2 * SLOT_SIZE; + for (uint8_t i = 0; i < SLOT_SIZE; ++i) { + uint8_t b1 = read_byte(pos1 + i); + uint8_t b2 = read_byte(pos2 + i); + write_byte(pos1 + i, b2); + write_byte(pos2 + i, b1); + } +} + +uint8_t Storage::read_config(uint8_t index) +{ + return read_byte(CONFIG_START_INDEX - index); +} + +void Storage::write_config(uint8_t index, uint8_t val) +{ + write_byte(CONFIG_START_INDEX - index, val); +} + +uint8_t Storage::crc8(uint8_t pos, uint8_t size) +{ + uint8_t hash = 33; // A non-zero initial value + for (uint8_t i = 0; i < size; ++i) { + hash = ((hash << 5) + hash) + read_byte(pos); + } + return hash; +} + +uint8_t Storage::crc_pos(uint8_t pos) +{ + // crc the entire slot except last byte + return crc8(pos, PATTERN_SIZE); +} + +uint8_t Storage::read_crc(uint8_t pos) +{ + // read the last byte of the slot + return read_byte(pos + PATTERN_SIZE); +} + +bool Storage::check_crc(uint8_t pos) +{ + // compare the last byte to the calculated crc + return (read_crc(pos) == crc_pos(pos)); +} + +void Storage::write_crc(uint8_t pos) +{ + // compare the last byte to the calculated crc + write_byte(pos + PATTERN_SIZE, crc_pos(pos)); +} + +void Storage::write_byte(uint8_t address, uint8_t data) +{ +#ifdef HELIOS_EMBEDDED + /* Wait for completion of previous write */ + while(EECR & (1< +#include "HeliosConfig.h" + +class Pattern; + +class Storage +{ +public: + + static bool init(); + + static bool read_pattern(uint8_t slot, Pattern &pat); + static void write_pattern(uint8_t slot, const Pattern &pat); + + static void swap_pattern(uint8_t slot1, uint8_t slot2); + + static uint8_t read_config(uint8_t index); + static void write_config(uint8_t index, uint8_t val); + +#ifdef HELIOS_CLI + // toggle storage on/off + static void enableStorage(bool enabled) { m_enableStorage = enabled; } +#endif +private: + static uint8_t crc8(uint8_t pos, uint8_t size); + static uint8_t crc_pos(uint8_t pos); + static uint8_t read_crc(uint8_t pos); + static bool check_crc(uint8_t pos); + static void write_crc(uint8_t pos); + static void write_byte(uint8_t address, uint8_t data); + static uint8_t read_byte(uint8_t address); + +#ifdef HELIOS_CLI + // whether storage is enabled + static bool m_enableStorage; +#endif +}; + +#endif diff --git a/Helios/TimeControl.cpp b/Helios/TimeControl.cpp new file mode 100644 index 00000000..04c98a87 --- /dev/null +++ b/Helios/TimeControl.cpp @@ -0,0 +1,165 @@ +#include "TimeControl.h" + +#include + +#include "Timings.h" + +#include "Led.h" + +#ifdef HELIOS_EMBEDDED +#include +#include +#ifdef HELIOS_ARDUINO +#include +#endif +#endif + +#ifdef HELIOS_CLI +#include +#include +uint64_t start = 0; +// convert seconds and nanoseconds to microseconds +#define SEC_TO_US(sec) ((sec)*1000000) +#define NS_TO_US(ns) ((ns)/1000) +#endif + +// static members +uint32_t Time::m_curTick = 0; +// the last frame timestamp +uint32_t Time::m_prevTime = 0; + +#ifdef HELIOS_CLI +// whether timestep is enabled, default enabled +bool Time::m_enableTimestep = true; +#endif + +bool Time::init() +{ + m_prevTime = microseconds(); + m_curTick = 0; + return true; +} + +void Time::cleanup() +{ +} + +void Time::tickClock() +{ + // tick clock forward + m_curTick++; + +#ifdef HELIOS_CLI + if (!m_enableTimestep) { + return; + } + // the rest of this only runs inside vortexlib because on the duo the tick runs in the + // tcb timer callback instead of in a busy loop constantly checking microseconds() + // perform timestep + uint32_t elapsed_us; + uint32_t us; + do { + us = microseconds(); + // detect rollover of microsecond counter + if (us < m_prevTime) { + // calculate wrapped around difference + elapsed_us = (uint32_t)((UINT32_MAX - m_prevTime) + us); + } else { + // otherwise calculate regular difference + elapsed_us = (uint32_t)(us - m_prevTime); + } + // if building anywhere except visual studio then we can run alternate sleep code + // because in visual studio + windows it's better to just spin and check the high + // resolution clock instead of trying to sleep for microseconds. + // 1000us per ms, divided by tickrate gives + // the number of microseconds per tick + } while (elapsed_us < (1000000 / TICKRATE)); + + // store current time + m_prevTime = microseconds(); +#endif +} + +uint32_t Time::microseconds() +{ +#ifdef HELIOS_CLI + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + uint64_t us = SEC_TO_US((uint64_t)ts.tv_sec) + NS_TO_US((uint64_t)ts.tv_nsec); + return (unsigned long)us; +#else +#ifdef HELIOS_ARDUINO + return micros(); +#else + // TODO: microseconds on attiny85 + return 0; +#endif +#endif +} + +#ifdef HELIOS_EMBEDDED +__attribute__((noinline)) +#endif +void +Time::delayMicroseconds(uint32_t us) +{ +#ifdef HELIOS_EMBEDDED +#if F_CPU >= 16000000L + // For the ATtiny85 running at 16MHz + + // The loop takes 3 cycles per iteration + us *= 2; // 0.5us per iteration + + // Subtract the overhead of the function call and loop setup + // Assuming approximately 5 cycles overhead + us -= 5; // Simplified subtraction + + // Assembly loop for delay + __asm__ __volatile__( + "1: sbiw %0, 1" + "\n\t" // 2 cycles + "nop" + "\n\t" // 1 cycle + "brne 1b" : "=w"(us) : "0"(us) // 2 cycles + ); + +#elif F_CPU >= 8000000L + // For the ATtiny85 running at 8MHz + + // The loop takes 4 cycles per iteration + us <<= 1; // 1us per iteration + + // Subtract the overhead of the function call and loop setup + // Assuming approximately 6 cycles overhead + us -= 6; // Simplified subtraction + + // Assembly loop for delay + __asm__ __volatile__( + "1: sbiw %0, 1" + "\n\t" // 2 cycles + "rjmp .+0" + "\n\t" // 2 cycles + "brne 1b" : "=w"(us) : "0"(us) // 2 cycles + ); +#endif + +#else + uint32_t newtime = microseconds() + us; + while (microseconds() < newtime) + { + // busy loop + } +#endif +} + +void Time::delayMilliseconds(uint32_t ms) +{ +#ifdef HELIOS_CLI + usleep(ms * 1000); +#else + // not very accurate + for (uint16_t i = 0; i < ms; ++i) { + delayMicroseconds(1000); + } +#endif +} diff --git a/Helios/TimeControl.h b/Helios/TimeControl.h new file mode 100644 index 00000000..b5f316d8 --- /dev/null +++ b/Helios/TimeControl.h @@ -0,0 +1,59 @@ +#ifndef TIME_CONTROL_H +#define TIME_CONTROL_H + +#include + +#include "HeliosConfig.h" + +// macros to convert milliseconds and seconds to measures of ticks +#define MS_TO_TICKS(ms) (uint32_t)(((uint32_t)(ms) * TICKRATE) / 1000) +#define SEC_TO_TICKS(s) (uint32_t)((uint32_t)(s) * TICKRATE) + +class Time +{ + // private unimplemented constructor + Time(); + +public: + // opting for static class here because there should only ever be one + // Settings control object and I don't like singletons + static bool init(); + static void cleanup(); + + // tick the clock forward to millis() + static void tickClock(); + + // get the current tick, offset by any active simulation (simulation only exists in vortexlib) + // Exposing this in the header seems to save on space a non negligible amount, it is used a lot + // and exposing in the header probably allows the compiler to optimize away repititive calls + static uint32_t getCurtime() { return m_curTick; } + + // Current microseconds since startup, only use this for things like measuring rapid data transfer timings. + // If you just need to perform regular time checks for a pattern or some logic then use getCurtime() and measure + // time in ticks, use the SEC_TO_TICKS() or MS_TO_TICKS() macros to convert timings to measures of ticks for + // purpose of comparing against getCurtime() + static uint32_t microseconds(); + + // delay for some number of microseconds or milliseconds, these are bad + static void delayMicroseconds(uint32_t us); + static void delayMilliseconds(uint32_t ms); + +#ifdef HELIOS_CLI + // toggle timestep on/off + static void enableTimestep(bool enabled) { m_enableTimestep = enabled; } +#endif + +private: + // global tick counter + static uint32_t m_curTick; + // the last frame timestamp + static uint32_t m_prevTime; + +#ifdef HELIOS_CLI + // whether timestep is enabled + static bool m_enableTimestep; +#endif +}; + +#endif + diff --git a/Helios/Timer.cpp b/Helios/Timer.cpp new file mode 100644 index 00000000..48dd685b --- /dev/null +++ b/Helios/Timer.cpp @@ -0,0 +1,59 @@ +#include + +#include "Timer.h" + +#include "TimeControl.h" + +Timer::Timer() : + m_alarm(0), + m_startTime(0) +{ +} + +Timer::~Timer() +{ +} + +void Timer::init(uint8_t alarm) +{ + reset(); + m_alarm = alarm; + start(); +} + +void Timer::start(uint32_t offset) +{ + // reset the start time + m_startTime = Time::getCurtime() + offset; +} + +void Timer::reset() +{ + m_alarm = 0; + m_startTime = 0; +} + +bool Timer::alarm() +{ + if (!m_alarm) { + return false; + } + uint32_t now = Time::getCurtime(); + // time since start (forward or backwards) + int32_t timeDiff = (int32_t)(int64_t)(now - m_startTime); + if (timeDiff < 0) { + return false; + } + // if no time passed it's first alarm that is starting + if (timeDiff == 0) { + return true; + } + // if the current alarm duration is not a multiple of the current tick + if (m_alarm && (timeDiff % m_alarm) != 0) { + // then the alarm was not hit + return false; + } + // update the start time of the timer + m_startTime = now; + return true; +} diff --git a/Helios/Timer.h b/Helios/Timer.h new file mode 100644 index 00000000..92a19a52 --- /dev/null +++ b/Helios/Timer.h @@ -0,0 +1,30 @@ +#ifndef TIMER_H +#define TIMER_H + +#include + +class Timer +{ +public: + Timer(); + ~Timer(); + + // init a timer with a number of alarms and optionally start it + void init(uint8_t alarm); + + // start the timer but don't change current alarm, this shifts + // the timer startTime but does not reset it's alarm state + void start(uint32_t offset = 0); + // delete all alarms from the timer and reset + void reset(); + // Will return the true if the timer hit + bool alarm(); + +private: + // the alarm + uint32_t m_alarm; + // start time in microseconds + uint32_t m_startTime; +}; + +#endif diff --git a/Helios/Timings.h b/Helios/Timings.h new file mode 100644 index 00000000..989ec6dc --- /dev/null +++ b/Helios/Timings.h @@ -0,0 +1,45 @@ +#ifndef TIMINGS_H +#define TIMINGS_H + +// Strobe Timings +// +// Below are timings for all different kinds of standard +// strobes as defined by the gloving community. All times +// are in milliseconds. + +// Basic Strobe +#define STROBE_ON_DURATION 6 +#define STROBE_OFF_DURATION 6 + +// Hyperstrobe +#define HYPERSTROBE_ON_DURATION 16 +#define HYPERSTROBE_OFF_DURATION 20 + +// Dops +#define PICOSTROBE_ON_DURATION 6 +#define PICOSTROBE_OFF_DURATION 40 + +// Dopy +#define DOPS_ON_DURATION 1 +#define DOPS_OFF_DURATION 10 + +// Ultradops +#define ULTRADOPS_ON_DURATION 1 +#define ULTRADOPS_OFF_DURATION 3 + +// Strobie +#define STROBIE_ON_DURATION 2 +#define STROBIE_OFF_DURATION 28 + +// Signal +#define SIGNAL_ON_DURATION 10 +#define SIGNAL_OFF_DURATION 296 + +// A blink speed good for blends +#define BLEND_ON_DURATION 2 +#define BLEND_OFF_DURATION 13 + +// Ribbon +#define RIBBON_DURATION 6 + +#endif From f45aac32510074a6b6e646a97754f0ad91ebb38d Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 19 Jan 2024 15:59:53 -0800 Subject: [PATCH 3/3] fixed workflow --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 503f28c7..526da2f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,8 +38,10 @@ jobs: python-version: '3.x' - name: Install Dependencies run: make install + working-directory: HeliosEmbedded - name: Build Binary run: make build + working-directory: HeliosEmbedded - name: Archive production artifacts uses: actions/upload-artifact@v3 with: