diff --git a/lib/ProtoTracer/Examples/Protogen/ProtogenHUB75Project.h b/lib/ProtoTracer/Examples/Protogen/ProtogenHUB75Project.h index cc46a4ff..234095a0 100644 --- a/lib/ProtoTracer/Examples/Protogen/ProtogenHUB75Project.h +++ b/lib/ProtoTracer/Examples/Protogen/ProtogenHUB75Project.h @@ -99,26 +99,11 @@ class ProtogenHUB75Project : public ProtogenProject { controller.SetBrightness(Menu::GetBrightness()); controller.SetAccentBrightness(Menu::GetAccentBrightness()); - if (IsBooped() && mode != 6){ - Surprised(); - } - else{ - if (mode == 0) Default(); - else if (mode == 1) Angry(); - else if (mode == 2) Doubt(); - else if (mode == 3) Frown(); - else if (mode == 4) LookUp(); - else if (mode == 5) Sad(); - else if (mode == 6) { - AudioReactiveGradientFace(); - } - else if (mode == 7){ - OscilloscopeFace(); - } - else { - SpectrumAnalyzerFace(); - } - } +#ifdef MORSEBUTTON + SelectFaceFromMorse(mode); +#else + SelectFace(mode); +#endif UpdateFace(ratio); @@ -129,4 +114,44 @@ class ProtogenHUB75Project : public ProtogenProject { pM.GetObject()->GetTransform()->SetPosition(GetWiggleOffset()); pM.GetObject()->UpdateTransform(); } + + void SelectFace(uint8_t code) { + if (IsBooped() && code != 6) { + Surprised(); + return; + } + + switch(code) { + case 0: Default(); break; + case 1: Angry(); break; + case 2: Doubt(); break; + case 3: Frown(); break; + case 4: LookUp(); break; + case 5: Sad(); break; + case 6: AudioReactiveGradientFace(); break; + case 7: OscilloscopeFace(); break; + default: SpectrumAnalyzerFace(); break; + } + } + + void SelectFaceFromMorse(uint8_t code) { + if (IsBooped() && code != 24) { + Surprised(); + return; + } + + switch(code) { + case 1: Angry(); break; // [A]ngry + case 2: Surprised(); break; // [B]lush + case 4: Doubt(); break; // [D]oubt + case 6: Frown(); break; // [F]rown + case 19: Sad(); break; // [S]ad + case 21: LookUp(); break; // Look [U]p + case 22: LookDown(); break; // Look [V] Down + case 24: AudioReactiveGradientFace(); break; // [X] X.X + case 25: OscilloscopeFace(); break; // [Y] Oscilloscope + case 26: SpectrumAnalyzerFace(); break; // [Z] Spectrum + default: Default(); break; // [H] Happy + } + } }; diff --git a/lib/ProtoTracer/Examples/UserConfiguration.h b/lib/ProtoTracer/Examples/UserConfiguration.h index 3c75313b..dc8d7412 100644 --- a/lib/ProtoTracer/Examples/UserConfiguration.h +++ b/lib/ProtoTracer/Examples/UserConfiguration.h @@ -3,4 +3,5 @@ #define PRINTINFO //Prints live stats, FPS,etc //#define DEBUG //Prints debug information //#define TESTHARDWARE //Tests for default hardware on the protocontroller, apds9960, ssd1306, neotrellis -//#define NEOTRELLISMENU //Enables the NeoTrellis controller, otherwise uses the button controller \ No newline at end of file +//#define NEOTRELLISMENU //Enables the NeoTrellis controller, otherwise uses the button controller +//#define MORSEBUTTON // Enables inputting morse code into the button, cannot be used at the same time as NEOTRELLISMENU \ No newline at end of file diff --git a/lib/ProtoTracer/ExternalDevices/InputDevices/Menu/Menu.cpp b/lib/ProtoTracer/ExternalDevices/InputDevices/Menu/Menu.cpp index b0d22f67..a53d2b44 100644 --- a/lib/ProtoTracer/ExternalDevices/InputDevices/Menu/Menu.cpp +++ b/lib/ProtoTracer/ExternalDevices/InputDevices/Menu/Menu.cpp @@ -61,7 +61,11 @@ Overflow Menu::overflow = Overflow(20); void Menu::SetMaxEntries() { // Define your max entries here +#ifdef MORSEBUTTON + MenuHandler::SetMenuMax(Faces, 27); // 1 unset + 26 letters +#else MenuHandler::SetMenuMax(Faces, faceCount); +#endif MenuHandler::SetMenuMax(Bright, 10); MenuHandler::SetMenuMax(AccentBright, 10); MenuHandler::SetMenuMax(Microphone, 2); @@ -194,7 +198,7 @@ void Menu::SetCurrentMenu(uint8_t currentMenu) { } void Menu::Update(float ratio) { -#ifdef NEOTRELLISMENU +#if defined NEOTRELLISMENU || defined MORSEBUTTON MenuHandler::Update(); #endif diff --git a/lib/ProtoTracer/ExternalDevices/InputDevices/Menu/Menu.h b/lib/ProtoTracer/ExternalDevices/InputDevices/Menu/Menu.h index 4c8b1051..45bb0eb4 100644 --- a/lib/ProtoTracer/ExternalDevices/InputDevices/Menu/Menu.h +++ b/lib/ProtoTracer/ExternalDevices/InputDevices/Menu/Menu.h @@ -22,6 +22,8 @@ #ifdef NEOTRELLISMENU #include "..\NeoTrellisMenuHandler.h" +#elif defined MORSEBUTTON +#include "..\SingleButtonMorseHandler.h" #else #include "..\SingleButtonMenuHandler.h" #endif diff --git a/lib/ProtoTracer/ExternalDevices/InputDevices/SingleButtonMorseHandler.h b/lib/ProtoTracer/ExternalDevices/InputDevices/SingleButtonMorseHandler.h new file mode 100644 index 00000000..ed0e5efd --- /dev/null +++ b/lib/ProtoTracer/ExternalDevices/InputDevices/SingleButtonMorseHandler.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +template +class MenuHandler { +private: + static bool previousState; + static long previousMillis; + static uint8_t inputCount; + static uint16_t inputStream[4]; + static uint8_t currentMenu; + static uint8_t currentValue[menuCount]; + static uint8_t maxValue[menuCount]; + static uint8_t pin; + + static uint8_t ReadEEPROM(uint16_t index); + static void WriteEEPROM(uint16_t index, uint8_t value); + +public: + static void Begin(); + static bool Initialize(uint8_t pin, uint16_t holdingTime); + static void SetDefaultValue(uint16_t menu, uint8_t value); + static void SetInitialized(); + static void SetMenuMax(uint8_t menu, uint8_t maxValue); + static void Update(); + static uint8_t GetMenuValue(uint8_t menu); + static uint8_t GetCurrentMenu(); +}; + +#include "SingleButtonMorseHandler.tpp" \ No newline at end of file diff --git a/lib/ProtoTracer/ExternalDevices/InputDevices/SingleButtonMorseHandler.tpp b/lib/ProtoTracer/ExternalDevices/InputDevices/SingleButtonMorseHandler.tpp new file mode 100644 index 00000000..e0d909a3 --- /dev/null +++ b/lib/ProtoTracer/ExternalDevices/InputDevices/SingleButtonMorseHandler.tpp @@ -0,0 +1,183 @@ +#pragma once + +template +bool MenuHandler::previousState; + +template +long MenuHandler::previousMillis; + +template +uint8_t MenuHandler::inputCount; + +template +uint8_t MenuHandler::currentMenu; + +template +uint16_t MenuHandler::inputStream[4]; + +template +uint8_t MenuHandler::currentValue[menuCount]; + +template +uint8_t MenuHandler::maxValue[menuCount]; + +template +uint8_t MenuHandler::pin; + +template +void MenuHandler::Begin() { + previousMillis = millis(); + previousState = false; +} + +template +void MenuHandler::Update() { + bool pinState = !digitalRead(pin); + long timeOn = millis() - previousMillis; + + if (timeOn < 40) return; + + if (!pinState && inputCount > 0 && timeOn > 500) { + uint32_t mask = 0; + if (inputStream[0] > 0) mask |= (inputStream[0] < 150 ? 1 : 2) << 0; // 1 2 + if (inputStream[1] > 0) mask |= (inputStream[1] < 150 ? 1 : 2) << 2; // 4 8 + if (inputStream[2] > 0) mask |= (inputStream[2] < 150 ? 1 : 2) << 4; // 16 32 + if (inputStream[3] > 0) mask |= (inputStream[3] < 150 ? 1 : 2) << 6; // 64 128 + + uint8_t val; + switch (mask) { + case 9: val = 1; break; // A: .- + case 86: val = 2; break; // B: -... + case 102: val = 3; break; // C: -.-. + case 22: val = 4; break; // D: -.. + case 1: val = 5; break; // E: . + case 101: val = 6; break; // F: ..-. + case 26: val = 7; break; // G: --. + case 85: val = 8; break; // H: .... + case 5: val = 9; break; // I: .. + case 169: val = 10; break; // J: .--- + case 38: val = 11; break; // K: -.- + case 89: val = 12; break; // L: .-.. + case 10: val = 13; break; // M: -- + case 6: val = 14; break; // N: -. + case 42: val = 15; break; // O: --- + case 105: val = 16; break; // P: .--. + case 154: val = 17; break; // Q: --.- + case 25: val = 18; break; // R: .-. + case 21: val = 19; break; // S: ... + case 2: val = 20; break; // T: - + case 37: val = 21; break; // U: ..- + case 149: val = 22; break; // V: ...- + case 41: val = 23; break; // W: .-- + case 150: val = 24; break; // X: -..- + case 166: val = 25; break; // Y: -.-- + case 90: val = 26; break; // Z: --.. + default: val = 0; break; + } + + if (val == 20) { + // T (long press) is used to advance menu, to keep it similar to default menu navigation + WriteEEPROM(currentMenu, currentValue[currentMenu]); + currentMenu = (currentMenu + 1) % menuCount; + } else if (currentMenu > 0) { + // When in the menu + switch (val) { + case 5: + // E (short press) is used to advance selection in non-face menu, to keep it similar to default menu navigation + val = currentValue[currentMenu] + 1; + if (maxValue[currentMenu] > 0) val = val % maxValue[currentMenu]; + currentValue[currentMenu] = val; + break; + case 9: + // I (2 short presses) is used to go back a selection in non-face menu + val = currentValue[currentMenu] - 1; + if (maxValue[currentMenu] < 0) val += maxValue[currentMenu]; + currentValue[currentMenu] = val; + break; + case 13: + // M (2 long presses) is used to go back a menu + WriteEEPROM(currentMenu, currentValue[currentMenu]); + currentMenu = (currentMenu - 1) % menuCount; + break; + case 15: + // O (3 long presses) is used to exit the menu + WriteEEPROM(currentMenu, currentValue[currentMenu]); + currentMenu = 0; + break; + } + } else { + if (maxValue[currentMenu] > 0) val = val % maxValue[currentMenu]; // If max is set, wrap value + currentValue[currentMenu] = val; + } + + std::fill_n(inputStream, inputCount, 0); + inputCount = 0; + previousState = false; + return; + } + + if (pinState == previousState) return; + + if (!pinState) { + inputStream[inputCount] = timeOn; + inputCount += 1; + } + + previousMillis = millis(); + previousState = pinState; +} + +template +uint8_t MenuHandler::ReadEEPROM(uint16_t index) { + return EEPROM.read(index); +} + +template +void MenuHandler::WriteEEPROM(uint16_t index, uint8_t value) { + EEPROM.write(index, value); +} + +template +bool MenuHandler::Initialize(uint8_t pin, uint16_t _holdingTime) { + MenuHandler::pin = pin; + MenuHandler::previousState = false; + + pinMode(pin, INPUT_PULLUP); + + for (uint8_t i = 0; i < menuCount; i++) { + currentValue[i] = ReadEEPROM(i); + } + + return ReadEEPROM(menuCount + 1) != 255; +} + +template +void MenuHandler::SetDefaultValue(uint16_t menu, uint8_t value) { + if (menu >= menuCount) return; + + currentValue[menu] = value; + + WriteEEPROM(menu, value); +} + +template +void MenuHandler::SetInitialized() { + WriteEEPROM(menuCount + 1, 0); +} + +template +void MenuHandler::SetMenuMax(uint8_t menu, uint8_t maxValue) { + if (menu >= menuCount) return; + + MenuHandler::maxValue[menu] = maxValue; +} + +template +uint8_t MenuHandler::GetMenuValue(uint8_t menu) { + return currentValue[menu]; +} + +template +uint8_t MenuHandler::GetCurrentMenu() { + return currentMenu; +}