diff --git a/.gitignore b/.gitignore index 055c97e4..80409b73 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ !*.test + +!*.conf diff --git a/Helios/Button.cpp b/Helios/Button.cpp index b58ed095..20dd5bde 100644 --- a/Helios/Button.cpp +++ b/Helios/Button.cpp @@ -1,278 +1,359 @@ -#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; -bool Button::m_holdClick = 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_holdClick = 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 -} - -// detect if the button is being held for a long hold (past long click) -bool Button::holdPressing() -{ - uint16_t holDur = (uint16_t)(Button::holdDuration()); - if (holDur > HOLD_CLICK_START && holDur <= HOLD_CLICK_END && Button::isPressed()) { - return true; - } - return false; -} - -// 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) && (m_holdDuration < HOLD_CLICK_START)); - m_holdClick = (m_newRelease && (m_holdDuration >= HOLD_CLICK_START) && (m_holdDuration <= HOLD_CLICK_END)); - -#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_newRelease = true; - m_shortClick = true; - m_pressTime = Time::getCurtime(); - m_holdDuration = SHORT_CLICK_THRESHOLD - 1; - m_releaseCount++; -} - -void Button::doLongClick() -{ - m_newRelease = true; - m_longClick = true; - m_pressTime = Time::getCurtime(); - m_holdDuration = SHORT_CLICK_THRESHOLD + 1; - m_releaseCount++; -} - -void Button::doHoldClick() -{ - m_newRelease = true; - m_holdClick = true; - m_pressTime = Time::getCurtime(); - m_holdDuration = HOLD_CLICK_START + 1; - 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; +#include "Button.h" +#include "TimeControl.h" +#include "HeliosConfig.h" + +#ifdef HELIOS_EMBEDDED +#include +#include +#ifdef HELIOS_ARDUINO +#include +#endif +#define BUTTON_PIN 3 +#define BUTTON_PORT 2 +#endif + +// Forward declaration +#ifdef __cplusplus +extern "C" { +#endif +void helios_wakeup(void); +void helios_terminate(void); +#ifdef __cplusplus +} +#endif + +#ifdef HELIOS_CLI +// Forward declarations for CLI functions +static uint8_t button_process_pre_input(void); +static uint8_t button_process_post_input(void); +#endif + +// static members of Button +static uint32_t m_pressTime = 0; +static uint32_t m_releaseTime = 0; +static uint32_t m_holdDuration = 0; +static uint32_t m_releaseDuration = 0; +static uint8_t m_releaseCount = 0; +static uint8_t m_buttonState = 0; +static uint8_t m_newPress = 0; +static uint8_t m_newRelease = 0; +static uint8_t m_isPressed = 0; +static uint8_t m_shortClick = 0; +static uint8_t m_longClick = 0; +static uint8_t m_holdClick = 0; + +#ifdef HELIOS_CLI +static uint8_t m_pinState = 0; +static uint8_t m_enableWake = 0; +// an input queue for the button, each tick one even is processed +// out of this queue and used to produce input +#define INPUT_QUEUE_SIZE 4096 +static char m_inputQueue[INPUT_QUEUE_SIZE]; +static uint32_t m_queueHead = 0; +static uint32_t m_queueTail = 0; +#endif + +// initialize a new button object with a pin number +uint8_t button_init(void) +{ + m_pressTime = 0; + m_releaseTime = 0; + m_holdDuration = 0; + m_releaseDuration = 0; + m_newPress = 0; + m_newRelease = 0; + m_shortClick = 0; + m_longClick = 0; + m_holdClick = 0; + m_buttonState = button_check(); + m_releaseCount = !m_buttonState; + m_isPressed = m_buttonState; +#ifdef HELIOS_CLI + m_pinState = 0; + m_enableWake = 0; + m_queueHead = 0; + m_queueTail = 0; +#endif +#ifdef HELIOS_EMBEDDED +#ifdef HELIOS_ARDUINO + pinMode(3, INPUT); +#else + // turn off wake + PCMSK &= ~(1 << PCINT3); + GIMSK &= ~(1 << PCIE); +#endif +#endif + return 1; +} + +// enable wake on press +void button_enable_wake(void) +{ +#ifdef HELIOS_EMBEDDED + // Configure INT0 to trigger on falling edge + PCMSK |= (1 << PCINT3); + GIMSK |= (1 << PCIE); + sei(); +#else // HELIOS_CLI + m_enableWake = 1; +#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 +uint8_t button_check(void) +{ +#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; +#else + return 0; +#endif +} + +// detect if the button is being held for a long hold (past long click) +uint8_t button_hold_pressing(void) +{ + uint16_t holDur = (uint16_t)(button_hold_duration()); + if (holDur > HOLD_CLICK_START && holDur <= HOLD_CLICK_END && button_is_pressed()) { + return 1; + } + return 0; +} + +// poll the button pin and update the state of the button object +void button_update(void) +{ +#ifdef HELIOS_CLI + // process any pre-input events in the queue + uint8_t processed_pre = button_process_pre_input(); +#endif + + uint8_t newButtonState = button_check(); + m_newPress = 0; + m_newRelease = 0; + if (newButtonState != m_buttonState) { + m_buttonState = newButtonState; + m_isPressed = m_buttonState; + if (m_isPressed) { + m_pressTime = time_get_current_time(); + m_newPress = 1; + } else { + m_releaseTime = time_get_current_time(); + m_newRelease = 1; + m_releaseCount++; + } + } + if (m_isPressed) { + m_holdDuration = (time_get_current_time() >= m_pressTime) ? (uint32_t)(time_get_current_time() - m_pressTime) : 0; + } else { + m_releaseDuration = (time_get_current_time() >= m_releaseTime) ? (uint32_t)(time_get_current_time() - m_releaseTime) : 0; + } + m_shortClick = (m_newRelease && (m_holdDuration <= SHORT_CLICK_THRESHOLD)); + m_longClick = (m_newRelease && (m_holdDuration > SHORT_CLICK_THRESHOLD) && (m_holdDuration < HOLD_CLICK_START)); + m_holdClick = (m_newRelease && (m_holdDuration >= HOLD_CLICK_START) && (m_holdDuration <= HOLD_CLICK_END)); + +#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) { + button_process_post_input(); + } + + if (m_enableWake) { + if (m_isPressed || m_shortClick || m_longClick) { + helios_wakeup(); + } + } +#endif +} + +uint8_t button_on_press(void) +{ + return m_newPress; +} + +uint8_t button_on_release(void) +{ + return m_newRelease; +} + +uint8_t button_is_pressed(void) +{ + return m_isPressed; +} + +uint8_t button_on_short_click(void) +{ + return m_shortClick; +} + +uint8_t button_on_long_click(void) +{ + return m_longClick; +} + +uint8_t button_on_hold_click(void) +{ + return m_holdClick; +} + +uint32_t button_press_time(void) +{ + return m_pressTime; +} + +uint32_t button_release_time(void) +{ + return m_releaseTime; +} + +uint32_t button_hold_duration(void) +{ + return m_holdDuration; +} + +uint32_t button_release_duration(void) +{ + return m_releaseDuration; +} + +uint8_t button_release_count(void) +{ + return m_releaseCount; +} + +#ifdef HELIOS_CLI +static uint8_t button_process_pre_input(void) +{ + if (m_queueHead == m_queueTail) { + return 0; + } + char command = m_inputQueue[m_queueHead]; + switch (command) { + case 'p': // press + button_do_press(); + break; + case 'r': // release + button_do_release(); + break; + case 't': // toggle + button_do_toggle(); + 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 0; + } + // now pop whatever pre-input command was processed + m_queueHead = (m_queueHead + 1) % INPUT_QUEUE_SIZE; + return 1; +} + +static uint8_t button_process_post_input(void) +{ + if (m_queueHead == m_queueTail) { + // probably processed the pre-input event already + return 0; + } + // process input queue from the command line + char command = m_inputQueue[m_queueHead]; + switch (command) { + case 'c': // click button + button_do_short_click(); + break; + case 'l': // long click button + button_do_long_click(); + break; + default: + // should never happen + return 0; + } + m_queueHead = (m_queueHead + 1) % INPUT_QUEUE_SIZE; + return 1; +} + +void button_do_short_click(void) +{ + m_newRelease = 1; + m_shortClick = 1; + m_pressTime = time_get_current_time(); + m_holdDuration = SHORT_CLICK_THRESHOLD - 1; + m_releaseCount++; +} + +void button_do_long_click(void) +{ + m_newRelease = 1; + m_longClick = 1; + m_pressTime = time_get_current_time(); + m_holdDuration = SHORT_CLICK_THRESHOLD + 1; + m_releaseCount++; +} + +void button_do_hold_click(void) +{ + m_newRelease = 1; + m_holdClick = 1; + m_pressTime = time_get_current_time(); + m_holdDuration = HOLD_CLICK_START + 1; + 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_do_press(void) +{ + m_pinState = 1; +} + +void button_do_release(void) +{ + m_pinState = 0; +} + +void button_do_toggle(void) +{ + m_pinState = !m_pinState; +} + +// queue up an input event for the button +void button_queue_input(char input) +{ + uint32_t nextTail = (m_queueTail + 1) % INPUT_QUEUE_SIZE; + if (nextTail != m_queueHead) { + m_inputQueue[m_queueTail] = input; + m_queueTail = nextTail; + } +} + +uint32_t button_input_queue_size(void) +{ + if (m_queueTail >= m_queueHead) { + return m_queueTail - m_queueHead; + } else { + return INPUT_QUEUE_SIZE - m_queueHead + m_queueTail; + } +} +#endif + diff --git a/Helios/Button.h b/Helios/Button.h index 33d26ecf..56d195bb 100644 --- a/Helios/Button.h +++ b/Helios/Button.h @@ -1,119 +1,85 @@ -#include +#ifndef BUTTON_H +#define BUTTON_H -#ifdef HELIOS_CLI -#include +#ifdef __cplusplus +extern "C" { #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; } - // whether the button was hold clicked this tick - static bool onHoldClick() { return m_holdClick; } - - // detect if the button is being held past long click - static bool holdPressing(); - - // 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(); +#include + +// initialize a new button object with a pin number +uint8_t button_init(void); + +// directly poll the pin for whether it's pressed right now +uint8_t button_check(void); + +// poll the button pin and update the state of the button object +void button_update(void); + +// whether the button was pressed this tick +uint8_t button_on_press(void); + +// whether the button was released this tick +uint8_t button_on_release(void); + +// whether the button is currently pressed +uint8_t button_is_pressed(void); + +// whether the button was shortclicked this tick +uint8_t button_on_short_click(void); + +// whether the button was long clicked this tick +uint8_t button_on_long_click(void); + +// whether the button was hold clicked this tick +uint8_t button_on_hold_click(void); + +// detect if the button is being held past long click +uint8_t button_hold_pressing(void); + +// when the button was last pressed +uint32_t button_press_time(void); + +// when the button was last released +uint32_t button_release_time(void); + +// how long the button is currently or was last held down (in ticks) +uint32_t button_hold_duration(void); + +// how long the button is currently or was last released for (in ticks) +uint32_t button_release_duration(void); + +// the number of releases +uint8_t button_release_count(void); + +// enable wake on press +void button_enable_wake(void); #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(); - static void doHoldClick(); +// 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 +void button_do_short_click(void); +void button_do_long_click(void); +void button_do_hold_click(void); // 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(); +void button_do_press(void); +void button_do_release(void); +void button_do_toggle(void); - // queue up an input event for the button - static void queueInput(char input); - static uint32_t inputQueueSize(); +// queue up an input event for the button +void button_queue_input(char input); +uint32_t button_input_queue_size(void); #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; - // whether a long hold occurred - static bool m_holdClick; +#ifdef __cplusplus +} +#endif -#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/Colorset.cpp b/Helios/Colorset.cpp index c3d1d6a9..1d808825 100644 --- a/Helios/Colorset.cpp +++ b/Helios/Colorset.cpp @@ -5,207 +5,184 @@ #include // when no color is selected in the colorset the index is this -// then when you call getNext() for the first time it returns +// then when you call colorset_getNext() for the first time it returns // the 0th color in the colorset and after the index will be 0 #define INDEX_INVALID 255 -Colorset::Colorset() : - m_palette(), - m_numColors(0), - m_curIndex(INDEX_INVALID) +void colorset_init(colorset_t *set) { - init(); + memset((void *)set->m_palette, 0, sizeof(set->m_palette)); + set->m_numColors = 0; + set->m_curIndex = INDEX_INVALID; } -Colorset::Colorset(RGBColor c1, RGBColor c2, RGBColor c3, RGBColor c4, - RGBColor c5, RGBColor c6, RGBColor c7, RGBColor c8) : - Colorset() +void colorset_init_multi(colorset_t *set, rgb_color_t c1, rgb_color_t c2, rgb_color_t c3, + rgb_color_t c4, rgb_color_t c5, rgb_color_t c6, rgb_color_t c7, rgb_color_t c8) { - init(c1, c2, c3, c4, c5, c6, c7, c8); + colorset_init(set); + // would be nice if we could do this another way + if (!rgb_empty(&c1)) colorset_add_color(set, c1); + if (!rgb_empty(&c2)) colorset_add_color(set, c2); + if (!rgb_empty(&c3)) colorset_add_color(set, c3); + if (!rgb_empty(&c4)) colorset_add_color(set, c4); + if (!rgb_empty(&c5)) colorset_add_color(set, c5); + if (!rgb_empty(&c6)) colorset_add_color(set, c6); + if (!rgb_empty(&c7)) colorset_add_color(set, c7); + if (!rgb_empty(&c8)) colorset_add_color(set, c8); } -Colorset::Colorset(uint8_t numCols, const uint32_t *cols) : - Colorset() +void colorset_init_array(colorset_t *set, uint8_t numCols, const uint32_t *cols) { + colorset_init(set); if (numCols > NUM_COLOR_SLOTS) { numCols = NUM_COLOR_SLOTS; } - for (uint8_t i = 0; i < numCols; ++i) { - addColor(RGBColor(cols[i])); + uint8_t i; + for (i = 0; i < numCols; ++i) { + rgb_color_t col; + rgb_init_from_raw(&col, cols[i]); + colorset_add_color(set, col); } } -Colorset::Colorset(const Colorset &other) : - Colorset() -{ - // invoke = operator - *this = other; -} - -Colorset::~Colorset() +void colorset_copy(colorset_t *dest, const colorset_t *src) { - clear(); + memcpy(dest->m_palette, src->m_palette, sizeof(dest->m_palette)); + dest->m_numColors = src->m_numColors; + dest->m_curIndex = src->m_curIndex; } -bool Colorset::operator==(const Colorset &other) const +uint8_t colorset_equals(const colorset_t *a, const colorset_t *b) { // 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(); + return (a->m_numColors == b->m_numColors) && + (memcmp(a->m_palette, b->m_palette, a->m_numColors * sizeof(rgb_color_t)) == 0); } -bool Colorset::equals(const Colorset &set) const +void colorset_clear(colorset_t *set) { - return operator==(set); + memset((void *)set->m_palette, 0, sizeof(set->m_palette)); + set->m_numColors = 0; + colorset_reset_index(set); } -bool Colorset::equals(const Colorset *set) const -{ - if (!set) { - return false; - } - return operator==(*set); -} - -// crc the colorset -uint32_t Colorset::crc32() const +uint32_t colorset_crc32(const colorset_t *set) { uint32_t hash = 5381; - for (uint8_t i = 0; i < m_numColors; ++i) { - hash = ((hash << 5) + hash) + m_palette[i].raw(); + uint8_t i; + for (i = 0; i < set->m_numColors; ++i) { + hash = ((hash << 5) + hash) + rgb_raw(&set->m_palette[i]); } return hash; } -RGBColor Colorset::operator[](int index) const +rgb_color_t colorset_get_at_index(const colorset_t *set, int index) { - return get(index); + return colorset_get(set, index); } -// add a single color -bool Colorset::addColor(RGBColor col) +uint8_t colorset_add_color(colorset_t *set, rgb_color_t col) { - if (m_numColors >= NUM_COLOR_SLOTS) { - return false; + if (set->m_numColors >= NUM_COLOR_SLOTS) { + return 0; } // insert new color and increment number of colors - m_palette[m_numColors] = col; - m_numColors++; - return true; + set->m_palette[set->m_numColors] = col; + set->m_numColors++; + return 1; } -bool Colorset::addColorHSV(uint8_t hue, uint8_t sat, uint8_t val) +uint8_t colorset_add_color_hsv(colorset_t *set, uint8_t hue, uint8_t sat, uint8_t val) { - return addColor(HSVColor(hue, sat, val)); + hsv_color_t hsv; + rgb_color_t rgb; + hsv_init3(&hsv, hue, sat, val); + rgb_init_from_hsv(&rgb, &hsv); + return colorset_add_color(set, rgb); } -void Colorset::addColorWithValueStyle(Random &ctx, uint8_t hue, uint8_t sat, ValueStyle valStyle, uint8_t numColors, uint8_t colorPos) +void colorset_add_color_with_value_style(colorset_t *set, random_t *ctx, uint8_t hue, uint8_t sat, + enum colorset_value_style valStyle, uint8_t numColors, uint8_t colorPos) { if (numColors == 1) { - addColorHSV(hue, sat, ctx.next8(16, 255)); + colorset_add_color_hsv(set, hue, sat, random_next8(ctx, 16, 255)); return; } switch (valStyle) { default: case VAL_STYLE_RANDOM: - addColorHSV(hue, sat, 85 * ctx.next8(1, 4)); + colorset_add_color_hsv(set, hue, sat, 85 * random_next8(ctx, 1, 4)); break; case VAL_STYLE_LOW_FIRST_COLOR: - if (m_numColors == 0) { - addColorHSV(hue, sat, ctx.next8(0, 86)); + if (set->m_numColors == 0) { + colorset_add_color_hsv(set, hue, sat, random_next8(ctx, 0, 86)); } else { - addColorHSV(hue, sat, 85 * ctx.next8(1, 4)); + colorset_add_color_hsv(set, hue, sat, 85 * random_next8(ctx, 1, 4)); } break; case VAL_STYLE_HIGH_FIRST_COLOR: - if (m_numColors == 0) { - addColorHSV(hue, sat, 255); + if (set->m_numColors == 0) { + colorset_add_color_hsv(set, hue, sat, 255); } else { - addColorHSV(hue, sat, ctx.next8(0, 86)); + colorset_add_color_hsv(set, hue, sat, random_next8(ctx, 0, 86)); } break; case VAL_STYLE_ALTERNATING: - if (m_numColors % 2 == 0) { - addColorHSV(hue, sat, 255); + if (set->m_numColors % 2 == 0) { + colorset_add_color_hsv(set, hue, sat, 255); } else { - addColorHSV(hue, sat, 85); + colorset_add_color_hsv(set, hue, sat, 85); } break; case VAL_STYLE_ASCENDING: - addColorHSV(hue, sat, (colorPos + 1) * (255 / numColors)); + colorset_add_color_hsv(set, hue, sat, (colorPos + 1) * (255 / numColors)); break; case VAL_STYLE_DESCENDING: - addColorHSV(hue, sat, 255 - (colorPos * (255 / numColors))); + colorset_add_color_hsv(set, hue, sat, 255 - (colorPos * (255 / numColors))); break; case VAL_STYLE_CONSTANT: - addColorHSV(hue, sat, 255); + colorset_add_color_hsv(set, hue, sat, 255); } } -void Colorset::removeColor(uint8_t index) +void colorset_remove_color(colorset_t *set, uint8_t index) { - if (index >= m_numColors) { + if (index >= set->m_numColors) { return; } - for (uint8_t i = index; i < (m_numColors - 1); ++i) { - m_palette[i] = m_palette[i + 1]; + uint8_t i; + for (i = index; i < (set->m_numColors - 1); ++i) { + set->m_palette[i] = set->m_palette[i + 1]; } - m_palette[--m_numColors].clear(); + rgb_clear(&set->m_palette[--set->m_numColors]); } -void Colorset::randomizeColors(Random &ctx, uint8_t numColors, ColorMode mode) +void colorset_randomize_colors(colorset_t *set, random_t *ctx, uint8_t numColors, enum colorset_color_mode mode) { // if they specify randomly pick the color mode then roll it if (mode >= COLOR_MODE_RANDOMLY_PICK) { - mode = (ColorMode)(ctx.next8() % COLOR_MODE_COUNT); + mode = (enum colorset_color_mode)(random_next8(ctx, 0, 255) % COLOR_MODE_COUNT); } - clear(); + colorset_clear(set); if (!numColors) { - numColors = ctx.next8(mode == COLOR_MODE_MONOCHROMATIC ? 2 : 1, 9); + numColors = random_next8(ctx, mode == COLOR_MODE_MONOCHROMATIC ? 2 : 1, 9); } - uint8_t randomizedHue = ctx.next8(); + uint8_t randomizedHue = random_next8(ctx, 0, 255); uint8_t colorGap = 0; if (mode == COLOR_MODE_COLOR_THEORY && numColors > 1) { - colorGap = ctx.next8(16, 256 / (numColors - 1)); + colorGap = random_next8(ctx, 16, 256 / (numColors - 1)); } - ValueStyle valStyle = (ValueStyle)ctx.next8(0, VAL_STYLE_COUNT); + enum colorset_value_style valStyle = (enum colorset_value_style)random_next8(ctx, 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)); + doubleStyle = random_next8(ctx, 0, 1); } if (numColors <= 4) { - doubleStyle = (ctx.next8(0, 2)); + doubleStyle = random_next8(ctx, 0, 2); } - for (uint8_t i = 0; i < numColors; i++) { + uint8_t i; + for (i = 0; i < numColors; i++) { uint8_t hueToUse; uint8_t valueToUse = 255; if (mode == COLOR_MODE_COLOR_THEORY) { @@ -216,150 +193,174 @@ void Colorset::randomizeColors(Random &ctx, uint8_t numColors, ColorMode mode) } else { // EVENLY_SPACED hueToUse = (randomizedHue + (256 / numColors) * i); } - addColorWithValueStyle(ctx, hueToUse, valueToUse, valStyle, numColors, i); + colorset_add_color_with_value_style(set, 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); + colorset_add_color_with_value_style(set, ctx, hueToUse, valueToUse, valStyle, numColors, i); } } } -void Colorset::adjustBrightness(uint8_t fadeby) +void colorset_adjust_brightness(colorset_t *set, uint8_t fadeby) { - for (uint8_t i = 0; i < m_numColors; ++i) { - m_palette[i].adjustBrightness(fadeby); + uint8_t i; + for (i = 0; i < set->m_numColors; ++i) { + rgb_adjust_brightness(&set->m_palette[i], fadeby); } } // get a color from the colorset -RGBColor Colorset::get(uint8_t index) const +rgb_color_t colorset_get(const colorset_t *set, uint8_t index) { - if (index >= m_numColors) { - return RGBColor(0, 0, 0); + rgb_color_t result; + if (index >= set->m_numColors) { + rgb_init3(&result, 0, 0, 0); + return result; } - return m_palette[index]; + return set->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) +void colorset_set(colorset_t *set, uint8_t index, rgb_color_t 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); + if (index >= set->m_numColors) { + if (!colorset_add_color(set, col)) { + // ERROR_LOGF("Failed to add new color at index %u", index); } return; } - m_palette[index] = col; + set->m_palette[index] = col; } -// skip some amount of colors -void Colorset::skip(int32_t amount) +void colorset_skip(colorset_t *set, int32_t amount) { - if (!m_numColors) { + if (!set->m_numColors) { return; } // if the colorset hasn't started yet - if (m_curIndex == INDEX_INVALID) { - m_curIndex = 0; + if (set->m_curIndex == INDEX_INVALID) { + set->m_curIndex = 0; } // first modulate the amount to skip to be within +/- the number of colors - amount %= (int32_t)m_numColors; + amount %= (int32_t)set->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 + set->m_curIndex = ((int32_t)set->m_curIndex + (int32_t)amount) % (int32_t)set->m_numColors; + if (set->m_curIndex > set->m_numColors) { // must have wrapped // simply wrap it back - m_curIndex += m_numColors; + set->m_curIndex += set->m_numColors; } } -RGBColor Colorset::cur() +rgb_color_t colorset_cur(const colorset_t *set) { - if (m_curIndex >= m_numColors) { - return RGBColor(0, 0, 0); + rgb_color_t result; + if (set->m_curIndex >= set->m_numColors) { + rgb_init3(&result, 0, 0, 0); + return result; } - return m_palette[m_curIndex]; + return set->m_palette[set->m_curIndex]; } -void Colorset::setCurIndex(uint8_t index) +void colorset_set_cur_index(colorset_t *set, uint8_t index) { - if (!m_numColors) { + if (!set->m_numColors) { return; } - if (index > (m_numColors - 1)) { + if (index > (set->m_numColors - 1)) { return; } - m_curIndex = index; + set->m_curIndex = index; +} + +void colorset_reset_index(colorset_t *set) +{ + set->m_curIndex = INDEX_INVALID; } -void Colorset::resetIndex() +uint8_t colorset_cur_index(const colorset_t *set) { - m_curIndex = INDEX_INVALID; + return set->m_curIndex; } -RGBColor Colorset::getPrev() +rgb_color_t colorset_get_prev(colorset_t *set) { - if (!m_numColors) { - return RGB_OFF; + rgb_color_t result; + if (!set->m_numColors) { + rgb_init_from_raw(&result, RGB_OFF); + return result; } // handle wrapping at 0 - if (m_curIndex == 0 || m_curIndex == INDEX_INVALID) { - m_curIndex = numColors() - 1; + if (set->m_curIndex == 0 || set->m_curIndex == INDEX_INVALID) { + set->m_curIndex = colorset_num_colors(set) - 1; } else { - m_curIndex--; + set->m_curIndex--; } // return the color - return m_palette[m_curIndex]; + return set->m_palette[set->m_curIndex]; } -RGBColor Colorset::getNext() +rgb_color_t colorset_get_next(colorset_t *set) { - if (!m_numColors) { - return RGB_OFF; + rgb_color_t result; + if (!set->m_numColors) { + rgb_init_from_raw(&result, RGB_OFF); + return result; } // iterate current index, let it wrap at max uint8 - m_curIndex++; + set->m_curIndex++; // then modulate the result within max colors - m_curIndex %= numColors(); + set->m_curIndex %= colorset_num_colors(set); // return the color - return m_palette[m_curIndex]; + return set->m_palette[set->m_curIndex]; } -// peek at the next color but don't iterate -RGBColor Colorset::peek(int32_t offset) const +rgb_color_t colorset_peek(const colorset_t *set, int32_t offset) { - if (!m_numColors) { - return RGB_OFF; + rgb_color_t result; + if (!set->m_numColors) { + rgb_init_from_raw(&result, RGB_OFF); + return result; } uint8_t nextIndex = 0; // get index of the next color if (offset >= 0) { - nextIndex = (m_curIndex + offset) % numColors(); + nextIndex = (set->m_curIndex + offset) % colorset_num_colors(set); } else { - if (offset < -1 * (int32_t)(numColors())) { - return RGB_OFF; + if (offset < -1 * (int32_t)(colorset_num_colors(set))) { + rgb_init_from_raw(&result, RGB_OFF); + return result; } - nextIndex = ((m_curIndex + numColors()) + (int)offset) % numColors(); + nextIndex = ((set->m_curIndex + colorset_num_colors(set)) + (int)offset) % colorset_num_colors(set); } // return the color - return m_palette[nextIndex]; + return set->m_palette[nextIndex]; } -bool Colorset::onStart() const +rgb_color_t colorset_peek_next(const colorset_t *set) { - return (m_curIndex == 0); + return colorset_peek(set, 1); } -bool Colorset::onEnd() const +uint8_t colorset_num_colors(const colorset_t *set) { - if (!m_numColors) { - return false; + return set->m_numColors; +} + +uint8_t colorset_on_start(const colorset_t *set) +{ + return (set->m_curIndex == 0); +} + +uint8_t colorset_on_end(const colorset_t *set) +{ + if (!set->m_numColors) { + return 0; } - return (m_curIndex == m_numColors - 1); + return (set->m_curIndex == set->m_numColors - 1); } + diff --git a/Helios/Colorset.h b/Helios/Colorset.h index 4c6aeafe..47642a5b 100644 --- a/Helios/Colorset.h +++ b/Helios/Colorset.h @@ -1,143 +1,144 @@ #ifndef COLORSET_H #define COLORSET_H +#ifdef __cplusplus +extern "C" { +#endif + #include "Colortypes.h" #include "HeliosConfig.h" -class Random; +// Forward declaration +typedef struct random_t random_t; +typedef struct colorset_t colorset_t; + +enum colorset_value_style +{ + // 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, + // Alternate 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 +}; -class Colorset +enum colorset_color_mode +{ + // randomize with color theory + COLOR_MODE_COLOR_THEORY, + // randomize a monochromatic set + COLOR_MODE_MONOCHROMATIC, + // randomize an evenly spaced hue set + COLOR_MODE_EVENLY_SPACED, + + // total different randomize modes above + COLOR_MODE_COUNT, + + // EXTRA OPTION: randomly pick one of the other 3 options + COLOR_MODE_RANDOMLY_PICK = COLOR_MODE_COUNT, +}; + +struct colorset_t { -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; - - // crc the colorset - uint32_t crc32() 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); - - // various modes of randomization types to use with randomizeColors - enum ColorMode { - // randomize with color theory - COLOR_MODE_COLOR_THEORY, - // randomize a nonochromatic set - COLOR_MODE_MONOCHROMATIC, - // randomize an evenly spaced hue set - COLOR_MODE_EVENLY_SPACED, - - // total different randomize modes above - COLOR_MODE_COUNT, - - // EXTRA OPTION: randomly pick one of the other 3 options - COLOR_MODE_RANDOMLY_PICK = COLOR_MODE_COUNT, - }; - // function to randomize the colors with various different modes of randomization - void randomizeColors(Random &ctx, uint8_t numColors, ColorMode color_mode); - - // 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]; + rgb_color_t 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 + // the current index, starts at 255 so that + // the very first call to colorset_getNext will iterate to 0 uint8_t m_curIndex; }; +// Empty colorset +void colorset_init(colorset_t *set); + +// Initialize with up to 8 colors +void colorset_init_multi(colorset_t *set, rgb_color_t c1, rgb_color_t c2, rgb_color_t c3, + rgb_color_t c4, rgb_color_t c5, rgb_color_t c6, rgb_color_t c7, rgb_color_t c8); + +// Initialize from array of colors +void colorset_init_array(colorset_t *set, uint8_t numCols, const uint32_t *cols); + +// Copy colorset +void colorset_copy(colorset_t *dest, const colorset_t *src); + +// Equality operators +uint8_t colorset_equals(const colorset_t *a, const colorset_t *b); + +// Clear the colorset +void colorset_clear(colorset_t *set); + +// CRC the colorset +uint32_t colorset_crc32(const colorset_t *set); + +// Index operator to access color index +rgb_color_t colorset_get_at_index(const colorset_t *set, int index); + +// Add a single color +uint8_t colorset_add_color(colorset_t *set, rgb_color_t col); +uint8_t colorset_add_color_hsv(colorset_t *set, uint8_t hue, uint8_t sat, uint8_t val); +void colorset_add_color_with_value_style(colorset_t *set, random_t *ctx, uint8_t hue, uint8_t sat, + enum colorset_value_style valStyle, uint8_t numColors, uint8_t colorPos); +void colorset_remove_color(colorset_t *set, uint8_t index); + +// Function to randomize the colors with various different modes of randomization +void colorset_randomize_colors(colorset_t *set, random_t *ctx, uint8_t numColors, enum colorset_color_mode color_mode); + +// Fade all of the colors in the set +void colorset_adjust_brightness(colorset_t *set, uint8_t fadeby); + +// Get a color from the colorset +rgb_color_t colorset_get(const colorset_t *set, uint8_t 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(colorset_t *set, uint8_t index, rgb_color_t col); + +// Skip some amount of colors +void colorset_skip(colorset_t *set, int32_t amount); + +// Get current color in cycle +rgb_color_t colorset_cur(const colorset_t *set); + +// Set the current index of the colorset +void colorset_set_cur_index(colorset_t *set, uint8_t index); +void colorset_reset_index(colorset_t *set); + +// The current index +uint8_t colorset_cur_index(const colorset_t *set); + +// Get the prev color in cycle +rgb_color_t colorset_get_prev(colorset_t *set); + +// Get the next color in cycle +rgb_color_t colorset_get_next(colorset_t *set); + +// Peek at the color indexes from current but don't iterate +rgb_color_t colorset_peek(const colorset_t *set, int32_t offset); + +// Better wording for peek 1 ahead +rgb_color_t colorset_peek_next(const colorset_t *set); + +// The number of colors in the palette +uint8_t colorset_num_colors(const colorset_t *set); + +// Whether the colorset is currently on the first color or last color +uint8_t colorset_on_start(const colorset_t *set); +uint8_t colorset_on_end(const colorset_t *set); + +#ifdef __cplusplus +} +#endif + #endif diff --git a/Helios/Colortypes.cpp b/Helios/Colortypes.cpp index 8039c446..7e8adf11 100644 --- a/Helios/Colortypes.cpp +++ b/Helios/Colortypes.cpp @@ -2,189 +2,210 @@ #if ALTERNATIVE_HSV_RGB == 1 // global hsv to rgb algorithm selector -hsv_to_rgb_algorithm g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; +enum hsv_to_rgb_algorithm g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; #endif -HSVColor::HSVColor() : - hue(0), - sat(0), - val(0) +// ========== HSVColor functions ========== + +void hsv_init(hsv_color_t *hsv) { + hsv->hue = 0; + hsv->sat = 0; + hsv->val = 0; } -HSVColor::HSVColor(uint8_t hue, uint8_t sat, uint8_t val) : - hue(hue), sat(sat), val(val) +void hsv_init3(hsv_color_t *hsv, uint8_t hue, uint8_t sat, uint8_t val) { + hsv->hue = hue; + hsv->sat = sat; + hsv->val = val; } -HSVColor::HSVColor(uint32_t dwVal) : - HSVColor() +void hsv_init_from_raw(hsv_color_t *hsv, uint32_t dwVal) { - *this = dwVal; + hsv_init(hsv); + hsv_assign_from_raw(hsv, dwVal); } -// assignment from uint32_t -HSVColor &HSVColor::operator=(const uint32_t &rhs) +void hsv_init_from_rgb(hsv_color_t *hsv, const rgb_color_t *rgb) { - hue = ((rhs >> 16) & 0xFF); - sat = ((rhs >> 8) & 0xFF); - val = (rhs & 0xFF); - return *this; + hsv_assign_from_rgb(hsv, rgb); } -// construction/assignment from RGB -HSVColor::HSVColor(const RGBColor &rhs) +void hsv_copy(hsv_color_t *dest, const hsv_color_t *src) { - *this = rhs; + dest->hue = src->hue; + dest->sat = src->sat; + dest->val = src->val; } -HSVColor &HSVColor::operator=(const RGBColor &rhs) +void hsv_assign_from_raw(hsv_color_t *hsv, uint32_t rhs) +{ + hsv->hue = ((rhs >> 16) & 0xFF); + hsv->sat = ((rhs >> 8) & 0xFF); + hsv->val = (rhs & 0xFF); +} + +void hsv_assign_from_rgb(hsv_color_t *hsv, const rgb_color_t *rhs) { // always use generic - *this = rgb_to_hsv_generic(rhs); - return *this; + hsv_color_t temp = rgb_to_hsv_generic(rhs); + hsv_copy(hsv, &temp); } -bool HSVColor::operator==(const HSVColor &other) const +uint8_t hsv_equals(const hsv_color_t *a, const hsv_color_t *b) { - return (other.raw() == raw()); + return (hsv_raw(b) == hsv_raw(a)); } -bool HSVColor::operator!=(const HSVColor &other) const +uint8_t hsv_empty(const hsv_color_t *hsv) { - return (other.raw() != raw()); + return !hsv->hue && !hsv->sat && !hsv->val; } -bool HSVColor::empty() const +void hsv_clear(hsv_color_t *hsv) { - return !hue && !sat && !val; + hsv->hue = 0; + hsv->sat = 0; + hsv->val = 0; } -void HSVColor::clear() +uint32_t hsv_raw(const hsv_color_t *hsv) { - hue = 0; - sat = 0; - val = 0; + return ((uint32_t)hsv->hue << 16) | ((uint32_t)hsv->sat << 8) | (uint32_t)hsv->val; } -// ========== -// RGBColor +// ========== RGBColor functions ========== -RGBColor::RGBColor() : - red(0), - green(0), - blue(0) +void rgb_init(rgb_color_t *rgb) { + rgb->red = 0; + rgb->green = 0; + rgb->blue = 0; } -RGBColor::RGBColor(uint8_t red, uint8_t green, uint8_t blue) : - red(red), green(green), blue(blue) +void rgb_init3(rgb_color_t *rgb, uint8_t red, uint8_t green, uint8_t blue) { + rgb->red = red; + rgb->green = green; + rgb->blue = blue; } -RGBColor::RGBColor(uint32_t dwVal) : - RGBColor() +void rgb_init_from_raw(rgb_color_t *rgb, uint32_t dwVal) { - *this = dwVal; + rgb_init(rgb); + rgb_assign_from_raw(rgb, dwVal); } -// assignment from uint32_t -RGBColor &RGBColor::operator=(const uint32_t &rhs) +void rgb_init_from_hsv(rgb_color_t *rgb, const hsv_color_t *hsv) { - red = ((rhs >> 16) & 0xFF); - green = ((rhs >> 8) & 0xFF); - blue = (rhs & 0xFF); - return *this; + rgb_assign_from_hsv(rgb, hsv); } -RGBColor::RGBColor(const HSVColor &rhs) +void rgb_copy(rgb_color_t *dest, const rgb_color_t *src) { - *this = rhs; + dest->red = src->red; + dest->green = src->green; + dest->blue = src->blue; } -RGBColor &RGBColor::operator=(const HSVColor &rhs) +void rgb_assign_from_raw(rgb_color_t *rgb, uint32_t rhs) +{ + rgb->red = ((rhs >> 16) & 0xFF); + rgb->green = ((rhs >> 8) & 0xFF); + rgb->blue = (rhs & 0xFF); +} + +void rgb_assign_from_hsv(rgb_color_t *rgb, const hsv_color_t *rhs) { #if ALTERNATIVE_HSV_RGB == 1 + rgb_color_t temp; switch (g_hsv_rgb_alg) { case HSV_TO_RGB_RAINBOW: - *this = hsv_to_rgb_rainbow(rhs); + temp = hsv_to_rgb_rainbow(rhs); break; case HSV_TO_RGB_GENERIC: - *this = hsv_to_rgb_generic(rhs); + temp = hsv_to_rgb_generic(rhs); break; } + rgb_copy(rgb, &temp); #else - *this = hsv_to_rgb_generic(rhs); + rgb_color_t temp = hsv_to_rgb_generic(rhs); + rgb_copy(rgb, &temp); #endif - return *this; } -bool RGBColor::operator==(const RGBColor &other) const +uint8_t rgb_equals(const rgb_color_t *a, const rgb_color_t *b) { - return (other.raw() == raw()); + return (rgb_raw(b) == rgb_raw(a)); } -bool RGBColor::operator!=(const RGBColor &other) const +uint8_t rgb_empty(const rgb_color_t *rgb) { - return (other.raw() != raw()); + return !rgb->red && !rgb->green && !rgb->blue; } -bool RGBColor::empty() const +void rgb_clear(rgb_color_t *rgb) { - return !red && !green && !blue; + rgb->red = 0; + rgb->green = 0; + rgb->blue = 0; } -void RGBColor::clear() +// scale down the brightness of a color by some fade amount +void rgb_adjust_brightness(rgb_color_t *rgb, uint8_t fadeBy) { - red = 0; - green = 0; - blue = 0; + rgb->red = (((int)rgb->red) * (int)(256 - fadeBy)) >> 8; + rgb->green = (((int)rgb->green) * (int)(256 - fadeBy)) >> 8; + rgb->blue = (((int)rgb->blue) * (int)(256 - fadeBy)) >> 8; } -// scale down the brightness of a color by some fade amount -RGBColor RGBColor::adjustBrightness(uint8_t fadeBy) +uint32_t rgb_raw(const rgb_color_t *rgb) { - red = (((int)red) * (int)(256 - fadeBy)) >> 8; - green = (((int)green) * (int)(256 - fadeBy)) >> 8; - blue = (((int)blue) * (int)(256 - fadeBy)) >> 8; - return *this; + return ((uint32_t)rgb->red << 16) | ((uint32_t)rgb->green << 8) | (uint32_t)rgb->blue; } #ifdef HELIOS_CLI -// Adjust brightness to ensure visibility on screens, without floating-point arithmetic and without using max function -RGBColor RGBColor::bringUpBrightness(uint8_t min_brightness) { - // Adjust each color component using FSCALE8 and ensure it doesn't drop below MIN_BRIGHTNESS - HSVColor col = *this; +// Adjust brightness to ensure visibility on screens, without floating-point arithmetic +void rgb_bring_up_brightness(rgb_color_t *rgb, uint8_t min_brightness) +{ + hsv_color_t col; + hsv_init_from_rgb(&col, rgb); if (col.val == 0) { - return col; + rgb_assign_from_hsv(rgb, &col); + return; } if (col.val < min_brightness) { col.val = min_brightness; } - return col; + rgb_assign_from_hsv(rgb, &col); } + // scale a uint8 by a float value, don't use this on embedded! #define FSCALE8(x, scale) (uint8_t)(((float)x * scale) > 255 ? 255 : ((float)x * scale)) + // return a scaled up the brightness version of the current color -RGBColor RGBColor::scaleBrightness(float scale) +void rgb_scale_brightness(rgb_color_t *rgb, float scale) { - // scale each color up by the given amount and return a new color - return RGBColor(FSCALE8(red, scale), FSCALE8(green, scale), FSCALE8(blue, scale)); + rgb->red = FSCALE8(rgb->red, scale); + rgb->green = FSCALE8(rgb->green, scale); + rgb->blue = FSCALE8(rgb->blue, scale); } #endif + // ======================================================== -// Below are various functions for converting hsv <-> rgb +// 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 +// Stolen from FastLED hsv to rgb full rainbow 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) +rgb_color_t hsv_to_rgb_rainbow(const hsv_color_t *rhs) { - RGBColor col; + rgb_color_t 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 @@ -204,10 +225,9 @@ RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs) // 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 hue = rhs->hue; + uint8_t sat = rhs->sat; + uint8_t val = rhs->val; uint8_t offset = hue & 0x1F; // 0..31 @@ -221,16 +241,16 @@ RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs) // 0XX if (!(hue & 0x40)) { // 00X - //section 0-1 + // section 0-1 if (!(hue & 0x20)) { // 000 - //case 0: // R -> O + // case 0: R -> O r = 255 - third; g = third; b = 0; } else { // 001 - //case 1: // O -> Y + // case 1: O -> Y if (Y1) { r = 171; g = 85 + third; @@ -238,20 +258,20 @@ RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs) } if (Y2) { r = 170 + third; - //uint8_t twothirds = (third << 1); + // uint8_t twothirds = (third << 1); uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); // max=170 g = 85 + twothirds; b = 0; } } } else { - //01X + // 01X // section 2-3 if (!(hue & 0x20)) { // 010 - //case 2: // Y -> G + // case 2: Y -> G if (Y1) { - //uint8_t twothirds = (third << 1); + // uint8_t twothirds = (third << 1); uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); // max=170 r = 171 - twothirds; g = 170 + third; @@ -264,7 +284,7 @@ RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs) } } else { // 011 - // case 3: // G -> A + // case 3: G -> A r = 0; g = 255 - third; b = third; @@ -277,15 +297,15 @@ RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs) // 10X if (!(hue & 0x20)) { // 100 - //case 4: // A -> B + // case 4: A -> B r = 0; - //uint8_t twothirds = (third << 1); + // uint8_t twothirds = (third << 1); uint8_t twothirds = SCALE8(offset8, ((256 * 2) / 3)); // max=170 - g = 171 - twothirds; //170? + g = 171 - twothirds; // 170? b = 85 + twothirds; } else { // 101 - //case 5: // B -> P + // case 5: B -> P r = third; g = 0; b = 255 - third; @@ -293,13 +313,13 @@ RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs) } else { if (!(hue & 0x20)) { // 110 - //case 6: // P -- K + // case 6: P -- K r = 85 + third; g = 0; b = 171 - third; } else { // 111 - //case 7: // K -> R + // case 7: K -> R r = 170 + third; g = 0; b = 85 - third; @@ -357,56 +377,56 @@ RGBColor hsv_to_rgb_rainbow(const HSVColor &rhs) #endif // generic hsv to rgb conversion nothing special -RGBColor hsv_to_rgb_generic(const HSVColor &rhs) +rgb_color_t hsv_to_rgb_generic(const hsv_color_t *rhs) { unsigned char region, remainder, p, q, t; - RGBColor col; + rgb_color_t col; - if (rhs.sat == 0) { - col.red = rhs.val; - col.green = rhs.val; - col.blue = rhs.val; + 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); + 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); + 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; + col.red = rhs->val; col.green = t; col.blue = p; break; case 1: - col.red = q; col.green = rhs.val; col.blue = p; + col.red = q; col.green = rhs->val; col.blue = p; break; case 2: - col.red = p; col.green = rhs.val; col.blue = t; + col.red = p; col.green = rhs->val; col.blue = t; break; case 3: - col.red = p; col.green = q; col.blue = rhs.val; + col.red = p; col.green = q; col.blue = rhs->val; break; case 4: - col.red = t; col.green = p; col.blue = rhs.val; + col.red = t; col.green = p; col.blue = rhs->val; break; default: - col.red = rhs.val; col.green = p; col.blue = q; + 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) +hsv_color_t rgb_to_hsv_generic(const rgb_color_t *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; + 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); + hsv_color_t hsv; hsv.val = rgbMax; if (hsv.val == 0) { @@ -421,12 +441,13 @@ HSVColor rgb_to_hsv_generic(const RGBColor &rhs) 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); + 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); + hsv.hue = 171 + 43 * (rhs->red - rhs->green) / (rgbMax - rgbMin); } return hsv; } + diff --git a/Helios/Colortypes.h b/Helios/Colortypes.h index 2a579de7..b1f0616b 100644 --- a/Helios/Colortypes.h +++ b/Helios/Colortypes.h @@ -6,8 +6,12 @@ #include "HeliosConfig.h" #include "ColorConstants.h" +#ifdef __cplusplus +extern "C" { +#endif + #if ALTERNATIVE_HSV_RGB == 1 -enum hsv_to_rgb_algorithm : uint8_t +enum hsv_to_rgb_algorithm { HSV_TO_RGB_GENERIC, HSV_TO_RGB_RAINBOW @@ -15,94 +19,75 @@ enum hsv_to_rgb_algorithm : uint8_t // global hsv to rgb algorithm selector, switch this to control // all hsv to rgb conversions -extern hsv_to_rgb_algorithm g_hsv_rgb_alg; +extern enum hsv_to_rgb_algorithm g_hsv_rgb_alg; #endif -class ByteStream; -class RGBColor; +// Forward declarations +typedef struct hsv_color_t hsv_color_t; +typedef struct rgb_color_t rgb_color_t; -class HSVColor +struct hsv_color_t { -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 +struct rgb_color_t { -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(); - - // scale down the brightness of a color by some fade amount - RGBColor adjustBrightness(uint8_t fadeBy); - -#ifdef HELIOS_CLI - // return a scale brightness version of the current color, this uses - // a float value to scale the r/g/b values of the color - // ex: 0.0 = black - // 0.5 = half as bright - // 1.0 = no change - // 1.5 = 50% brighter - // 2.0 = twice as bright - // 255.0 = white - RGBColor scaleBrightness(float scale); - // bring up the brightness of a color to a minimum level - RGBColor bringUpBrightness(uint8_t min_brightness); -#endif - - 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 +// HSVColor functions +void hsv_init(hsv_color_t *hsv); +void hsv_init3(hsv_color_t *hsv, uint8_t hue, uint8_t sat, uint8_t val); +void hsv_init_from_raw(hsv_color_t *hsv, uint32_t dwVal); +void hsv_init_from_rgb(hsv_color_t *hsv, const rgb_color_t *rgb); +void hsv_copy(hsv_color_t *dest, const hsv_color_t *src); +void hsv_assign_from_raw(hsv_color_t *hsv, uint32_t rhs); +void hsv_assign_from_rgb(hsv_color_t *hsv, const rgb_color_t *rhs); +uint8_t hsv_equals(const hsv_color_t *a, const hsv_color_t *b); +uint8_t hsv_empty(const hsv_color_t *hsv); +void hsv_clear(hsv_color_t *hsv); +uint32_t hsv_raw(const hsv_color_t *hsv); + +// RGBColor functions +void rgb_init(rgb_color_t *rgb); +void rgb_init3(rgb_color_t *rgb, uint8_t red, uint8_t green, uint8_t blue); +void rgb_init_from_raw(rgb_color_t *rgb, uint32_t dwVal); +void rgb_init_from_hsv(rgb_color_t *rgb, const hsv_color_t *hsv); +void rgb_copy(rgb_color_t *dest, const rgb_color_t *src); +void rgb_assign_from_raw(rgb_color_t *rgb, uint32_t rhs); +void rgb_assign_from_hsv(rgb_color_t *rgb, const hsv_color_t *rhs); +uint8_t rgb_equals(const rgb_color_t *a, const rgb_color_t *b); +uint8_t rgb_empty(const rgb_color_t *rgb); +void rgb_clear(rgb_color_t *rgb); +void rgb_adjust_brightness(rgb_color_t *rgb, uint8_t fadeBy); +uint32_t rgb_raw(const rgb_color_t *rgb); + +#ifdef HELIOS_CLI +// Return a scaled brightness version of the current color +// ex: 0.0 = black, 0.5 = half brightness, 1.0 = no change, +// 1.5 = 50% brighter, 2.0 = twice as bright, 255.0 = white +void rgb_scale_brightness(rgb_color_t *rgb, float scale); +// Bring up the brightness of a color to a minimum level +void rgb_bring_up_brightness(rgb_color_t *rgb, uint8_t min_brightness); +#endif + +// Conversion functions +// Stolen from FastLED hsv to rgb full rainbow 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); - +rgb_color_t hsv_to_rgb_rainbow(const hsv_color_t *rhs); +// Generic hsv to rgb conversion nothing special +rgb_color_t hsv_to_rgb_generic(const hsv_color_t *rhs); // Convert rgb to hsv with generic fast method -HSVColor rgb_to_hsv_generic(const RGBColor &rhs); +hsv_color_t rgb_to_hsv_generic(const rgb_color_t *rhs); + +#ifdef __cplusplus +} +#endif #endif diff --git a/Helios/Helios.cpp b/Helios/Helios.cpp index c0dcc84b..4d38b798 100644 --- a/Helios/Helios.cpp +++ b/Helios/Helios.cpp @@ -1,662 +1,690 @@ -#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 - -// some internal macros that shouldn't change -// The number of menus in hue/sat/val selection -#define NUM_COLORS_PER_GROUP 4 -// the number of color groups in the color selection menu -#define NUM_COLOR_GROUPS 4 -// the number of menus in group selection -#define NUM_MENUS_GROUP 8 - -Helios::State Helios::cur_state; -Helios::Flags Helios::global_flags; -uint8_t Helios::menu_selection; -uint8_t Helios::cur_mode; -uint8_t Helios::selected_base_group; -uint8_t Helios::num_colors_selected; -Pattern Helios::pat; -bool Helios::keepgoing; -uint32_t Helios::last_mode_switch_time; -Colorset Helios::new_colorset; - -#ifdef HELIOS_CLI -bool Helios::sleeping; -#endif - -volatile char helios_version[] = HELIOS_VERSION_STR; - -bool Helios::init() -{ - // first initialize all the components of helios - if (!init_components()) { - return false; - } - // then initialize the hardware for embedded helios -#ifdef HELIOS_EMBEDDED - // Set PB0, PB1, PB4 as output - DDRB |= (1 << DDB0) | (1 << DDB1) | (1 << DDB4); - // Timer0 Configuration for PWM - TCCR0A = (1 << WGM01) | (1 << WGM00) | (1 << COM0A1) | (1 << COM0B1); - // No prescaler - TCCR0B = (1 << CS00); - // Timer1 for PWM on PB4, Fast PWM, Non-inverting, No prescaler - TCCR1 = (1 << PWM1A) | (1 << COM1A1) | (1 << CS10); - // Enable PWM on OC1B - GTCCR = (1 << PWM1B) | (1 << COM1B1); - // Enable Timer0 overflow interrupt - TIMSK |= (1 << TOIE0); - // Enable interrupts - sei(); -#endif - return true; -} - -bool Helios::init_components() -{ - // initialize various components of Helios - if (!Time::init()) { - return false; - } - if (!Led::init()) { - return false; - } - if (!Storage::init()) { - return false; - } - if (!Button::init()) { - return false; - } - // initialize global variables - cur_state = STATE_MODES; - menu_selection = 0; - cur_mode = 0; - num_colors_selected = 0; - selected_base_group = 0; - keepgoing = true; - last_mode_switch_time = 0; -#ifdef HELIOS_CLI - sleeping = false; -#endif - load_global_flags(); - load_cur_mode(); - return true; -} - -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(); - - // Update the Leds once per frame - Led::update(); - - // 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 - // clear the led colors - Led::clear(); - // Set all pins to input - DDRB = 0x00; - // Disable pull-ups on all pins - PORTB = 0x00; - // 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 - - // Set PB0, PB1, PB4 as output - DDRB |= (1 << DDB0) | (1 << DDB1) | (1 << DDB4); - // wakeup here, re-init - init_components(); -#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 - // if the button was held down then they are entering off-menus - // but if we re-initialize the button it will clear this state - bool pressed = Button::isPressed(); - // re-initialize some stuff - Time::init(); - Button::init(); - // so just re-press it - if (pressed) { - Button::doPress(); - } - cur_state = STATE_MODES; - // turn off the sleeping flag that only CLI has - sleeping = false; -#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(); - // Update the last mode switch time when loading a mode - last_mode_switch_time = Time::getCurtime(); -} - -void Helios::save_cur_mode() -{ - Storage::write_pattern(cur_mode, pat); -} - -void Helios::load_global_flags() -{ - // read the global flags from index 0 config - global_flags = (Flags)Storage::read_global_flags(); - cur_mode = Storage::read_current_mode(); -} - -void Helios::save_global_flags() -{ - Storage::write_global_flags(global_flags); - Storage::write_current_mode(cur_mode); -} - -void Helios::set_mode_index(uint8_t mode_index) -{ - cur_mode = (uint8_t)mode_index % NUM_MODE_SLOTS; - // now load current mode again - load_cur_mode(); -} - -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_GROUP_SELECTION: - case STATE_COLOR_VARIANT_SELECTION: - handle_state_color_selection(); - break; - case STATE_PATTERN_SELECT: - handle_state_pat_select(); - break; - case STATE_TOGGLE_LOCK: - handle_state_toggle_flag(FLAG_LOCKED); - break; - case STATE_SET_DEFAULTS: - handle_state_set_defaults(); - break; -#ifdef HELIOS_CLI - case STATE_SLEEP: - // simulate sleep in helios CLI - if (Button::onPress() || Button::onShortClick() || Button::onLongClick()) { - wakeup(); - } - break; -#endif - } -} - -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()) { - enter_sleep(); - 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); - - // flash red briefly when locked and short clicked - if (has_flag(FLAG_LOCKED) && holdDur < SHORT_CLICK_THRESHOLD) { - Led::set(RGB_RED_BRI_LOW); - } - // 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 - } - } else { - if (has_flag(FLAG_LOCKED)) { - switch (magnitude) { - default: - case 0: Led::clear(); break; - case TIME_TILL_GLOW_LOCK_UNLOCK: Led::set(RGB_RED_BRI_LOW); break; // Exit - } - } 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 - } - } - } - } - // if this isn't a release tick there's nothing more to do - if (Button::onRelease()) { - // Resets the menu selection before entering new state - menu_selection = 0; - 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 TIME_TILL_GLOW_LOCK_UNLOCK: // 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 - 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_GROUP_SELECTION; - // reset the menu selection and colors selected - menu_selection = 0; - num_colors_selected = 0; - // Store original colorset before clearing - new_colorset = pat.colorset(); - // Clear existing colors in pattern - new_colorset.clear(); -#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; - default: // hold past - break; - } -} - -void Helios::handle_state_color_selection() -{ - switch (cur_state) { - case STATE_COLOR_GROUP_SELECTION: - // pick the hue group - handle_state_color_group_selection(); - break; - case STATE_COLOR_VARIANT_SELECTION: - // pick the hue - handle_state_color_variant_selection(); - break; - default: - break; - } - // get the current color - RGBColor cur = Led::get(); - cur.red /= 2; - cur.green /= 2; - cur.blue /= 2; - // show selection in all of these menus - show_selection(cur); -} - -struct ColorsMenuData { - RGBColor colors[4]; -}; - -// array of colors for selection -static const ColorsMenuData color_menu_data[NUM_COLOR_GROUPS] = { - // color0 color1 color2 color3 - // =================================================================== - { RGB_RED, RGB_CORAL_ORANGE, RGB_ORANGE, RGB_YELLOW }, - { RGB_LIME_GREEN, RGB_GREEN, RGB_SEAFOAM, RGB_TURQUOISE }, - { RGB_ICE_BLUE, RGB_LIGHT_BLUE, RGB_BLUE, RGB_ROYAL_BLUE }, - { RGB_PURPLE, RGB_PINK, RGB_HOT_PINK, RGB_MAGENTA }, -}; - -void Helios::handle_state_color_group_selection() -{ - if (Button::onShortClick()) { - menu_selection = (menu_selection + 1) % NUM_MENUS_GROUP; - } - - uint8_t color_quad = (menu_selection - 2) % NUM_COLOR_GROUPS; // Now using 4 groups - if (menu_selection > 6) { - menu_selection = 0; - } - - if (Button::onLongClick()) { - // select color - switch (menu_selection) { - case 0: // selected blank - // add blank to set - new_colorset.addColor(RGB_OFF); - num_colors_selected++; - break; - case 1: // selected white - // adds white - new_colorset.addColor(RGB_WHITE); - num_colors_selected++; - break; - default: // 2-6 (color groups) - selected_base_group = color_quad; - cur_state = STATE_COLOR_VARIANT_SELECTION; - menu_selection = 0; - return; - } - - // If we've selected enough colors, save and exit - if (num_colors_selected >= NUM_COLOR_SLOTS) { - // Restore original colorset if no colors were selected - pat.setColorset(new_colorset); - save_cur_mode(); -#if ALTERNATIVE_HSV_RGB == 1 - // restore hsv to rgb algorithm type, done color selection - g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; -#endif - cur_state = STATE_MODES; - return; - } - // Otherwise reset menu selection to continue selecting colors - menu_selection = 0; - } - - // 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 = color_menu_data[color_quad].colors[0]; - col2 = color_menu_data[color_quad].colors[2]; - on_dur = 500; - off_dur = 500; - break; - } - Led::strobe(on_dur, off_dur, col1, col2); - // show a white flash for the first two menus - if (menu_selection <= 1) { - show_selection(RGB_WHITE_BRI_LOW); - } else { - // dim the color for the quad menus - RGBColor cur = Led::get(); - cur.red /= 2; - cur.green /= 2; - cur.blue /= 2; - show_selection(RGB_WHITE_BRI_LOW); - } - - if (menu_selection == 0) { - // If the user is on the blank option (menu_selection == 0) and holding, flash red to indicate they can save with current colors - if (Button::holdPressing()) { - // flash red to indicate save action is available - Led::strobe(150, 150, RGB_RED_BRI_LOW, RGB_OFF); - } - - if (Button::onHoldClick()) { - cur_state = STATE_MODES; - if (num_colors_selected > 0) { - pat.setColorset(new_colorset); - // Save with current colors if at least one color is selected - save_cur_mode(); - } - num_colors_selected = 0; - } - } -} - -void Helios::handle_state_color_variant_selection() -{ - // handle iterating to the next option - if (Button::onShortClick()) { - menu_selection = (menu_selection + 1) % NUM_COLORS_PER_GROUP; - } - - // Get the color directly from the color menu data - RGBColor selected_color = color_menu_data[selected_base_group].colors[menu_selection]; - - // render current selection - Led::set(selected_color); - - if (Button::onLongClick()) { - // Save the color and increment counter - new_colorset.addColor(selected_color); - num_colors_selected++; - - // If we've selected enough colors, save and exit - if (num_colors_selected >= NUM_COLOR_SLOTS) { - // Restore original colorset if no colors were selected - pat.setColorset(new_colorset); - save_cur_mode(); -#if ALTERNATIVE_HSV_RGB == 1 - // restore hsv to rgb algorithm type, done color selection - g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; -#endif - cur_state = STATE_MODES; - } else { - // Go back to group selection for next color - cur_state = STATE_COLOR_GROUP_SELECTION; - menu_selection = 0; - } - } -} - -void Helios::handle_state_pat_select() -{ - if (Button::onLongClick()) { - save_cur_mode(); - cur_state = STATE_MODES; - } - if (Button::onShortClick()) { - Patterns::make_pattern((PatternID)menu_selection, pat); - menu_selection = (menu_selection + 1) % PATTERN_COUNT; - pat.init(); - } - pat.play(); - show_selection(RGB_MAGENTA_BRI_LOW); -} - -void Helios::handle_state_toggle_flag(Flags 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(80, 20, RGB_RED_BRI_LOW, RGB_OFF); - } else { - Led::strobe(20, 10, 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) { - factory_reset(); - } - cur_state = STATE_MODES; - } - show_selection(RGB_WHITE_BRI_LOW); -} - -void Helios::factory_reset() -{ - for (uint8_t i = 0; i < NUM_MODE_SLOTS; ++i) { - Patterns::make_default(i, pat); - Storage::write_pattern(i, pat); - } - // Reset global brightness to default - Led::setBrightness(DEFAULT_BRIGHTNESS); - Storage::write_brightness(DEFAULT_BRIGHTNESS); - // set global flags to autoplay - global_flags = FLAG_NONE; - cur_mode = 0; - // save global flags - save_global_flags(); - // re-load current mode - load_cur_mode(); -} - - -void Helios::show_selection(RGBColor color) -{ - // only show selection while pressing the button - if (!Button::isPressed()) { - return; - } - uint16_t holdDur = (uint16_t)Button::holdDuration(); - // if the hold duration is outside the flashing range do nothing - if (holdDur < SHORT_CLICK_THRESHOLD || holdDur >= HOLD_CLICK_START) { - return; - } - Led::set(color); -} +#include + +#include "Helios.h" + +#include "ColorConstants.h" +#include "TimeControl.h" +#include "Storage.h" +#include "Pattern.h" +#include "Patterns.h" +#include "Random.h" +#include "Button.h" +#include "Led.h" + +#ifdef HELIOS_EMBEDDED +#include +#include +#include +#endif + +#ifdef HELIOS_CLI +#include +#endif + +#include + +// some internal macros that shouldn't change +// The number of menus in hue/sat/val selection +#define NUM_COLORS_PER_GROUP 4 +// the number of color groups in the color selection menu +#define NUM_COLOR_GROUPS 4 +// the number of menus in group selection +#define NUM_MENUS_GROUP 8 + +// Forward declarations for internal functions +static uint8_t helios_init_components(void); +static void helios_handle_state(void); +static void helios_handle_state_modes(void); +static void helios_handle_off_menu(uint8_t mag, uint8_t past); +static void helios_handle_on_menu(uint8_t mag, uint8_t past); +static void helios_handle_state_color_selection(void); +static void helios_handle_state_color_group_selection(void); +static void helios_handle_state_color_variant_selection(void); +static void helios_handle_state_pat_select(void); +static void helios_handle_state_toggle_flag(enum helios_flags flag); +static void helios_handle_state_set_defaults(void); +static void helios_show_selection(rgb_color_t color); +static void helios_factory_reset(void); + +// the slot selection returns this info for internal menu logic +enum helios_color_select_option { + OPTION_NONE = 0, + + SELECTED_ADD, + SELECTED_EXIT, + SELECTED_SLOT +}; + +enum helios_state { + STATE_MODES, + STATE_COLOR_GROUP_SELECTION, + STATE_COLOR_VARIANT_SELECTION, + STATE_PATTERN_SELECT, + STATE_TOGGLE_LOCK, + STATE_SET_DEFAULTS, +#ifdef HELIOS_CLI + STATE_SLEEP, +#endif +}; + +// static members +static enum helios_state cur_state; +static enum helios_flags global_flags; +static uint8_t menu_selection; +static uint8_t cur_mode; +static uint8_t selected_base_group; +static uint8_t num_colors_selected; // +static pattern_t pat; +static uint8_t keepgoing; +static uint32_t last_mode_switch_time; +static colorset_t new_colorset; + +#ifdef HELIOS_CLI +static uint8_t sleeping; // +#endif + +volatile char helios_version[] = HELIOS_VERSION_STR; + +uint8_t helios_init(void) +{ + // first initialize all the components of helios + if (!helios_init_components()) { + return 0; + } + // then initialize the hardware for embedded helios +#ifdef HELIOS_EMBEDDED + // Set PB0, PB1, PB4 as output + DDRB |= (1 << DDB0) | (1 << DDB1) | (1 << DDB4); + // Timer0 Configuration for PWM + TCCR0A = (1 << WGM01) | (1 << WGM00) | (1 << COM0A1) | (1 << COM0B1); + // No prescaler + TCCR0B = (1 << CS00); + // Timer1 for PWM on PB4, Fast PWM, Non-inverting, No prescaler + TCCR1 = (1 << PWM1A) | (1 << COM1A1) | (1 << CS10); + // Enable PWM on OC1B + GTCCR = (1 << PWM1B) | (1 << COM1B1); + // Enable Timer0 overflow interrupt + TIMSK |= (1 << TOIE0); + // Enable interrupts + sei(); +#endif + return 1; +} + +static uint8_t helios_init_components(void) +{ + // initialize various components of Helios + if (!time_init()) { + return 0; + } + if (!led_init()) { + return 0; + } + if (!storage_init()) { + return 0; + } + if (!button_init()) { + return 0; + } + // initialize global variables + cur_state = STATE_MODES; + menu_selection = 0; + cur_mode = 0; + num_colors_selected = 0; + selected_base_group = 0; + keepgoing = 1; + last_mode_switch_time = 0; +#ifdef HELIOS_CLI + sleeping = 0; +#endif + helios_load_global_flags(); + helios_load_cur_mode(); + return 1; +} + +void helios_tick(void) +{ + // + button_update(); + + // + helios_handle_state(); + + // Update the Leds once per frame + led_update(); + + // + time_tick_clock(); +} + +void helios_enter_sleep(void) +{ +#ifdef HELIOS_EMBEDDED + // clear the led colors + led_clear(); + // Set all pins to input + DDRB = 0x00; + // Disable pull-ups on all pins + PORTB = 0x00; + // Enable wake on interrupt for the button + button_enable_wake(); + // Set sleep mode to POWER DOWN mode + set_sleep_mode(SLEEP_MODE_PWR_DOWN); + // enter sleep + sleep_mode(); + // ... interrupt will make us wake here + + // Set PB0, PB1, PB4 as output + DDRB |= (1 << DDB0) | (1 << DDB1) | (1 << DDB4); + // wakeup here, re-init + helios_init_components(); +#else + cur_state = STATE_SLEEP; + // enable the sleep bool + sleeping = 1; + // Enable wake on button press/click for CLI + button_enable_wake(); +#endif +} + +void helios_wakeup(void) +{ +#ifdef HELIOS_EMBEDDED + // nothing needed here, this interrupt firing will make the mainthread resume +#else + // + uint8_t pressed = button_is_pressed(); + // re-initialize some stuff + time_init(); + button_init(); + // so just re-press it + if (pressed) { + button_do_press(); + } + cur_state = STATE_MODES; + // turn off the sleeping flag that only CLI has + sleeping = 0; +#endif +} + +void helios_load_next_mode(void) +{ + // increment current mode and wrap around + cur_mode = (uint8_t)(cur_mode + 1) % NUM_MODE_SLOTS; + // now load current mode again + helios_load_cur_mode(); +} + +void helios_load_cur_mode(void) +{ + // 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 + pattern_init_state(&pat); + // Update the last mode switch time when loading a mode + last_mode_switch_time = time_get_current_time(); +} + +void helios_save_cur_mode(void) +{ + storage_write_pattern(cur_mode, &pat); +} + +void helios_load_global_flags(void) +{ + // read the global flags from index 0 config + global_flags = (enum helios_flags)storage_read_global_flags(); + cur_mode = storage_read_current_mode(); +} + +void helios_save_global_flags(void) +{ + storage_write_global_flags(global_flags); + storage_write_current_mode(cur_mode); +} + +void helios_set_mode_index(uint8_t mode_index) +{ + cur_mode = (uint8_t)mode_index % NUM_MODE_SLOTS; + // now load current mode again + helios_load_cur_mode(); +} + +uint8_t helios_keep_going(void) +{ + return keepgoing; +} + +void helios_terminate(void) +{ + keepgoing = 0; +} + +void helios_set_flag(enum helios_flags flag) +{ + global_flags = (enum helios_flags)(global_flags | flag); +} + +uint8_t helios_has_flag(enum helios_flags flag) +{ + return (global_flags & flag) == flag; +} + +void helios_clear_flag(enum helios_flags flag) +{ + global_flags = (enum helios_flags)(global_flags & ~flag); +} + +void helios_toggle_flag(enum helios_flags flag) +{ + global_flags = (enum helios_flags)(global_flags ^ flag); +} + +#ifdef HELIOS_CLI +uint8_t helios_is_asleep(void) +{ + return sleeping; +} + +pattern_t *helios_cur_pattern(void) +{ + return &pat; +} +#endif + +static void helios_handle_state(void) +{ + // check for the force sleep button hold regardless of which state we're in + if (button_hold_duration() > FORCE_SLEEP_TIME) { + // when released the device will just sleep + if (button_on_release()) { + helios_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_is_pressed()) { + led_clear(); + return; + } + } + // otherwise just handle the state like normal + switch (cur_state) { + case STATE_MODES: + helios_handle_state_modes(); + break; + case STATE_COLOR_GROUP_SELECTION: + case STATE_COLOR_VARIANT_SELECTION: + helios_handle_state_color_selection(); + break; + case STATE_PATTERN_SELECT: + helios_handle_state_pat_select(); + break; + case STATE_TOGGLE_LOCK: + helios_handle_state_toggle_flag(FLAG_LOCKED); + break; + case STATE_SET_DEFAULTS: + helios_handle_state_set_defaults(); + break; +#ifdef HELIOS_CLI + case STATE_SLEEP: + // simulate sleep in helios CLI + if (button_on_press() || button_on_short_click() || button_on_long_click()) { + helios_wakeup(); + } + break; +#endif + } +} + +static void helios_handle_state_modes(void) +{ + // whether they have released the button since turning on + uint8_t hasReleased = (button_release_count() > 0); + + if (button_release_count() > 1 && button_on_short_click()) { + helios_enter_sleep(); + return; + } + + // check for lock and go back to sleep + if (helios_has_flag(FLAG_LOCKED) && hasReleased && !button_on_release()) { + helios_enter_sleep(); + // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! + return; + } + + if (!helios_has_flag(FLAG_LOCKED) && hasReleased) { + // just play the current mode + pattern_play(&pat); + } + // check how long the button is held + uint32_t holdDur = button_hold_duration(); + // + uint8_t magnitude = (uint8_t)(holdDur / MENU_HOLD_TIME); + // whether the user has held the button longer than a short click + uint8_t heldPast = (holdDur > SHORT_CLICK_THRESHOLD); + + // flash red briefly when locked and short clicked + if (helios_has_flag(FLAG_LOCKED) && holdDur < SHORT_CLICK_THRESHOLD) { + rgb_color_t red; + rgb_init_from_raw(&red, RGB_RED_BRI_LOW); + led_set_rgb(&red); + } + // if the button is held for at least 1 second + if (button_is_pressed() && heldPast) { + rgb_color_t color; + // if the button has been released before then show the on menu + if (hasReleased) { + switch (magnitude) { + default: + case 0: led_clear(); break; // + case 1: rgb_init_from_raw(&color, RGB_TURQUOISE_BRI_LOW); led_set_rgb(&color); break; // + case 2: rgb_init_from_raw(&color, RGB_MAGENTA_BRI_LOW); led_set_rgb(&color); break; // + } + } else { + if (helios_has_flag(FLAG_LOCKED)) { + switch (magnitude) { + default: + case 0: led_clear(); break; + case TIME_TILL_GLOW_LOCK_UNLOCK: rgb_init_from_raw(&color, RGB_RED_BRI_LOW); led_set_rgb(&color); break; // + } + } else { + switch (magnitude) { + default: + case 0: led_clear(); break; // + case 1: rgb_init_from_raw(&color, RGB_RED_BRI_LOW); led_set_rgb(&color); break; // + case 2: rgb_init_from_raw(&color, RGB_BLUE_BRI_LOW); led_set_rgb(&color); break; // + } + } + } + } + // if this isn't a release tick there's nothing more to do + if (button_on_release()) { + // Resets the menu selection before entering new state + menu_selection = 0; + if (heldPast && button_release_count() == 1) { + helios_handle_off_menu(magnitude, heldPast); + return; + } + // otherwise if we have released it then we are in the 'on' menu + helios_handle_on_menu(magnitude, heldPast); + } +} + +static void helios_handle_off_menu(uint8_t mag, uint8_t past) +{ + (void)past; // + // if still locked then handle the unlocking menu which is just if mag == 5 + if (helios_has_flag(FLAG_LOCKED)) { + switch (mag) { + case TIME_TILL_GLOW_LOCK_UNLOCK: // + cur_state = STATE_TOGGLE_LOCK; + break; + default: + // just go back to sleep in hold-past off menu + helios_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: // + cur_state = STATE_TOGGLE_LOCK; + led_clear(); + return; // + case 2: // + cur_state = STATE_SET_DEFAULTS; + return; // + default: + // just go back to sleep in hold-past off menu + helios_enter_sleep(); + // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! + return; + } +} + +static void helios_handle_on_menu(uint8_t mag, uint8_t past) +{ + switch (mag) { + case 0: // + // but only if we held for more than a short click + if (past) { + helios_enter_sleep(); + // ALWAYS RETURN AFTER SLEEP! WE WILL WAKE HERE! + return; + } + break; + case 1: // + cur_state = STATE_COLOR_GROUP_SELECTION; + // reset the menu selection and colors selected + menu_selection = 0; + num_colors_selected = 0; + // Store original colorset before clearing + new_colorset = *pattern_colorset_ptr(&pat); + // Clear existing colors in pattern + colorset_clear(&new_colorset); +#if ALTERNATIVE_HSV_RGB == 1 + // use the nice hue to rgb rainbow + g_hsv_rgb_alg = HSV_TO_RGB_RAINBOW; +#endif + break; + case 2: // + cur_state = STATE_PATTERN_SELECT; + // reset the menu selection + menu_selection = 0; + break; + default: // + break; + } +} + +struct colors_menu_data { + uint32_t colors[4]; +}; + +// array of colors for selection +static const struct colors_menu_data color_menu_data[NUM_COLOR_GROUPS] = { + // color0 color1 color2 color3 + // =================================================================== + { {RGB_RED, RGB_CORAL_ORANGE, RGB_ORANGE, RGB_YELLOW} }, + { {RGB_LIME_GREEN, RGB_GREEN, RGB_SEAFOAM, RGB_TURQUOISE} }, + { {RGB_ICE_BLUE, RGB_LIGHT_BLUE, RGB_BLUE, RGB_ROYAL_BLUE} }, + { {RGB_PURPLE, RGB_PINK, RGB_HOT_PINK, RGB_MAGENTA} }, +}; + +static void helios_handle_state_color_selection(void) +{ + switch (cur_state) { + case STATE_COLOR_GROUP_SELECTION: + // pick the hue group + helios_handle_state_color_group_selection(); + break; + case STATE_COLOR_VARIANT_SELECTION: + // pick the hue + helios_handle_state_color_variant_selection(); + break; + default: + break; + } + // get the current color + rgb_color_t cur = led_get(); + cur.red /= 2; + cur.green /= 2; + cur.blue /= 2; + // show selection in all of these menus + helios_show_selection(cur); +} + +static void helios_handle_state_color_group_selection(void) +{ + rgb_color_t color; + + if (button_on_short_click()) { + menu_selection = (menu_selection + 1) % NUM_COLOR_GROUPS; + } + + // Display a sample color from the selected group + rgb_init_from_raw(&color, color_menu_data[menu_selection].colors[0]); + led_set_rgb(&color); + + if (button_on_long_click()) { + selected_base_group = menu_selection; + cur_state = STATE_COLOR_VARIANT_SELECTION; + menu_selection = 0; + } +} + +static void helios_handle_state_color_variant_selection(void) +{ + rgb_color_t color; + + if (button_on_short_click()) { + // If we've selected max colors, next click exits + if (num_colors_selected >= NUM_COLOR_SLOTS) { + // Apply the newly built colorset + pattern_set_colorset(&pat, &new_colorset); + // Save and return to normal mode + helios_save_cur_mode(); + cur_state = STATE_MODES; + menu_selection = 0; +#if ALTERNATIVE_HSV_RGB == 1 + g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; +#endif + return; + } + + // Cycle through colors in the group + menu_selection = (menu_selection + 1) % NUM_COLORS_PER_GROUP; + } + + // Display the currently selected color + rgb_init_from_raw(&color, color_menu_data[selected_base_group].colors[menu_selection]); + led_set_rgb(&color); + + if (button_on_long_click()) { + // Add the selected color to the colorset + rgb_init_from_raw(&color, color_menu_data[selected_base_group].colors[menu_selection]); + if (colorset_add_color(&new_colorset, color)) { + num_colors_selected++; + } + + // If we've selected max colors, exit + if (num_colors_selected >= NUM_COLOR_SLOTS) { + // Apply the newly built colorset + pattern_set_colorset(&pat, &new_colorset); + // Save and return to normal mode + helios_save_cur_mode(); + cur_state = STATE_MODES; + menu_selection = 0; +#if ALTERNATIVE_HSV_RGB == 1 + g_hsv_rgb_alg = HSV_TO_RGB_GENERIC; +#endif + return; + } + + // Otherwise go back to group selection for next color + cur_state = STATE_COLOR_GROUP_SELECTION; + menu_selection = 0; + } +} + +static void helios_handle_state_pat_select(void) +{ + rgb_color_t color; + + if (button_on_short_click()) { + menu_selection = (menu_selection + 1) % PATTERN_COUNT; + } + + // show the menu selection + switch (menu_selection) { + case 0: rgb_init_from_raw(&color, RGB_RED); break; + case 1: rgb_init_from_raw(&color, RGB_GREEN); break; + case 2: rgb_init_from_raw(&color, RGB_BLUE); break; + case 3: rgb_init_from_raw(&color, RGB_YELLOW); break; + case 4: rgb_init_from_raw(&color, RGB_MAGENTA); break; + default: rgb_init_from_raw(&color, RGB_WHITE); break; + } + led_set_rgb(&color); + + if (button_on_long_click()) { + // make the selected pattern + patterns_make_pattern((enum pattern_id)(PATTERN_FIRST + menu_selection), &pat); + // reset the pattern to revert to on/off state + pattern_init_state(&pat); + // save and return to normal mode + helios_save_cur_mode(); + cur_state = STATE_MODES; + menu_selection = 0; + } +} + +static void helios_handle_state_toggle_flag(enum helios_flags flag) +{ + rgb_color_t color; + + // wait until button release then toggle the flag + if (button_on_release()) { + helios_toggle_flag(flag); + helios_save_global_flags(); + // show feedback based on new state + if (helios_has_flag(flag)) { + rgb_init_from_raw(&color, RGB_GREEN); + } else { + rgb_init_from_raw(&color, RGB_RED); + } + led_hold(&color); + cur_state = STATE_MODES; + } +} + +static void helios_handle_state_set_defaults(void) +{ + rgb_color_t color; + + // wait until button release then factory reset + if (button_on_release()) { + helios_factory_reset(); + // show feedback + rgb_init_from_raw(&color, RGB_BLUE); + led_hold(&color); + cur_state = STATE_MODES; + } +} + +static void helios_show_selection(rgb_color_t color) +{ + uint32_t time_since_click = time_get_current_time(); + if (button_press_time() > 0) { + time_since_click = time_get_current_time() - button_press_time(); + } + // flash the selection color briefly after clicking + if (time_since_click < 150) { + led_set_rgb(&color); + } +} + +static void helios_factory_reset(void) +{ + uint8_t slot; + // write default patterns to all slots + for (slot = 0; slot < NUM_MODE_SLOTS; ++slot) { + patterns_make_default(slot, &pat); + storage_write_pattern(slot, &pat); + } + // clear all flags + global_flags = FLAG_NONE; + helios_save_global_flags(); + // reload current mode + helios_load_cur_mode(); +} + diff --git a/Helios/Helios.h b/Helios/Helios.h index 07bb3f2e..8efed95a 100644 --- a/Helios/Helios.h +++ b/Helios/Helios.h @@ -1,98 +1,54 @@ +#ifndef HELIOS_H +#define HELIOS_H + +#ifdef __cplusplus +extern "C" { +#endif + #include #include "HeliosConfig.h" #include "Colorset.h" #include "Pattern.h" -class Helios -{ -public: - static bool init(); - static void tick(); +// Forward declaration +typedef struct pattern_t pattern_t; +typedef struct colorset_t colorset_t; + +uint8_t helios_init(void); +void helios_tick(void); - static void enter_sleep(); - static void wakeup(); +void helios_enter_sleep(void); +void helios_wakeup(void); - static bool keep_going() { return keepgoing; } - static void terminate() { keepgoing = false; } +uint8_t helios_keep_going(void); +void helios_terminate(void); - static void load_next_mode(); - static void load_cur_mode(); - static void save_cur_mode(); - static void load_global_flags(); - static void save_global_flags(); - static void set_mode_index(uint8_t mode_index); +void helios_load_next_mode(void); +void helios_load_cur_mode(void); +void helios_save_cur_mode(void); +void helios_load_global_flags(void); +void helios_save_global_flags(void); +void helios_set_mode_index(uint8_t mode_index); #ifdef HELIOS_CLI - static bool is_asleep() { return sleeping; } - static Pattern &cur_pattern() { return pat; } +uint8_t helios_is_asleep(void); +pattern_t *helios_cur_pattern(void); #endif - enum Flags : uint8_t { - FLAG_NONE = 0, - FLAG_LOCKED = (1 << 0), - }; - - // 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); } - -private: - // initialize the various components of helios - static bool init_components(); - - 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 - }; +enum helios_flags { + FLAG_NONE = 0, + FLAG_LOCKED = (1 << 0), +}; - static void handle_off_menu(uint8_t mag, bool past); - static void handle_on_menu(uint8_t mag, bool past); - static void handle_state_color_selection(); - static void handle_state_color_group_selection(); - static void handle_state_color_variant_selection(); - static void handle_state_pat_select(); - static void handle_state_toggle_flag(Flags flag); - static void handle_state_set_defaults(); - static void show_selection(RGBColor color); - static void factory_reset(); +// get/set global flags +void helios_set_flag(enum helios_flags flag); +uint8_t helios_has_flag(enum helios_flags flag); +void helios_clear_flag(enum helios_flags flag); +void helios_toggle_flag(enum helios_flags flag); - enum State : uint8_t { - STATE_MODES, - STATE_COLOR_GROUP_SELECTION, - STATE_COLOR_VARIANT_SELECTION, - STATE_PATTERN_SELECT, - STATE_TOGGLE_LOCK, - STATE_SET_DEFAULTS, -#ifdef HELIOS_CLI - STATE_SLEEP, +#ifdef __cplusplus +} #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 group that was selected in color select - static uint8_t selected_base_group; - static uint8_t num_colors_selected; // Track number of colors selected in current session - static Pattern pat; - static bool keepgoing; - static uint32_t last_mode_switch_time; - static Colorset new_colorset; - -#ifdef HELIOS_CLI - static bool sleeping; // Only used in CLI mode #endif -}; diff --git a/Helios/HeliosConfig.h b/Helios/HeliosConfig.h index 2af799e9..12260037 100644 --- a/Helios/HeliosConfig.h +++ b/Helios/HeliosConfig.h @@ -1,167 +1,167 @@ -#ifndef HELIOS_CONFIG_H -#define HELIOS_CONFIG_H - -// Version Configurations -// -// The engine major version indicates the state of the save file, -// if changes to the save format occur then the major version -// must increment so that the savefiles will not be loaded -#ifndef HELIOS_VERSION_MAJOR -#define HELIOS_VERSION_MAJOR 0 -#endif - -// A minor version simply indicates a bugfix or minor change that -// will not affect the save files produced by the engine. This means -// a savefile produced by 1.1 should be loadable by an engine on 1.2 -// and vice versa, but an engine on 2.0 cannot share savefiles with -// either of the engines on version 1.1 or 1.2 -#ifndef HELIOS_VERSION_MINOR -#define HELIOS_VERSION_MINOR 0 -#endif - -// The build or patch number based on the major.minor version, this is -// set by the build system using the number of commits since last version -#ifndef HELIOS_BUILD_NUMBER -#define HELIOS_BUILD_NUMBER 0 -#endif - -// Produces a number like 1.3.0 -#ifndef HELIOS_VERSION_NUMBER -#define HELIOS_VERSION_NUMBER HELIOS_VERSION_MAJOR.HELIOS_VERSION_MINOR.HELIOS_BUILD_NUMBER -#endif - - -// Helios Version String -// -// This is the string literal equivalent of HELIOS_VERSION_NUMBER above -#define ADD_QUOTES(str) #str -#define EXPAND_AND_QUOTE(str) ADD_QUOTES(str) -#define HELIOS_VERSION_STR EXPAND_AND_QUOTE(HELIOS_VERSION_NUMBER) - -// 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 TIME_TILL_LONG_CLICK_FLASH 1000 - -// Unlock Glow Lock Duration -// -// How long the hold the button to unlock chip -#define TIME_TILL_GLOW_LOCK_UNLOCK 2 - -// Hold Click Start Threshold -// -// The minimum length a hold click can be -#define HOLD_CLICK_START (SHORT_CLICK_THRESHOLD + TIME_TILL_LONG_CLICK_FLASH) - -// Hold Click End Threshold -// -// The maximum length a long click can be -#define HOLD_CLICK_END (HOLD_CLICK_START + TIME_TILL_LONG_CLICK_FLASH) - -// Max Color Slots -// -// The number of slots in a colorset -#define NUM_COLOR_SLOTS 3 - -// Mode Slots -// -// The number of modes on the device -#define NUM_MODE_SLOTS 1 - -// 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 - - -// 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 - -// ============================================================================ -// Storage Constants -// -// These are various storage sizes of data and some math to help -// calculate sizes or space requirements, note these will produce -// compiler errors unless you include the respective headers - - -// Storage Name -// -// This is mainly used by the CLI tool as a filename for simulated eeprom -#define STORAGE_FILENAME "Helios.storage" - -// Storage Size -// -// The total size of storage where modes and global settings are saved. -// The EEPROM on attiny85 is 512 bytes, but due to limitations on flash -// only the lower half of the eeprom is being used -#define STORAGE_SIZE 256 - -// 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 Args Size -// -// There is currently 6 args for a pattern: on, off, gap, dash, group, blend -// Each takes up 1 byte currently -#define PAT_ARGS_SIZE (sizeof(PatternArgs)) - -// Pattern Size -// -// The actual pattern storage size is the size of the colorset + params + 1 pat flags -#define PATTERN_SIZE (COLORSET_SIZE + PAT_ARGS_SIZE + 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 - -// Debug Pattern Logic -// -// Turn this on 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 0 - -#endif +#ifndef HELIOS_CONFIG_H +#define HELIOS_CONFIG_H + +// Version Configurations +// +// The engine major version indicates the state of the save file, +// if changes to the save format occur then the major version +// must increment so that the savefiles will not be loaded +#ifndef HELIOS_VERSION_MAJOR +#define HELIOS_VERSION_MAJOR 0 +#endif + +// A minor version simply indicates a bugfix or minor change that +// will not affect the save files produced by the engine. This means +// a savefile produced by 1.1 should be loadable by an engine on 1.2 +// and vice versa, but an engine on 2.0 cannot share savefiles with +// either of the engines on version 1.1 or 1.2 +#ifndef HELIOS_VERSION_MINOR +#define HELIOS_VERSION_MINOR 0 +#endif + +// The build or patch number based on the major.minor version, this is +// set by the build system using the number of commits since last version +#ifndef HELIOS_BUILD_NUMBER +#define HELIOS_BUILD_NUMBER 0 +#endif + +// Produces a number like 1.3.0 +#ifndef HELIOS_VERSION_NUMBER +#define HELIOS_VERSION_NUMBER HELIOS_VERSION_MAJOR.HELIOS_VERSION_MINOR.HELIOS_BUILD_NUMBER +#endif + + +// Helios Version String +// +// This is the string literal equivalent of HELIOS_VERSION_NUMBER above +#define ADD_QUOTES(str) #str +#define EXPAND_AND_QUOTE(str) ADD_QUOTES(str) +#define HELIOS_VERSION_STR EXPAND_AND_QUOTE(HELIOS_VERSION_NUMBER) + +// 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 TIME_TILL_LONG_CLICK_FLASH 1000 + +// Unlock Glow Lock Duration +// +// How long the hold the button to unlock chip +#define TIME_TILL_GLOW_LOCK_UNLOCK 2 + +// Hold Click Start Threshold +// +// The minimum length a hold click can be +#define HOLD_CLICK_START (SHORT_CLICK_THRESHOLD + TIME_TILL_LONG_CLICK_FLASH) + +// Hold Click End Threshold +// +// The maximum length a long click can be +#define HOLD_CLICK_END (HOLD_CLICK_START + TIME_TILL_LONG_CLICK_FLASH) + +// Max Color Slots +// +// The number of slots in a colorset +#define NUM_COLOR_SLOTS 3 + +// Mode Slots +// +// The number of modes on the device +#define NUM_MODE_SLOTS 1 + +// 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 + + +// 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 + +// ============================================================================ +// Storage Constants +// +// These are various storage sizes of data and some math to help +// calculate sizes or space requirements, note these will produce +// compiler errors unless you include the respective headers + + +// Storage Name +// +// This is mainly used by the CLI tool as a filename for simulated eeprom +#define STORAGE_FILENAME "Helios.storage" + +// Storage Size +// +// The total size of storage where modes and global settings are saved. +// The EEPROM on attiny85 is 512 bytes, but due to limitations on flash +// only the lower half of the eeprom is being used +#define STORAGE_SIZE 256 + +// Colorset Size +// +// the colorset is just an array of colors but it also has a num colors val +#define COLORSET_SIZE ((sizeof(rgb_color_t) * NUM_COLOR_SLOTS) + 1) + +// Pattern Args Size +// +// There is currently 6 args for a pattern: on, off, gap, dash, group, blend +// Each takes up 1 byte currently +#define PAT_ARGS_SIZE (sizeof(pattern_args_t)) + +// Pattern Size +// +// The actual pattern storage size is the size of the colorset + params + 1 pat flags +#define PATTERN_SIZE (COLORSET_SIZE + PAT_ARGS_SIZE + 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 + +// Debug Pattern Logic +// +// Turn this on 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 0 + +#endif diff --git a/Helios/Led.cpp b/Helios/Led.cpp index 13052f1e..9bce4e01 100644 --- a/Helios/Led.cpp +++ b/Helios/Led.cpp @@ -1,147 +1,190 @@ -#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 -#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) -#endif - -#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 - // pin ctrl done in Helios::init -#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::set(uint8_t r, uint8_t g, uint8_t b) -{ - set(RGBColor(r, g, b)); -} - -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::setPWM(uint8_t pwmPin, uint8_t pwmValue, volatile uint8_t &controlRegister, - uint8_t controlBit, volatile uint8_t &compareRegister) -{ -#ifdef HELIOS_EMBEDDED - if (pwmValue == 0) { - // digitalWrite(pin, LOW) - controlRegister &= ~controlBit; // Disable PWM - PORTB &= ~(1 << pwmPin); // Set the pin low - } else if (pwmValue == 255) { - // digitalWrite(pin, HIGH) - controlRegister &= ~controlBit; // Disable PWM - PORTB |= (1 << pwmPin); // Set the pin high - } else { - // analogWrite(pin, value) - controlRegister |= controlBit; // Enable PWM - compareRegister = pwmValue; // Set PWM duty cycle - } -#endif -} - -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 - // backup SREG and turn off interrupts - uint8_t oldSREG = SREG; - cli(); - - // set the PWM for R/G/B output - setPWM(PWM_PIN_R, m_realColor.red, TCCR0A, (1 << COM0A1), OCR0A); - setPWM(PWM_PIN_G, m_realColor.green, TCCR0A, (1 << COM0B1), OCR0B); - setPWM(PWM_PIN_B, m_realColor.blue, GTCCR, (1 << COM1B1), OCR1B); - - // turn interrupts back on - SREG = oldSREG; -#endif -#endif -} +#include + +#include "Led.h" + +#include "TimeControl.h" + +#include "HeliosConfig.h" + +#ifdef HELIOS_EMBEDDED +#ifdef HELIOS_ARDUINO +#include +#else +#include +#include +#include +#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) +#endif + +#define SCALE8(i, scale) (((uint16_t)i * (uint16_t)(scale)) >> 8) + +// Forward declaration +static void led_set_pwm(uint8_t pwmPin, uint8_t pwmValue, volatile uint8_t *controlRegister, + uint8_t controlBit, volatile uint8_t *compareRegister); + +// array of led color values +static rgb_color_t m_ledColor; +static rgb_color_t m_realColor; +// global brightness +static uint8_t m_brightness = DEFAULT_BRIGHTNESS; + +uint8_t led_init(void) +{ + // clear the led colors + rgb_init_from_raw(&m_ledColor, RGB_OFF); + rgb_init_from_raw(&m_realColor, RGB_OFF); +#ifdef HELIOS_EMBEDDED +#ifdef HELIOS_ARDUINO + pinMode(0, OUTPUT); + pinMode(1, OUTPUT); + pinMode(4, OUTPUT); +#else + // pin ctrl done in helios_init +#endif +#endif + return 1; +} + +void led_cleanup(void) +{ +} + +void led_set_rgb(const rgb_color_t *col) +{ + rgb_copy(&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_set_rgb3(uint8_t r, uint8_t g, uint8_t b) +{ + rgb_color_t col; + rgb_init3(&col, r, g, b); + led_set_rgb(&col); +} + +void led_clear(void) +{ + rgb_color_t off; + rgb_init_from_raw(&off, RGB_OFF); + led_set_rgb(&off); +} + +void led_adjust_brightness(uint8_t fadeBy) +{ + rgb_adjust_brightness(&m_ledColor, fadeBy); +} + +void led_strobe(uint16_t on_time, uint16_t off_time, const rgb_color_t *off_col, const rgb_color_t *on_col) +{ + if ((time_get_current_time() % (on_time + off_time)) > on_time) { + led_set_rgb(off_col); + } else { + led_set_rgb(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_get_current_time() % (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 + hsv_color_t hsv; + rgb_color_t off, on; + hsv_init3(&hsv, shiftedHue, sat, val); + rgb_init_from_raw(&off, RGB_OFF); + rgb_init_from_hsv(&on, &hsv); + led_strobe(2, 13, &off, &on); +} + +void led_hold(const rgb_color_t *col) +{ + led_set_rgb(col); + led_update(); + time_delay_milliseconds(250); +} + +static void led_set_pwm(uint8_t pwmPin, uint8_t pwmValue, volatile uint8_t *controlRegister, + uint8_t controlBit, volatile uint8_t *compareRegister) +{ +#ifdef HELIOS_EMBEDDED + if (pwmValue == 0) { + // digitalWrite(pin, LOW) + *controlRegister &= ~controlBit; // Disable PWM + PORTB &= ~(1 << pwmPin); // Set the pin low + } else if (pwmValue == 255) { + // digitalWrite(pin, HIGH) + *controlRegister &= ~controlBit; // Disable PWM + PORTB |= (1 << pwmPin); // Set the pin high + } else { + // analogWrite(pin, value) + *controlRegister |= controlBit; // Enable PWM + *compareRegister = pwmValue; // Set PWM duty cycle + } +#else + (void)pwmPin; + (void)pwmValue; + (void)controlRegister; + (void)controlBit; + (void)compareRegister; +#endif +} + +rgb_color_t led_get(void) +{ + return m_ledColor; +} + +uint8_t led_get_brightness(void) +{ + return m_brightness; +} + +void led_set_brightness(uint8_t brightness) +{ + m_brightness = brightness; +} + +void led_update(void) +{ +#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 + // backup SREG and turn off interrupts + uint8_t oldSREG = SREG; + cli(); + + // set the PWM for R/G/B output + led_set_pwm(PWM_PIN_R, m_realColor.red, &TCCR0A, (1 << COM0A1), &OCR0A); + led_set_pwm(PWM_PIN_G, m_realColor.green, &TCCR0A, (1 << COM0B1), &OCR0B); + led_set_pwm(PWM_PIN_B, m_realColor.blue, >CCR, (1 << COM1B1), &OCR1B); + + // turn interrupts back on + SREG = oldSREG; +#endif +#endif +} + diff --git a/Helios/Led.h b/Helios/Led.h index 34c5faad..a132b493 100644 --- a/Helios/Led.h +++ b/Helios/Led.h @@ -1,61 +1,52 @@ #ifndef LED_CONTROL_H #define LED_CONTROL_H +#ifdef __cplusplus +extern "C" { +#endif + #include #include "Colortypes.h" -class Led -{ - // private unimplemented constructor - Led(); +// opting for static functions here because there should only ever be one +// Led control object and I don't like singletons -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(); +uint8_t led_init(void); +void led_cleanup(void); - // control individual LED, these are appropriate to use in internal pattern logic - static void set(RGBColor col); - static void set(uint8_t r, uint8_t g, uint8_t b); +// control individual LED, these are appropriate to use in internal pattern logic +void led_set_rgb(const rgb_color_t *col); +void led_set_rgb3(uint8_t r, uint8_t g, uint8_t b); - // Turn off individual LEDs, these are appropriate to use in internal pattern logic - static void clear() { set(RGB_OFF); } +// Turn off individual LEDs, these are appropriate to use in internal pattern logic +void led_clear(void); - // Dim individual LEDs, these are appropriate to use in internal pattern logic - static void adjustBrightness(uint8_t fadeBy); +// Dim individual LEDs, these are appropriate to use in internal pattern logic +void led_adjust_brightness(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); +// strobe between two colors with a simple on/off timing +void led_strobe(uint16_t on_time, uint16_t off_time, const rgb_color_t *col1, const rgb_color_t *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); +// breath the hue on an index +// warning: these use hsv to rgb in realtime! +void led_breath(uint8_t hue, uint32_t duration, uint8_t magnitude, uint8_t sat, uint8_t val); - // a very specialized api to hold all leds on a color for 250ms - static void hold(RGBColor col); +// a very specialized api to hold all leds on a color for 250ms +void led_hold(const rgb_color_t *col); - // get the RGBColor of an Led index - static RGBColor get() { return m_ledColor; } +// get the RGBColor of an Led index +rgb_color_t led_get(void); - // global brightness - static uint8_t getBrightness() { return m_brightness; } - static void setBrightness(uint8_t brightness) { m_brightness = brightness; } +// global brightness +uint8_t led_get_brightness(void); +void led_set_brightness(uint8_t brightness); - // actually update the LEDs and show the changes - static void update(); +// actually update the LEDs and show the changes +void led_update(void); -private: - static void setPWM(uint8_t pwmPin, uint8_t pwmValue, volatile uint8_t &controlRegister, - uint8_t controlBit, volatile uint8_t &compareRegister); - - // the global brightness - static uint8_t m_brightness; - // led color - static RGBColor m_ledColor; - static RGBColor m_realColor; -}; +#ifdef __cplusplus +} +#endif #endif diff --git a/Helios/Pattern.cpp b/Helios/Pattern.cpp index 804c52d1..7d1b701a 100644 --- a/Helios/Pattern.cpp +++ b/Helios/Pattern.cpp @@ -1,6 +1,5 @@ #include "Pattern.h" -//#include "../Patterns/PatternBuilder.h" #include "TimeControl.h" #include "Colorset.h" @@ -9,70 +8,94 @@ #include // for memcpy -Pattern::Pattern(uint8_t onDur, uint8_t offDur, uint8_t gap, - uint8_t dash, uint8_t group, uint8_t blend, uint8_t fade) : - m_args(onDur, offDur, gap, dash, group, blend, fade), - m_patternFlags(0), - m_colorset(), - m_groupCounter(0), - m_state(STATE_BLINK_ON), - m_blinkTimer(), - m_cur(), - m_next(), - m_fadeValue(0), - m_fadeStartTime(0) +// Forward declarations for internal functions +static void pattern_on_blink_on(pattern_t *pat); +static void pattern_on_blink_off(pattern_t *pat); +static void pattern_begin_gap(pattern_t *pat); +static void pattern_begin_dash(pattern_t *pat); +static void pattern_next_state(pattern_t *pat, uint8_t timing); +static void pattern_blend_blink_on(pattern_t *pat); +static void pattern_interpolate(uint8_t *current, const uint8_t next, uint8_t blend_speed); +static void pattern_tick_fade(pattern_t *pat); + +// ================================== +// Pattern Args Functions + +void pattern_args_init(pattern_args_t *args, uint8_t on, uint8_t off, uint8_t gap, + uint8_t dash, uint8_t group, uint8_t blend, uint8_t fade) { + args->on_dur = on; + args->off_dur = off; + args->gap_dur = gap; + args->dash_dur = dash; + args->group_size = group; + args->blend_speed = blend; + args->fade_dur = fade; } -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.fade_dur) +// ================================== +// Pattern Functions + +void pattern_init(pattern_t *pat, uint8_t onDur, uint8_t offDur, uint8_t gap, + uint8_t dash, uint8_t group, uint8_t blend, uint8_t fade) { + pattern_args_init(&pat->m_args, onDur, offDur, gap, dash, group, blend, fade); + pat->m_patternFlags = 0; + colorset_init(&pat->m_colorset); + pat->m_groupCounter = 0; + pat->m_state = STATE_BLINK_ON; + timer_init_default(&pat->m_blinkTimer); + rgb_init(&pat->m_cur); + rgb_init(&pat->m_next); + pat->m_fadeValue = 0; + pat->m_fadeStartTime = 0; } -Pattern::~Pattern() +void pattern_init_with_args(pattern_t *pat, const pattern_args_t *args) { + pattern_init(pat, args->on_dur, args->off_dur, args->gap_dur, + args->dash_dur, args->group_size, args->blend_speed, args->fade_dur); } -void Pattern::init() +void pattern_init_state(pattern_t *pat) { - m_colorset.resetIndex(); + colorset_reset_index(&pat->m_colorset); // Reset the fade start time to the current time - m_fadeStartTime = Time::getCurtime(); + pat->m_fadeStartTime = time_get_current_time(); // the default state to begin with - m_state = STATE_BLINK_ON; + pat->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_args.dash_dur > 0) { - m_state = STATE_BEGIN_DASH; + if (pat->m_args.dash_dur > 0) { + pat->m_state = STATE_BEGIN_DASH; } // if there's no on duration or dash duration the led is just disabled - if ((!m_args.on_dur && !m_args.dash_dur) || !m_colorset.numColors()) { - m_state = STATE_DISABLED; + if ((!pat->m_args.on_dur && !pat->m_args.dash_dur) || !colorset_num_colors(&pat->m_colorset)) { + pat->m_state = STATE_DISABLED; } - m_groupCounter = m_args.group_size ? m_args.group_size : (m_colorset.numColors() - (m_args.dash_dur != 0)); + pat->m_groupCounter = pat->m_args.group_size ? pat->m_args.group_size : (colorset_num_colors(&pat->m_colorset) - (pat->m_args.dash_dur != 0)); - if (m_args.blend_speed > 0) { + if (pat->m_args.blend_speed > 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(); - } else if (m_args.fade_dur) { + pat->m_cur = colorset_get_next(&pat->m_colorset); + pat->m_next = colorset_get_next(&pat->m_colorset); + } else if (pat->m_args.fade_dur) { // if there is a fade dur and no blend need to iterate colorset - m_colorset.getNext(); + colorset_get_next(&pat->m_colorset); } // Initialize the fluctuating fade value - m_fadeValue = 0; + pat->m_fadeValue = 0; } -void Pattern::tickFade() +static void pattern_tick_fade(pattern_t *pat) { - uint32_t now = Time::getCurtime(); + uint32_t now = time_get_current_time(); // Calculate relative time since pattern was initialized - uint32_t relativeTime = now - m_fadeStartTime; - uint32_t duration = m_args.fade_dur * 10; + uint32_t relativeTime = now - pat->m_fadeStartTime; + uint32_t duration = pat->m_args.fade_dur * 10; // only tick forward every fade_dur ticks if (!relativeTime || (relativeTime % duration) != 0) { @@ -81,11 +104,11 @@ void Pattern::tickFade() // count the number of steps based on relative time uint32_t steps = relativeTime / duration; - uint32_t range = m_args.off_dur; + uint32_t range = pat->m_args.off_dur; // make sure the range is non-zero if (range == 0) { - m_fadeValue = 0; + pat->m_fadeValue = 0; return; } @@ -93,19 +116,19 @@ void Pattern::tickFade() uint32_t step = steps % double_range; // Triangle wave: up from 0 to range, then down to 0 - m_fadeValue = (step < range) ? step : (double_range - step - 1); + pat->m_fadeValue = (step < range) ? step : (double_range - step - 1); // iterate color when at lowest point if (step == 0) { - m_colorset.getNext(); + colorset_get_next(&pat->m_colorset); } } -void Pattern::play() +void pattern_play(pattern_t *pat) { // tick forward the fade logic each tick - if (isFade()) { - tickFade(); + if (pattern_is_fade(pat)) { + pattern_tick_fade(pat); } // Sometimes the pattern needs to cycle multiple states in a single frame so @@ -113,229 +136,240 @@ void Pattern::play() replay: // its kinda evolving as i go - switch (m_state) { + switch (pat->m_state) { case STATE_DISABLED: return; case STATE_BLINK_ON: - if (m_args.on_dur > 0) { - onBlinkOn(); - --m_groupCounter; + if (pat->m_args.on_dur > 0) { + pattern_on_blink_on(pat); + --pat->m_groupCounter; // When in ON state, use current fading on-time - nextState(m_args.on_dur + m_fadeValue); + pattern_next_state(pat, pat->m_args.on_dur + pat->m_fadeValue); return; } - m_state = STATE_BLINK_OFF; + pat->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_args.gap_dur && !m_args.dash_dur)) { - if (m_args.off_dur > 0) { - onBlinkOff(); - nextState(m_args.off_dur - m_fadeValue); + if (pat->m_groupCounter > 0 || (!pat->m_args.gap_dur && !pat->m_args.dash_dur)) { + if (pat->m_args.off_dur > 0) { + pattern_on_blink_off(pat); + pattern_next_state(pat, pat->m_args.off_dur - pat->m_fadeValue); return; } - if (m_groupCounter > 0 && m_args.on_dur > 0) { - m_state = STATE_BLINK_ON; + if (pat->m_groupCounter > 0 && pat->m_args.on_dur > 0) { + pat->m_state = STATE_BLINK_ON; goto replay; } } - m_state = STATE_BEGIN_GAP; + pat->m_state = STATE_BEGIN_GAP; case STATE_BEGIN_GAP: - m_groupCounter = m_args.group_size ? m_args.group_size : (m_colorset.numColors() - (m_args.dash_dur != 0)); - if (m_args.gap_dur > 0) { - beginGap(); - nextState(m_args.gap_dur); + pat->m_groupCounter = pat->m_args.group_size ? pat->m_args.group_size : (colorset_num_colors(&pat->m_colorset) - (pat->m_args.dash_dur != 0)); + if (pat->m_args.gap_dur > 0) { + pattern_begin_gap(pat); + pattern_next_state(pat, pat->m_args.gap_dur); return; } - m_state = STATE_BEGIN_DASH; + pat->m_state = STATE_BEGIN_DASH; case STATE_BEGIN_DASH: - if (m_args.dash_dur > 0) { - beginDash(); - nextState(m_args.dash_dur); + if (pat->m_args.dash_dur > 0) { + pattern_begin_dash(pat); + pattern_next_state(pat, pat->m_args.dash_dur); return; } - m_state = STATE_BEGIN_GAP2; + pat->m_state = STATE_BEGIN_GAP2; case STATE_BEGIN_GAP2: - if (m_args.dash_dur > 0 && m_args.gap_dur > 0) { - beginGap(); - nextState(m_args.gap_dur); + if (pat->m_args.dash_dur > 0 && pat->m_args.gap_dur > 0) { + pattern_begin_gap(pat); + pattern_next_state(pat, pat->m_args.gap_dur); return; } - m_state = STATE_BLINK_ON; + pat->m_state = STATE_BLINK_ON; goto replay; default: break; } - if (!m_blinkTimer.alarm()) { + if (!timer_alarm(&pat->m_blinkTimer)) { // 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)) { + if (pat->m_state == STATE_IN_GAP2 || (pat->m_state == STATE_OFF && pat->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_args.on_dur ? STATE_BLINK_ON : (m_args.dash_dur ? STATE_BEGIN_DASH : STATE_BEGIN_GAP); - } else if (m_state == STATE_OFF && (!m_groupCounter || m_colorset.numColors() == 1)) { + pat->m_state = pat->m_args.on_dur ? STATE_BLINK_ON : (pat->m_args.dash_dur ? STATE_BEGIN_DASH : STATE_BEGIN_GAP); + } else if (pat->m_state == STATE_OFF && (!pat->m_groupCounter || colorset_num_colors(&pat->m_colorset) == 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; + pat->m_state = (pat->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); + pat->m_state = (enum pattern_state)(pat->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) +void pattern_set_args(pattern_t *pat, const pattern_args_t *args) { - memcpy(&m_args, &args, sizeof(PatternArgs)); + memcpy(&pat->m_args, args, sizeof(pattern_args_t)); } -void Pattern::onBlinkOn() +pattern_args_t pattern_get_args(const pattern_t *pat) { - PRINT_STATE(STATE_ON); - if (isBlend()) { - blendBlinkOn(); + return pat->m_args; +} + +pattern_args_t *pattern_args_ptr(pattern_t *pat) +{ + return &pat->m_args; +} + +static void pattern_on_blink_on(pattern_t *pat) +{ + if (pattern_is_blend(pat)) { + pattern_blend_blink_on(pat); return; } // Check if this is a fading duration pattern - if (isFade()) { - Led::set(m_colorset.cur()); + if (pattern_is_fade(pat)) { + rgb_color_t cur_col = colorset_cur(&pat->m_colorset); + led_set_rgb(&cur_col); return; } + rgb_color_t next_col = colorset_get_next(&pat->m_colorset); + led_set_rgb(&next_col); +} + +static void pattern_on_blink_off(pattern_t *pat) +{ + (void)pat; // unused + led_clear(); +} - Led::set(m_colorset.getNext()); +static void pattern_begin_gap(pattern_t *pat) +{ + (void)pat; // unused + led_clear(); } -void Pattern::onBlinkOff() +static void pattern_begin_dash(pattern_t *pat) { - PRINT_STATE(STATE_OFF); - Led::clear(); + rgb_color_t next_col = colorset_get_next(&pat->m_colorset); + led_set_rgb(&next_col); } -void Pattern::beginGap() +static void pattern_next_state(pattern_t *pat, uint8_t timing) { - PRINT_STATE(STATE_IN_GAP); - Led::clear(); + timer_init(&pat->m_blinkTimer, timing); + pat->m_state = (enum pattern_state)(pat->m_state + 1); } -void Pattern::beginDash() +colorset_t pattern_get_colorset(const pattern_t *pat) { - PRINT_STATE(STATE_IN_DASH); - Led::set(m_colorset.getNext()); + return pat->m_colorset; } -void Pattern::nextState(uint8_t timing) +colorset_t *pattern_colorset_ptr(pattern_t *pat) { - m_blinkTimer.init(timing); - m_state = (PatternState)(m_state + 1); + return &pat->m_colorset; } -// change the colorset -void Pattern::setColorset(const Colorset &set) +void pattern_set_colorset(pattern_t *pat, const colorset_t *set) { - m_colorset = set; - init(); + colorset_copy(&pat->m_colorset, set); + pattern_init_state(pat); } -void Pattern::clearColorset() +void pattern_clear_colorset(pattern_t *pat) { - m_colorset.clear(); + colorset_clear(&pat->m_colorset); } -bool Pattern::equals(const Pattern *other) +uint8_t pattern_equals(const pattern_t *pat, const pattern_t *other) { if (!other) { - return false; + return 0; } // compare the colorset - if (!m_colorset.equals(&other->m_colorset)) { - return false; + if (!colorset_equals(&pat->m_colorset, &other->m_colorset)) { + return 0; } // compare the args of each pattern for equality - if (memcmp(&m_args, &other->m_args, sizeof(PatternArgs)) == 0) { - return false; + if (memcmp(&pat->m_args, &other->m_args, sizeof(pattern_args_t)) != 0) { + return 0; } // if those match then it's effectively the same // pattern even if anything else is different - return true; + return 1; } -void Pattern::updateColor(uint8_t index, const RGBColor &col) +void pattern_update_color(pattern_t *pat, uint8_t index, const rgb_color_t *col) { - m_colorset.set(index, col); - init(); + colorset_set(&pat->m_colorset, index, *col); + pattern_init_state(pat); } -uint32_t Pattern::crc32() const +uint32_t pattern_crc32(const pattern_t *pat) { uint32_t hash = 5381; - for (uint8_t i = 0; i < PATTERN_SIZE; ++i) { - hash = ((hash << 5) + hash) + ((uint8_t *)this)[i]; + uint8_t i; + for (i = 0; i < PATTERN_SIZE; ++i) { + hash = ((hash << 5) + hash) + ((uint8_t *)pat)[i]; } return hash; } -void Pattern::blendBlinkOn() +uint32_t pattern_get_flags(const pattern_t *pat) +{ + return pat->m_patternFlags; +} + +uint8_t pattern_has_flags(const pattern_t *pat, uint32_t flags) +{ + return (pat->m_patternFlags & flags) != 0; +} + +uint8_t pattern_is_blend(const pattern_t *pat) +{ + return pat->m_args.blend_speed > 0; +} + +uint8_t pattern_is_fade(const pattern_t *pat) +{ + return pat->m_args.fade_dur > 0; +} + +static void pattern_blend_blink_on(pattern_t *pat) { // if we reached the next color, then cycle the colorset // like normal and begin playing the next color - if (m_cur == m_next) { - m_next = m_colorset.getNext(); + if (rgb_equals(&pat->m_cur, &pat->m_next)) { + pat->m_next = colorset_get_next(&pat->m_colorset); } // interpolate to the next color - interpolate(m_cur.red, m_next.red); - interpolate(m_cur.green, m_next.green); - interpolate(m_cur.blue, m_next.blue); + pattern_interpolate(&pat->m_cur.red, pat->m_next.red, pat->m_args.blend_speed); + pattern_interpolate(&pat->m_cur.green, pat->m_next.green, pat->m_args.blend_speed); + pattern_interpolate(&pat->m_cur.blue, pat->m_next.blue, pat->m_args.blend_speed); // set the color - Led::set(m_cur); + led_set_rgb(&pat->m_cur); } -void Pattern::interpolate(uint8_t ¤t, const uint8_t next) +static void pattern_interpolate(uint8_t *current, const uint8_t next, uint8_t blend_speed) { - if (current < next) { - uint8_t step = (next - current) > m_args.blend_speed ? m_args.blend_speed : (next - current); - current += step; - } else if (current > next) { - uint8_t step = (current - next) > m_args.blend_speed ? m_args.blend_speed : (current - next); - current -= step; + if (*current < next) { + uint8_t step = (next - *current) > blend_speed ? blend_speed : (next - *current); + *current += step; + } else if (*current > next) { + uint8_t step = (*current - next) > blend_speed ? blend_speed : (*current - next); + *current -= step; } } -// ================================== -// Debug Code -#if DEBUG_BASIC_PATTERN == 1 -#include -void Pattern::printState(PatternState state) -{ - static uint32_t lastPrint = UINT32_MAX; - if (lastPrint == Time::getCurtime()) { - return; - } - switch (m_state) { - case STATE_DISABLED: printf("DIS "); break; - case STATE_BLINK_ON: printf("ON "); break; - case STATE_ON: printf("on "); break; - case STATE_BLINK_OFF: printf("OFF "); break; - case STATE_OFF: printf("off "); break; - case STATE_BEGIN_GAP: printf("GAP1"); break; - case STATE_IN_GAP: printf("gap1"); break; - case STATE_BEGIN_DASH: printf("DASH"); break; - case STATE_IN_DASH: printf("dash"); break; - case STATE_BEGIN_GAP2: printf("GAP2"); break; - case STATE_IN_GAP2: printf("gap2"); break; - default: printf("(%02u)", m_state); break; - } - lastPrint = Time::getCurtime(); -} -#endif diff --git a/Helios/Pattern.h b/Helios/Pattern.h index 4c2216b9..bee17c0f 100644 --- a/Helios/Pattern.h +++ b/Helios/Pattern.h @@ -1,15 +1,21 @@ #ifndef PATTERN_H #define PATTERN_H +#ifdef __cplusplus +extern "C" { +#endif + #include "Colorset.h" #include "Timer.h" #include "Patterns.h" +// Forward declarations +typedef struct pattern_args_t pattern_args_t; +typedef struct pattern_t pattern_t; + // for specifying things like default args -struct PatternArgs { - PatternArgs(uint8_t on = 0, uint8_t off = 0, uint8_t gap = 0, uint8_t dash = 0, uint8_t group = 0, uint8_t blend = 0, uint8_t fade = 0) : - on_dur(on), off_dur(off), gap_dur(gap), dash_dur(dash), group_size(group), blend_speed(blend), fade_dur(fade) {} +struct pattern_args_t { uint8_t on_dur; uint8_t off_dur; uint8_t gap_dur; @@ -19,57 +25,42 @@ struct PatternArgs { uint8_t fade_dur; }; -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 fade = 0); - Pattern(const PatternArgs &args); - ~Pattern(); - - // init the pattern to initial state - void init(); +// Initialize pattern args with all parameters +void pattern_args_init(pattern_args_t *args, uint8_t on, uint8_t off, uint8_t gap, + uint8_t dash, uint8_t group, uint8_t blend, uint8_t fade); - // play the pattern - void play(); - - // set/get args - void setArgs(const PatternArgs &args); - const PatternArgs getArgs() const { return m_args; } - PatternArgs getArgs() { return m_args; } - PatternArgs &args() { return m_args; } - - // 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(); - - // comparison to other pattern - bool equals(const Pattern *other); +// The various different blinking states the pattern can be in +enum pattern_state +{ + // the led is disabled (there is no on or dash) + STATE_DISABLED, - // set a color in the colorset and re-initialize - void updateColor(uint8_t index, const RGBColor &col); + // the pattern is blinking on the next color in the set + STATE_BLINK_ON, + STATE_ON, - // calculate crc of the colorset + pattern - uint32_t crc32() const; + // the pattern is blinking off + STATE_BLINK_OFF, + STATE_OFF, - // get the pattern flags - uint32_t getFlags() const { return m_patternFlags; } - bool hasFlags(uint32_t flags) const { return (m_patternFlags & flags) != 0; } + // the pattern is starting a gap after a colorset + STATE_BEGIN_GAP, + STATE_IN_GAP, - // whether blend speed is non 0 - bool isBlend() const { return m_args.blend_speed > 0; } + // the pattern is beginning a dash after a colorset or gap + STATE_BEGIN_DASH, + STATE_IN_DASH, - // whether fade speed is non 0 - bool isFade() const { return m_args.fade_dur > 0; } + // the pattern is starting a gap after a dash + STATE_BEGIN_GAP2, + STATE_IN_GAP2, +}; -protected: +struct pattern_t +{ // ================================== // Pattern Parameters - PatternArgs m_args; + pattern_args_t m_args; // ================================== // Pattern Members @@ -77,62 +68,24 @@ class Pattern // any flags the pattern has uint8_t m_patternFlags; // a copy of the colorset that this pattern is initialized with - Colorset m_colorset; + colorset_t 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; + enum pattern_state m_state; // the blink timer used to measure blink timings - Timer m_blinkTimer; + helios_timer_t m_blinkTimer; // ================================== // Blend Members // current color and target blend color - RGBColor m_cur; - RGBColor m_next; - - // apis for blend - void blendBlinkOn(); - void interpolate(uint8_t ¤t, const uint8_t next); + rgb_color_t m_cur; + rgb_color_t m_next; // ================================== // Fade Members @@ -140,25 +93,53 @@ class Pattern // shifting value to represent current fade uint8_t m_fadeValue; - // apis for fade - void tickFade(); - - // ================================== - // Debug Code - - // if debug basic pattern is enabled -#if DEBUG_BASIC_PATTERN == 1 - // define a function for printing out state info - void printState(PatternState state); - // and define a macro that wraps it - #define PRINT_STATE(state) printState(state) -#else - // otherwise if debug is off the macro does nothing - #define PRINT_STATE(state) -#endif - - // Add a new member variable to store when the pattern was last initialized + // Add a member variable to store when the pattern was last initialized uint32_t m_fadeStartTime; }; +// try to not set on duration to 0 +void pattern_init(pattern_t *pat, uint8_t onDur, uint8_t offDur, uint8_t gap, + uint8_t dash, uint8_t group, uint8_t blend, uint8_t fade); +void pattern_init_with_args(pattern_t *pat, const pattern_args_t *args); + +// init the pattern to initial state +void pattern_init_state(pattern_t *pat); + +// play the pattern +void pattern_play(pattern_t *pat); + +// set/get args +void pattern_set_args(pattern_t *pat, const pattern_args_t *args); +pattern_args_t pattern_get_args(const pattern_t *pat); +pattern_args_t *pattern_args_ptr(pattern_t *pat); + +// change the colorset +colorset_t pattern_get_colorset(const pattern_t *pat); +colorset_t *pattern_colorset_ptr(pattern_t *pat); +void pattern_set_colorset(pattern_t *pat, const colorset_t *set); +void pattern_clear_colorset(pattern_t *pat); + +// comparison to other pattern +uint8_t pattern_equals(const pattern_t *pat, const pattern_t *other); + +// set a color in the colorset and re-initialize +void pattern_update_color(pattern_t *pat, uint8_t index, const rgb_color_t *col); + +// calculate crc of the colorset + pattern +uint32_t pattern_crc32(const pattern_t *pat); + +// get the pattern flags +uint32_t pattern_get_flags(const pattern_t *pat); +uint8_t pattern_has_flags(const pattern_t *pat, uint32_t flags); + +// whether blend speed is non 0 +uint8_t pattern_is_blend(const pattern_t *pat); + +// whether fade speed is non 0 +uint8_t pattern_is_fade(const pattern_t *pat); + +#ifdef __cplusplus +} +#endif + #endif diff --git a/Helios/Patterns.cpp b/Helios/Patterns.cpp index a4b69e02..80399183 100644 --- a/Helios/Patterns.cpp +++ b/Helios/Patterns.cpp @@ -1,81 +1,86 @@ -#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_GREEN, RGB_BLUE}; // Rainbow Flow - -// 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[] = { - { 3, color_codes0 }, // 0 Rainbow Flow -}; - -void Patterns::make_default(uint8_t index, Pattern &pat) -{ - if (index >= NUM_MODE_SLOTS) { - return; - } - PatternArgs args; - switch (index) { - case 0: // Rainbow Flow - args.on_dur = 3; - args.off_dur = 23; - 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_STROBE: - args.on_dur = 5; - args.off_dur = 8; - break; - - case PATTERN_HYPNOSTROBE: - args.on_dur = 14; - args.off_dur = 10; - break; - - case PATTERN_STROBIE: - args.on_dur = 3; - args.off_dur = 23; - break; - - case PATTERN_RAZOR: - args.on_dur = 3; - args.off_dur = 1; - args.gap_dur = 30; // 29 for flashing pattern circles - break; - - case PATTERN_DASH_DOPS: - args.on_dur = 1; - args.off_dur = 9; - args.gap_dur = 6; - args.dash_dur = 15; - break; - - } - - pat.setArgs(args); -} +#include "Patterns.h" + +#include "Storage.h" +#include "Pattern.h" +#include "ColorConstants.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_GREEN, RGB_BLUE}; // Nyx Default + +// Define Colorset configurations for each slot +struct default_colorset_t { + 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 struct default_colorset_t default_colorsets[] = { + { 3, color_codes0 }, // 0 Nyx Default +}; + +void patterns_make_default(uint8_t index, pattern_t *pat) +{ + if (index >= NUM_MODE_SLOTS) { + return; + } + pattern_args_t args; + pattern_args_init(&args, 0, 0, 0, 0, 0, 0, 0); + switch (index) { + case 0: // Nyx Default + args.on_dur = 3; + args.off_dur = 23; + break; + } + // assign default args + pattern_set_args(pat, &args); + // build the set out of the defaults + colorset_t set; + colorset_init_array(&set, default_colorsets[index].num_cols, default_colorsets[index].cols); + // assign default colorset + pattern_set_colorset(pat, &set); +} + +void patterns_make_pattern(enum pattern_id id, pattern_t *pat) +{ + pattern_args_t args; + pattern_args_init(&args, 0, 0, 0, 0, 0, 0, 0); + switch (id) + { + default: + + case PATTERN_STROBE: + args.on_dur = 5; + args.off_dur = 8; + break; + + case PATTERN_HYPNOSTROBE: + args.on_dur = 14; + args.off_dur = 10; + break; + + case PATTERN_STROBIE: + args.on_dur = 3; + args.off_dur = 23; + break; + + case PATTERN_RAZOR: + args.on_dur = 3; + args.off_dur = 1; + args.gap_dur = 30; + break; + + case PATTERN_DASH_DOPS: + args.on_dur = 1; + args.off_dur = 9; + args.gap_dur = 6; + args.dash_dur = 15; + break; + + } + + pattern_set_args(pat, &args); +} + diff --git a/Helios/Patterns.h b/Helios/Patterns.h index 9c0a63c8..6cd74bb5 100644 --- a/Helios/Patterns.h +++ b/Helios/Patterns.h @@ -1,17 +1,19 @@ #ifndef PATTERNS_H #define PATTERNS_H +#ifdef __cplusplus +extern "C" { +#endif + #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, +// Forward declaration +typedef struct pattern_t pattern_t; + +// +enum pattern_id { + // + PATTERN_NONE = -1, // first pattern of all PATTERN_FIRST = 0, @@ -24,19 +26,17 @@ enum PatternID : int8_t { PATTERN_RAZOR, PATTERN_DASH_DOPS, - - // Meta pattern constants INTERNAL_PATTERNS_END, PATTERN_LAST = (INTERNAL_PATTERNS_END - 1), - PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, // total number of patterns + PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, // }; -class Pattern; +void patterns_make_default(uint8_t index, pattern_t *pat); +void patterns_make_pattern(enum pattern_id id, pattern_t *pat); + +#ifdef __cplusplus +} +#endif -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 index fb10f201..a4c90905 100644 --- a/Helios/Random.cpp +++ b/Helios/Random.cpp @@ -1,45 +1,43 @@ #include "Random.h" -Random::Random() : - m_seed(0) +void random_init(random_t *rng) { + rng->m_seed = 0; } -Random::Random(uint32_t newseed) : - Random() +void random_init_seed(random_t *rng, uint32_t newseed) { - seed(newseed); + random_init(rng); + random_seed(rng, newseed); } -Random::~Random() -{ -} - -void Random::seed(uint32_t newseed) +void random_seed(random_t *rng, uint32_t newseed) { if (!newseed) { - m_seed = 42; + rng->m_seed = 42; + } else { + rng->m_seed = newseed; } - m_seed = newseed; } -uint16_t Random::next16(uint16_t minValue, uint16_t maxValue) +uint16_t random_next16(random_t *rng, uint16_t minValue, uint16_t maxValue) { // walk the LCG forward to the next step - m_seed = (m_seed * 1103515245 + 12345) & 0x7FFFFFFF; + rng->m_seed = (rng->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 ((rng->m_seed >> 16) % (range + 1)) + minValue; } - return (m_seed >> 16); + return (rng->m_seed >> 16); } -uint8_t Random::next8(uint8_t minValue, uint8_t maxValue) +uint8_t random_next8(random_t *rng, uint8_t minValue, uint8_t maxValue) { - uint32_t result = next16(minValue, maxValue); - return static_cast(result); + uint32_t result = random_next16(rng, minValue, maxValue); + return (uint8_t)result; } + diff --git a/Helios/Random.h b/Helios/Random.h index 1f556f42..bef01d0c 100644 --- a/Helios/Random.h +++ b/Helios/Random.h @@ -1,20 +1,36 @@ -#pragma once +#ifndef RANDOM_H +#define RANDOM_H + +#ifdef __cplusplus +extern "C" { +#endif #include -class Random +typedef struct random_t random_t; + +struct random_t { -public: - Random(); - Random(uint32_t newseed); - ~Random(); + uint32_t m_seed; +}; - void seed(uint32_t newseed); +// Initialize a random struct with default seed +void random_init(random_t *rng); - uint8_t next8(uint8_t minValue = 0, uint8_t maxValue = 0xFF); - uint16_t next16(uint16_t minValue = 0, uint16_t maxValue = 0xFFFF); +// Initialize a random struct with a specific seed +void random_init_seed(random_t *rng, uint32_t newseed); -private: - uint32_t m_seed; -}; +// Set the seed for the random number generator +void random_seed(random_t *rng, uint32_t newseed); + +// Generate next random 8-bit value within range [minValue, maxValue] +uint8_t random_next8(random_t *rng, uint8_t minValue, uint8_t maxValue); + +// Generate next random 16-bit value within range [minValue, maxValue] +uint16_t random_next16(random_t *rng, uint16_t minValue, uint16_t maxValue); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/Helios/Storage.cpp b/Helios/Storage.cpp index af401f99..c3e0d636 100644 --- a/Helios/Storage.cpp +++ b/Helios/Storage.cpp @@ -15,16 +15,29 @@ #include #endif +// Forward declarations for internal functions +static uint8_t storage_crc_pos(uint8_t pos); +static uint8_t storage_read_crc(uint8_t pos); +static uint8_t storage_check_crc(uint8_t pos); +static void storage_write_crc(uint8_t pos); +static void storage_write_byte(uint8_t address, uint8_t data); +static uint8_t storage_read_byte(uint8_t address); + +#ifdef HELIOS_EMBEDDED +static inline uint8_t storage_internal_read(uint8_t address); +static inline void storage_internal_write(uint8_t address, uint8_t data); +#endif + #ifdef HELIOS_CLI // whether storage is enabled, default enabled -bool Storage::m_enableStorage = true; +static uint8_t m_enableStorage = 1; #endif -bool Storage::init() +uint8_t storage_init(void) { #ifdef HELIOS_CLI if (!m_enableStorage) { - return true; + return 1; } // if the storage filename doesn't exist then create it if (access(STORAGE_FILENAME, O_RDWR) != 0 && errno == ENOENT) { @@ -32,107 +45,142 @@ bool Storage::init() FILE *f = fopen(STORAGE_FILENAME, "w+b"); if (!f) { perror("Error creating storage file for write"); - return false; + return 0; } // fill the storage with 0s - for (uint32_t i = 0; i < STORAGE_SIZE; ++i){ + uint32_t i; + for (i = 0; i < STORAGE_SIZE; ++i){ uint8_t b = 0x0; fwrite(&b, 1, sizeof(uint8_t), f); } fclose(f); } #endif - return true; + return 1; } -bool Storage::read_pattern(uint8_t slot, Pattern &pat) +uint8_t storage_read_pattern(uint8_t slot, pattern_t *pat) { uint8_t pos = slot * SLOT_SIZE; - if (!check_crc(pos)) { - return false; + if (!storage_check_crc(pos)) { + return 0; } - for (uint8_t i = 0; i < PATTERN_SIZE; ++i) { - ((uint8_t *)&pat)[i] = read_byte(pos + i); + uint8_t i; + for (i = 0; i < PATTERN_SIZE; ++i) { + ((uint8_t *)pat)[i] = storage_read_byte(pos + i); } - return true; + return 1; } -void Storage::write_pattern(uint8_t slot, const Pattern &pat) +void storage_write_pattern(uint8_t slot, const pattern_t *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 i; + for (i = 0; i < PATTERN_SIZE; ++i) { + uint8_t val = ((uint8_t *)pat)[i]; uint8_t target = pos + i; - write_byte(target, val); + storage_write_byte(target, val); } - write_crc(pos); + storage_write_crc(pos); } -void Storage::copy_slot(uint8_t srcSlot, uint8_t dstSlot) +void storage_copy_slot(uint8_t srcSlot, uint8_t dstSlot) { uint8_t src = srcSlot * SLOT_SIZE; uint8_t dst = dstSlot * SLOT_SIZE; - for (uint8_t i = 0; i < SLOT_SIZE; ++i) { - write_byte(dst + i, read_byte(src + i)); + uint8_t i; + for (i = 0; i < SLOT_SIZE; ++i) { + storage_write_byte(dst + i, storage_read_byte(src + i)); } } -uint8_t Storage::read_config(uint8_t index) +uint8_t storage_read_config(uint8_t index) +{ + return storage_read_byte(CONFIG_START_INDEX - index); +} + +void storage_write_config(uint8_t index, uint8_t val) +{ + storage_write_byte(CONFIG_START_INDEX - index, val); +} + +uint8_t storage_read_global_flags(void) +{ + return storage_read_config(STORAGE_GLOBAL_FLAG_INDEX); +} + +void storage_write_global_flags(uint8_t global_flags) +{ + storage_write_config(STORAGE_GLOBAL_FLAG_INDEX, global_flags); +} + +uint8_t storage_read_current_mode(void) +{ + return storage_read_config(STORAGE_CURRENT_MODE_INDEX); +} + +void storage_write_current_mode(uint8_t current_mode) +{ + storage_write_config(STORAGE_CURRENT_MODE_INDEX, current_mode); +} + +uint8_t storage_read_brightness(void) { - return read_byte(CONFIG_START_INDEX - index); + return storage_read_config(STORAGE_BRIGHTNESS_INDEX); } -void Storage::write_config(uint8_t index, uint8_t val) +void storage_write_brightness(uint8_t brightness) { - write_byte(CONFIG_START_INDEX - index, val); + storage_write_config(STORAGE_BRIGHTNESS_INDEX, brightness); } -uint8_t Storage::crc8(uint8_t pos, uint8_t size) +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); + uint8_t i; + for (i = 0; i < size; ++i) { + hash = ((hash << 5) + hash) + storage_read_byte(pos); } return hash; } -uint8_t Storage::crc_pos(uint8_t pos) +static uint8_t storage_crc_pos(uint8_t pos) { // crc the entire slot except last byte - return crc8(pos, PATTERN_SIZE); + return storage_crc8(pos, PATTERN_SIZE); } -uint8_t Storage::read_crc(uint8_t pos) +static uint8_t storage_read_crc(uint8_t pos) { // read the last byte of the slot - return read_byte(pos + PATTERN_SIZE); + return storage_read_byte(pos + PATTERN_SIZE); } -bool Storage::check_crc(uint8_t pos) +static uint8_t storage_check_crc(uint8_t pos) { // compare the last byte to the calculated crc - return (read_crc(pos) == crc_pos(pos)); + return (storage_read_crc(pos) == storage_crc_pos(pos)); } -void Storage::write_crc(uint8_t pos) +static void storage_write_crc(uint8_t pos) { // compare the last byte to the calculated crc - write_byte(pos + PATTERN_SIZE, crc_pos(pos)); + storage_write_byte(pos + PATTERN_SIZE, storage_crc_pos(pos)); } -void Storage::write_byte(uint8_t address, uint8_t data) +static void storage_write_byte(uint8_t address, uint8_t data) { #ifdef HELIOS_EMBEDDED // 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 (read_byte(address) == data) { + if (storage_read_byte(address) == data) { return; } - internal_write(address, data); + storage_internal_write(address, data); // double check that shit - if (read_byte(address) != data) { + if (storage_read_byte(address) != data) { // do it again because eeprom is stupid - internal_write(address, data); + storage_internal_write(address, data); // god forbid it doesn't write again } #else // HELIOS_CLI @@ -151,22 +199,23 @@ void Storage::write_byte(uint8_t address, uint8_t data) return; } if (!fwrite((const void *)&data, sizeof(uint8_t), 1, f)) { + fclose(f); return; } fclose(f); // Close the file #endif } -uint8_t Storage::read_byte(uint8_t address) +static uint8_t storage_read_byte(uint8_t address) { #ifdef HELIOS_EMBEDDED // do a three way read because the attiny85 eeprom basically doesn't work - uint8_t b1 = internal_read(address); - uint8_t b2 = internal_read(address); + uint8_t b1 = storage_internal_read(address); + uint8_t b2 = storage_internal_read(address); if (b1 == b2) { return b2; } - uint8_t b3 = internal_read(address); + uint8_t b3 = storage_internal_read(address); if (b3 == b1) { return b1; } @@ -184,8 +233,8 @@ uint8_t Storage::read_byte(uint8_t address) } FILE *f = fopen(STORAGE_FILENAME, "rb"); // Open file for reading in binary mode if (!f) { - // this error is ok, just means no storage - //perror("Error opening file for read"); + // this error is ok, just means no storage + // perror("Error opening file for read"); return val; } // Seek to the specified address @@ -205,7 +254,7 @@ uint8_t Storage::read_byte(uint8_t address) } #ifdef HELIOS_EMBEDDED -inline void Storage::internal_write(uint8_t address, uint8_t data) +static inline void storage_internal_write(uint8_t address, uint8_t data) { while (EECR & (1< #include "HeliosConfig.h" @@ -16,55 +20,37 @@ #define STORAGE_CURRENT_MODE_INDEX 1 #define STORAGE_BRIGHTNESS_INDEX 2 -class Pattern; - -class Storage -{ -public: +// Forward declaration +typedef struct pattern_t pattern_t; - static bool init(); +uint8_t storage_init(void); - static bool read_pattern(uint8_t slot, Pattern &pat); - static void write_pattern(uint8_t slot, const Pattern &pat); +uint8_t storage_read_pattern(uint8_t slot, pattern_t *pat); +void storage_write_pattern(uint8_t slot, const pattern_t *pat); - static void copy_slot(uint8_t srcSlot, uint8_t dstSlot); +void storage_copy_slot(uint8_t srcSlot, uint8_t dstSlot); - static uint8_t read_config(uint8_t index); - static void write_config(uint8_t index, uint8_t val); +uint8_t storage_read_config(uint8_t index); +void storage_write_config(uint8_t index, uint8_t val); - static uint8_t read_global_flags() { return read_config(STORAGE_GLOBAL_FLAG_INDEX); } - static void write_global_flags(uint8_t global_flags) { write_config(STORAGE_GLOBAL_FLAG_INDEX, global_flags); } +uint8_t storage_read_global_flags(void); +void storage_write_global_flags(uint8_t global_flags); - static uint8_t read_current_mode() { return read_config(STORAGE_CURRENT_MODE_INDEX); } - static void write_current_mode(uint8_t current_mode) { write_config(STORAGE_CURRENT_MODE_INDEX, current_mode); } +uint8_t storage_read_current_mode(void); +void storage_write_current_mode(uint8_t current_mode); - static uint8_t read_brightness() { return read_config(STORAGE_BRIGHTNESS_INDEX); } - static void write_brightness(uint8_t brightness) { write_config(STORAGE_BRIGHTNESS_INDEX, brightness); } +uint8_t storage_read_brightness(void); +void storage_write_brightness(uint8_t brightness); - static uint8_t crc8(uint8_t pos, uint8_t size); +uint8_t storage_crc8(uint8_t pos, uint8_t size); #ifdef HELIOS_CLI - // toggle storage on/off - static void enableStorage(bool enabled) { m_enableStorage = enabled; } -#endif -private: - 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_EMBEDDED - static inline uint8_t internal_read(uint8_t address); - static inline void internal_write(uint8_t address, uint8_t data); +// toggle storage on/off +void storage_enable_storage(uint8_t enabled); #endif -#ifdef HELIOS_CLI - // whether storage is enabled - static bool m_enableStorage; +#ifdef __cplusplus +} #endif -}; #endif diff --git a/Helios/TimeControl.cpp b/Helios/TimeControl.cpp index 904be2a3..ff33a894 100644 --- a/Helios/TimeControl.cpp +++ b/Helios/TimeControl.cpp @@ -1,183 +1,201 @@ -#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; - } -#endif - - // 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(); -} - -#ifdef HELIOS_EMBEDDED -volatile uint32_t timer0_overflow_count = 0; -ISR(TIMER0_OVF_vect) { - timer0_overflow_count++; // Increment on each overflow -} -#endif - -uint32_t Time::microseconds() -{ -#ifdef HELIOS_CLI - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &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 - // The only reason that micros() is actually necessary is if Helios::tick() - // cannot be called in a 1Khz ISR. If Helios::tick() cannot be reliably called - // by an interrupt then Time::tickClock() must perform manual timestep via micros(). - // If Helios::tick() is called by an interrupt then you don't need this function and - // should always just rely on the current tick to perform operations - uint8_t oldSREG = SREG; - cli(); - // multiply by 8 early to avoid floating point math or division - uint32_t micros = (timer0_overflow_count * (256 * 8)) + (TCNT0 * 8); - SREG = oldSREG; - // then shift right to counteract the multiplication by 8 - return micros >> 6; -#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 -} +// Enable POSIX features for clock_gettime, usleep, etc. +#ifdef HELIOS_CLI +#define _POSIX_C_SOURCE 200112L +#endif + +#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 +// convert seconds and nanoseconds to microseconds +#define SEC_TO_US(sec) ((sec)*1000000) +#define NS_TO_US(ns) ((ns)/1000) +#endif + +// static members +static uint32_t m_curTick = 0; +// the last frame timestamp +static uint32_t m_prevTime = 0; + +#ifdef HELIOS_CLI +// whether timestep is enabled, default enabled +static uint8_t m_enableTimestep = 1; +#endif + +uint8_t time_init(void) +{ + m_prevTime = time_microseconds(); + m_curTick = 0; + return 1; +} + +void time_cleanup(void) +{ +} + +void time_tick_clock(void) +{ + // tick clock forward + m_curTick++; + +#ifdef HELIOS_CLI + if (!m_enableTimestep) { + return; + } +#endif + + // 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 = time_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 = time_microseconds(); +} + +uint32_t time_get_current_time(void) +{ + return m_curTick; +} + +#ifdef HELIOS_EMBEDDED +volatile uint32_t timer0_overflow_count = 0; +ISR(TIMER0_OVF_vect) { + timer0_overflow_count++; // Increment on each overflow +} +#endif + +uint32_t time_microseconds(void) +{ +#ifdef HELIOS_CLI + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &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 + // The only reason that micros() is actually necessary is if Helios::tick() + // cannot be called in a 1Khz ISR. If Helios::tick() cannot be reliably called + // by an interrupt then Time::tickClock() must perform manual timestep via micros(). + // If Helios::tick() is called by an interrupt then you don't need this function and + // should always just rely on the current tick to perform operations + uint8_t oldSREG = SREG; + cli(); + // multiply by 8 early to avoid floating point math or division + uint32_t micros = (timer0_overflow_count * (256 * 8)) + (TCNT0 * 8); + SREG = oldSREG; + // then shift right to counteract the multiplication by 8 + return micros >> 6; +#endif +#endif +} + +#ifdef HELIOS_EMBEDDED +__attribute__((noinline)) +#endif +void +time_delay_microseconds(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 = time_microseconds() + us; + while (time_microseconds() < newtime) + { + // busy loop + } +#endif +} + +void time_delay_milliseconds(uint32_t ms) +{ +#ifdef HELIOS_CLI + usleep(ms * 1000); +#else + // not very accurate + uint16_t i; + for (i = 0; i < ms; ++i) { + time_delay_microseconds(1000); + } +#endif +} + +#ifdef HELIOS_CLI +void time_enable_timestep(uint8_t enabled) +{ + m_enableTimestep = enabled; +} +#endif + diff --git a/Helios/TimeControl.h b/Helios/TimeControl.h index b5f316d8..587bddb8 100644 --- a/Helios/TimeControl.h +++ b/Helios/TimeControl.h @@ -1,6 +1,10 @@ #ifndef TIME_CONTROL_H #define TIME_CONTROL_H +#ifdef __cplusplus +extern "C" { +#endif + #include #include "HeliosConfig.h" @@ -9,51 +13,35 @@ #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(); +// Initialize time system +uint8_t time_init(void); +void time_cleanup(void); - // tick the clock forward to millis() - static void tickClock(); +// Tick the clock forward to millis() +void time_tick_clock(void); - // 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; } +// Get the current tick, offset by any active simulation (simulation only exists in vortexlib) +// Exposing this as inline or macro 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 repetitive calls +uint32_t time_get_current_time(void); - // 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(); +// 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 time_get_current_time() 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 time_get_current_time() +uint32_t time_microseconds(void); - // delay for some number of microseconds or milliseconds, these are bad - static void delayMicroseconds(uint32_t us); - static void delayMilliseconds(uint32_t ms); +// Delay for some number of microseconds or milliseconds, these are bad +void time_delay_microseconds(uint32_t us); +void time_delay_milliseconds(uint32_t ms); #ifdef HELIOS_CLI - // toggle timestep on/off - static void enableTimestep(bool enabled) { m_enableTimestep = enabled; } +// Toggle timestep on/off +void time_enable_timestep(uint8_t 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; +#ifdef __cplusplus +} #endif -}; #endif - diff --git a/Helios/Timer.cpp b/Helios/Timer.cpp index 48dd685b..1f623387 100644 --- a/Helios/Timer.cpp +++ b/Helios/Timer.cpp @@ -4,56 +4,53 @@ #include "TimeControl.h" -Timer::Timer() : - m_alarm(0), - m_startTime(0) +void timer_init_default(helios_timer_t *timer) { + timer->m_alarm = 0; + timer->m_startTime = 0; } -Timer::~Timer() +void timer_init(helios_timer_t *timer, uint8_t alarm) { + timer_reset(timer); + timer->m_alarm = alarm; + timer_start(timer, 0); } -void Timer::init(uint8_t alarm) -{ - reset(); - m_alarm = alarm; - start(); -} - -void Timer::start(uint32_t offset) +void timer_start(helios_timer_t *timer, uint32_t offset) { // reset the start time - m_startTime = Time::getCurtime() + offset; + timer->m_startTime = time_get_current_time() + offset; } -void Timer::reset() +void timer_reset(helios_timer_t *timer) { - m_alarm = 0; - m_startTime = 0; + timer->m_alarm = 0; + timer->m_startTime = 0; } -bool Timer::alarm() +uint8_t timer_alarm(helios_timer_t *timer) { - if (!m_alarm) { - return false; + if (!timer->m_alarm) { + return 0; } - uint32_t now = Time::getCurtime(); + uint32_t now = time_get_current_time(); // time since start (forward or backwards) - int32_t timeDiff = (int32_t)(int64_t)(now - m_startTime); + int32_t timeDiff = (int32_t)(int64_t)(now - timer->m_startTime); if (timeDiff < 0) { - return false; + return 0; } // if no time passed it's first alarm that is starting if (timeDiff == 0) { - return true; + return 1; } // if the current alarm duration is not a multiple of the current tick - if (m_alarm && (timeDiff % m_alarm) != 0) { + if (timer->m_alarm && (timeDiff % timer->m_alarm) != 0) { // then the alarm was not hit - return false; + return 0; } // update the start time of the timer - m_startTime = now; - return true; + timer->m_startTime = now; + return 1; } + diff --git a/Helios/Timer.h b/Helios/Timer.h index 92a19a52..f3c2e1f0 100644 --- a/Helios/Timer.h +++ b/Helios/Timer.h @@ -3,28 +3,38 @@ #include -class Timer +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct helios_timer_t helios_timer_t; + +struct helios_timer_t { -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; }; +// Initialize a timer struct to default values +void timer_init_default(helios_timer_t *timer); + +// Init a timer with a number of alarms and optionally start it +void timer_init(helios_timer_t *timer, 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 timer_start(helios_timer_t *timer, uint32_t offset); + +// Delete all alarms from the timer and reset +void timer_reset(helios_timer_t *timer); + +// Will return true if the timer hit +uint8_t timer_alarm(helios_timer_t *timer); + +#ifdef __cplusplus +} +#endif + #endif diff --git a/HeliosCLI/cli_main.cpp b/HeliosCLI/cli_main.cpp index 0dd4386e..22d98734 100644 --- a/HeliosCLI/cli_main.cpp +++ b/HeliosCLI/cli_main.cpp @@ -22,6 +22,8 @@ #include "Colortypes.h" #include "Button.h" #include "Led.h" +#include "Patterns.h" +#include "Pattern.h" #include "color_map.h" /* @@ -53,7 +55,7 @@ bool timestep = true; bool eeprom = false; std::string eeprom_file; bool generate_bmp = false; -std::vector colorBuffer; +std::vector colorBuffer; uint32_t num_cycles = 0; float brightness_scale = 1.0f; uint8_t minumum_brightness = 75; @@ -71,7 +73,7 @@ static bool read_inputs(); static void show(); static void restore_terminal(); static void set_terminal_nonblocking(); -static bool writeBMP(const std::string& filename, const std::vector& colors); +static bool writeBMP(const std::string& filename, const std::vector& colors); static void print_usage(const char* program_name); static bool parse_eep_file(const std::string& filename, std::vector& memory); static bool parse_csv_hex(const std::string& filename, std::vector& memory); @@ -91,29 +93,30 @@ int main(int argc, char *argv[]) return 0; } // toggle timestep in the engine based on the cli input - Time::enableTimestep(timestep); + time_enable_timestep(timestep); // toggle storage in the engine based on cli input - Storage::enableStorage(storage); + storage_enable_storage(storage); // run the engine initialization - Helios::init(); + helios_init(); // set the initial mode index - Helios::set_mode_index(initial_mode_index); + helios_set_mode_index(initial_mode_index); // Set the initial pattern based on user arguments if (initial_pattern_str.length() > 0) { // convert the string arg to integer, then treat it as a PatternID - PatternID id = (PatternID)strtoul(initial_pattern_str.c_str(), NULL, 10); + pattern_id id = (pattern_id)strtoul(initial_pattern_str.c_str(), NULL, 10); // pass the current pattern to make_pattern to update it's internals - Patterns::make_pattern(id, Helios::cur_pattern()); + pattern_t* pat = helios_cur_pattern(); + patterns_make_pattern(id, pat); // re-initialize the current pattern - Helios::cur_pattern().init(); + pattern_init_state(pat); } // set initial pattern args based on user arguments if (initial_pattern_args_str.length() > 0) { // parse the list of args into an array of ints std::vector vals; std::istringstream ss(initial_pattern_args_str); - // push 6 args into the array - while (vals.size() < 6) { + // push 7 args into the array (on_dur, off_dur, gap_dur, dash_dur, group_size, blend_speed, fade_dur) + while (vals.size() < 7) { std::string arg; uint32_t val = 0; // try to parse out a number @@ -124,28 +127,40 @@ int main(int argc, char *argv[]) vals.push_back(val); } // construct pattern args from the array of values - PatternArgs args(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]); + pattern_args_t args; + pattern_args_init(&args, vals[0], vals[1], vals[2], vals[3], vals[4], vals[5], vals[6]); // set the args of the current pattern - Helios::cur_pattern().setArgs(args); + pattern_t* pat = helios_cur_pattern(); + pattern_set_args(pat, &args); } // Set the initial colorset based on user arguments if (initial_colorset_str.length() > 0) { std::stringstream ss(initial_colorset_str); std::string color; - Colorset set; + colorset_t set; + colorset_init(&set); while (getline(ss, color, ',')) { // iterate letters and lowercase them std::transform(color.begin(), color.end(), color.begin(), [](unsigned char c){ return tolower(c); }); + rgb_color_t rgb; if (color_map.count(color) > 0) { - set.addColor(color_map[color]); + uint32_t color_val = color_map[color]; + rgb.red = (color_val >> 16) & 0xFF; + rgb.green = (color_val >> 8) & 0xFF; + rgb.blue = color_val & 0xFF; } else { - set.addColor(strtoul(color.c_str(), nullptr, 16)); + uint32_t color_val = strtoul(color.c_str(), nullptr, 16); + rgb.red = (color_val >> 16) & 0xFF; + rgb.green = (color_val >> 8) & 0xFF; + rgb.blue = color_val & 0xFF; } + colorset_add_color(&set, rgb); } // update the colorset of the current pattern - Helios::cur_pattern().setColorset(set); + pattern_t* pat = helios_cur_pattern(); + pattern_set_colorset(pat, &set); // re-initialize the current pattern - Helios::cur_pattern().init(); + pattern_init_state(pat); } // just generate eeprom? if (eeprom) { @@ -155,19 +170,19 @@ int main(int argc, char *argv[]) // so that we can detect when one full cycle of the pattern has passed uint32_t cycle_count = 0; uint8_t last_index = 0; - while (Helios::keep_going()) { + while (helios_keep_going()) { // check for any inputs and read the next one read_inputs(); // if lockstep is enabled, only run logic if the // input queue isn't actually empty - if (lockstep && !Button::inputQueueSize()) { + if (lockstep && !button_input_queue_size()) { // just keep waiting for an input continue; } // run the main loop - Helios::tick(); + helios_tick(); // don't render anything if asleep, but technically it's still running... - if (Helios::is_asleep()) { + if (helios_is_asleep()) { continue; } // watch for a full cycle if it was requested by the command line @@ -175,7 +190,8 @@ int main(int argc, char *argv[]) // grab the current index of the colorset, which might be the same for // several tick in a row, so we must check whether it just changed this // tick by comparing it to the index we saved last tick - uint8_t cur_index = Helios::cur_pattern().colorset().curIndex(); + pattern_t* pat = helios_cur_pattern(); + uint8_t cur_index = pat->m_colorset.m_curIndex; if (cur_index == 0 && last_index != 0) { // only if the current index is 0 (start of colorset) and the last index was // not 0 then the colorset *just* started iterating through it's colors, so @@ -184,7 +200,7 @@ int main(int argc, char *argv[]) } // then if we run more than the chosen number of cycles just quit if (cycle_count >= num_cycles) { - Helios::terminate(); + helios_terminate(); break; } last_index = cur_index; @@ -389,7 +405,7 @@ static bool read_inputs() } for (uint32_t i = 0; i < repeatAmount; ++i) { // otherwise just queue up the command - Button::queueInput(command); + button_queue_input(command); } } return true; @@ -402,8 +418,9 @@ static void show() if (generate_bmp) { // still need to generate the BMP by recoring all the output colors // even if they have chosen the -q for quiet option - RGBColor currentColor = {Led::get().red, Led::get().green, Led::get().blue}; - RGBColor scaledColor = currentColor.scaleBrightness(brightness_scale); + rgb_color_t currentColor = led_get(); + rgb_color_t scaledColor = currentColor; + rgb_scale_brightness(&scaledColor, brightness_scale); colorBuffer.push_back(scaledColor); } return; @@ -414,8 +431,9 @@ static void show() out += "\r"; } // Get the current color and scale its brightness up - RGBColor currentColor = {Led::get().red, Led::get().green, Led::get().blue}; - RGBColor scaledColor = currentColor.scaleBrightness(brightness_scale); + rgb_color_t currentColor = led_get(); + rgb_color_t scaledColor = currentColor; + rgb_scale_brightness(&scaledColor, brightness_scale); if (output_type == OUTPUT_TYPE_COLOR) { out += "\x1B[0m["; // opening | out += "\x1B[48;2;"; // colorcode start @@ -473,7 +491,7 @@ static void set_terminal_nonblocking() atexit(restore_terminal); } -bool writeBMP(const std::string& filename, const std::vector& colors) +bool writeBMP(const std::string& filename, const std::vector& colors) { if (colors.empty()) { std::cerr << "Invalid image dimensions or empty color array." << std::endl; @@ -525,7 +543,7 @@ bool writeBMP(const std::string& filename, const std::vector& colors) // write out data for (int32_t y = height - 1; y >= 0; --y) { for (int32_t x = 0; x < width; ++x) { - const RGBColor& color = colors[y * width + x]; + const rgb_color_t& color = colors[y * width + x]; // BGR format const unsigned char pixel[3] = { color.blue, color.green, color.red }; file.write((const char *)pixel, 3); @@ -717,27 +735,27 @@ static void dump_eeprom(const std::string& filename) for (size_t slot = 0; slot < NUM_MODE_SLOTS; ++slot) { size_t pos = slot * SLOT_SIZE; - Pattern pat; - memcpy((void*)&pat, &memory[pos], sizeof(Pattern)); + pattern_t pat; + memcpy((void*)&pat, &memory[pos], sizeof(pattern_t)); printf("Slot %zu:\n", slot); printf(" Colorset: "); - for (size_t i = 0; i < pat.getColorset().numColors(); ++i) { - RGBColor color = pat.getColorset()[i]; + for (size_t i = 0; i < pat.m_colorset.m_numColors; ++i) { + rgb_color_t color = colorset_get(&pat.m_colorset, i); char hexCode[8]; snprintf(hexCode, sizeof(hexCode), "#%02X%02X%02X", color.red, color.green, color.blue); printf("\033[48;2;%d;%d;%dm \033[0m (%s) ", color.red, color.green, color.blue, hexCode); } printf("\n"); - PatternArgs args = pat.getArgs(); + pattern_args_t args = pat.m_args; printf(" Args: on_dur=%d, off_dur=%d, gap_dur=%d, dash_dur=%d, group_size=%d, blend_speed=%d\n", args.on_dur, args.off_dur, args.gap_dur, args.dash_dur, args.group_size, args.blend_speed); - printf(" Flags: %02X\n", pat.getFlags()); + printf(" Flags: %02X\n", pat.m_patternFlags); } uint8_t flags = (uint8_t)memory[CONFIG_START_INDEX - STORAGE_GLOBAL_FLAG_INDEX]; - bool locked = (flags & Helios::FLAG_LOCKED) != 0; + bool locked = (flags & FLAG_LOCKED) != 0; uint8_t modeIdx = (uint8_t)memory[CONFIG_START_INDEX - STORAGE_CURRENT_MODE_INDEX]; uint8_t brightness = (uint8_t)memory[CONFIG_START_INDEX - STORAGE_BRIGHTNESS_INDEX]; diff --git a/HeliosEmbedded/Makefile b/HeliosEmbedded/Makefile index d6e47a98..974ad3b5 100644 --- a/HeliosEmbedded/Makefile +++ b/HeliosEmbedded/Makefile @@ -46,8 +46,8 @@ endif ### TOOLCHAIN SETUP ### ####################### -CC = ${BINDIR}avr-g++ -LD = ${BINDIR}avr-g++ +CC = ${BINDIR}avr-gcc +LD = ${BINDIR}avr-gcc OBJCOPY = ${BINDIR}avr-objcopy -v AR = ${BINDIR}avr-gcc-ar SIZE = ${BINDIR}avr-size @@ -117,15 +117,12 @@ CFLAGS = -g \ -Wall \ -flto \ -mrelax \ - -std=gnu++17 \ -fshort-enums \ -fpack-struct \ - -fno-exceptions \ -fdata-sections \ -funsigned-char \ -ffunction-sections\ -funsigned-bitfields \ - -fno-threadsafe-statics \ -mcall-prologues \ -D__AVR_ATtiny85__ \ -mmcu=$(AVRDUDE_CHIP) \ diff --git a/HeliosEmbedded/main.cpp b/HeliosEmbedded/main.cpp index 391fdf1b..d537ca01 100644 --- a/HeliosEmbedded/main.cpp +++ b/HeliosEmbedded/main.cpp @@ -4,14 +4,18 @@ #include #if !defined(HELIOS_CLI) && !defined(HELIOS_ARDUINO) -// this is the main thread for non-arduino embedded builds +/* this is the main thread for non-arduino embedded builds */ int main(int argc, char *argv[]) { - Helios::init(); - // the main thread just initializes Helios then continuously calls tick - while (Helios::keep_going()) { - Helios::tick(); + (void)argc; /* unused */ + (void)argv; /* unused */ + + helios_init(); + /* the main thread just initializes Helios then continuously calls tick */ + while (helios_keep_going()) { + helios_tick(); } return 0; } #endif +