diff --git a/.github/workflows/core_build.yml b/.github/workflows/core_build.yml deleted file mode 100644 index 2b1404b02b..0000000000 --- a/.github/workflows/core_build.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: Core Build - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - workflow_dispatch: # manual trigger - -jobs: - setup: - runs-on: ubuntu-latest - outputs: - vortex_version_major: ${{ steps.set_version.outputs.vortex_version_major }} - vortex_version_minor: ${{ steps.set_version.outputs.vortex_version_minor }} - vortex_build_number: ${{ steps.set_version.outputs.vortex_build_number }} - vortex_version_number: ${{ steps.set_version.outputs.vortex_version_number }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetches all history for all branches and tags - - name: Determine Version and Build Number - id: set_version - run: | - # Fetch all tags - git fetch --depth=1 origin +refs/tags/*:refs/tags/* - # Get the latest tag that matches the branch suffix - LATEST_TAG=$(git tag --list | grep -E "^[[:digit:]]+\.[[:digit:]]+\$" | sort -V | tail -n1) - if [ -z "$LATEST_TAG" ]; then - echo "No matching tags found. Setting default version." - VERSION_MAJOR="0" - VERSION_MINOR="1" - BUILD_NUMBER="0" - else - echo "Found latest tag: $LATEST_TAG" - VERSION_MAJOR=$(echo $LATEST_TAG | cut -d. -f1) - VERSION_MINOR=$(echo $LATEST_TAG | cut -d. -f2) - BUILD_NUMBER=$(git rev-list --count $LATEST_TAG..HEAD) - fi - FULL_VERSION="$VERSION_MAJOR.$VERSION_MINOR.$BUILD_NUMBER" - echo "vortex_version_major=$VERSION_MAJOR" >> $GITHUB_OUTPUT - echo "vortex_version_minor=$VERSION_MINOR" >> $GITHUB_OUTPUT - echo "vortex_build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT - echo "vortex_version_number=$FULL_VERSION" >> $GITHUB_OUTPUT - echo "Version Number: $FULL_VERSION" - - test: - needs: setup - runs-on: ubuntu-latest - steps: - - name: Checkout current repository - uses: actions/checkout@v3 - - name: Update Package Lists - run: sudo apt-get update - - name: Install Dependencies - run: sudo apt-get install valgrind g++ make --fix-missing - - name: Build - run: | - export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }} - export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }} - export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} - export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} - make -j - working-directory: VortexEngine - - name: Set execute permissions for test script - run: chmod +x ./runtests.sh - working-directory: VortexEngine/tests - - name: Run general tests - run: ./runtests.sh --general - working-directory: VortexEngine/tests - - wasm: - needs: [setup, test] - runs-on: ubuntu-latest - steps: - - name: Checkout current repository - uses: actions/checkout@v3 - - name: Update Package Lists - run: sudo apt-get update - - name: Install Emscripten - run: | - sudo apt install -y cmake python3 - git clone https://github.com/emscripten-core/emsdk.git - cd emsdk - ./emsdk install latest - ./emsdk activate latest - working-directory: VortexEngine/VortexLib - - name: Build Webassembly - run: | - source ./emsdk/emsdk_env.sh - export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }} - export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }} - export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} - export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} - make -j wasm - working-directory: VortexEngine/VortexLib - - docs: - needs: [setup, test, wasm] - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/master' - steps: - - name: Checkout current repository - uses: actions/checkout@v3 - - name: Update Package Lists - run: sudo apt-get update - - name: Install Dependencies - run: sudo apt-get install doxygen graphviz texlive --fix-missing - - name: Checkout doxygen-awesome - run: git clone https://github.com/jothepro/doxygen-awesome-css.git doxygen-awesome-css - - name: Generate Documentation - run: | - mkdir -p docs/core - doxygen Doxyfile - echo "Listing contents of docs/core:" - ls -R docs/core || echo "No files found in docs/core" - - name: Upload Doxygen Documentation as Artifact - uses: actions/upload-artifact@v3 - with: - name: doxygen-docs-core - path: docs/core diff --git a/.github/workflows/orbit_build.yml b/.github/workflows/orbit_build.yml new file mode 100644 index 0000000000..6a196b6b63 --- /dev/null +++ b/.github/workflows/orbit_build.yml @@ -0,0 +1,183 @@ +name: Orbit Build + +on: + push: + branches: [ "orbit" ] + pull_request: + branches: [ "orbit" ] + workflow_dispatch: # manual trigger + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + vortex_version_major: ${{ steps.set_version.outputs.vortex_version_major }} + vortex_version_minor: ${{ steps.set_version.outputs.vortex_version_minor }} + vortex_build_number: ${{ steps.set_version.outputs.vortex_build_number }} + vortex_version_number: ${{ steps.set_version.outputs.vortex_version_number }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetches all history for all branches and tags + - name: Determine Version and Build Number + id: set_version + run: | + BRANCH_SUFFIX="o" + # Fetch all tags + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + # Get the latest tag that matches the branch suffix + LATEST_TAG=$(git tag --list "*${BRANCH_SUFFIX}" | sort -V | tail -n1) + if [ -z "$LATEST_TAG" ]; then + echo "No matching tags found. Setting default version." + VERSION_MAJOR="0" + VERSION_MINOR="1" + BUILD_NUMBER="0" + else + echo "Found latest tag: $LATEST_TAG" + VERSION_NUMBER=$(echo $LATEST_TAG | sed "s/${BRANCH_SUFFIX}//g") + VERSION_MAJOR=$(echo $VERSION_NUMBER | cut -d. -f1) + VERSION_MINOR=$(echo $VERSION_NUMBER | cut -d. -f2) + BUILD_NUMBER=$(git rev-list --count $LATEST_TAG..HEAD) + fi + FULL_VERSION="$VERSION_MAJOR.$VERSION_MINOR.$BUILD_NUMBER" + echo "vortex_version_major=$VERSION_MAJOR" >> $GITHUB_OUTPUT + echo "vortex_version_minor=$VERSION_MINOR" >> $GITHUB_OUTPUT + echo "vortex_build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT + echo "vortex_version_number=$FULL_VERSION" >> $GITHUB_OUTPUT + echo "Version Number: $FULL_VERSION" + + test: + needs: setup + runs-on: ubuntu-latest + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + - name: Update Package Lists + run: sudo apt-get update + - name: Install Dependencies + run: sudo apt-get install valgrind g++ make --fix-missing + - name: Build + run: | + export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }} + export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }} + export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} + export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} + make -j + working-directory: VortexEngine + - name: Set execute permissions for test script + run: chmod +x ./runtests.sh + working-directory: VortexEngine/tests + - name: Run general tests + run: ./runtests.sh --general + working-directory: VortexEngine/tests + + embedded: + needs: [setup, test] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install Dependencies + run: make install + - name: Build Binary + run: | + export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }} + export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }} + export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} + export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} + make build + - name: Archive production artifacts + uses: actions/upload-artifact@v4 + with: + name: embedded firmware + path: | + build/VortexEngine.ino.bin + build/VortexEngine.ino.elf + build/VortexEngine.ino.map + build/VortexEngine.ino.hex + build/VortexEngine.ino.uf2 + - name: Archive production artifacts for deployment + uses: actions/upload-artifact@v4 + with: + name: firmware-artifact + path: build/VortexEngine.ino.uf2 + + wasm: + needs: [setup, test, embedded] + runs-on: ubuntu-latest + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + - name: Update Package Lists + run: sudo apt-get update + - name: Install Emscripten + run: | + sudo apt install -y cmake python3 + git clone https://github.com/emscripten-core/emsdk.git + cd emsdk + ./emsdk install latest + ./emsdk activate latest + working-directory: VortexEngine/VortexLib + - name: Build Webassembly + run: | + source ./emsdk/emsdk_env.sh + export VORTEX_VERSION_MAJOR=${{ needs.setup.outputs.vortex_version_major }} + export VORTEX_VERSION_MINOR=${{ needs.setup.outputs.vortex_version_minor }} + export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} + export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} + make -j wasm + working-directory: VortexEngine/VortexLib + + docs: + needs: [setup, test, embedded, wasm] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/orbit' + steps: + - name: Checkout current repository + uses: actions/checkout@v4 + - name: Update Package Lists + run: sudo apt-get update + - name: Install Dependencies + run: sudo apt-get install doxygen graphviz texlive --fix-missing + - name: Checkout doxygen-awesome + run: git clone https://github.com/jothepro/doxygen-awesome-css.git doxygen-awesome-css + - name: Generate Documentation + run: | + mkdir -p docs/orbit + doxygen Doxyfile + echo "Listing contents of docs/orbit:" + ls -R docs/orbit || echo "No files found in docs/orbit" + - name: Upload Doxygen Documentation as Artifact + uses: actions/upload-artifact@v3 + with: + name: doxygen-docs-orbit + path: docs/orbit + + deploy: + needs: [setup, test, embedded, wasm, docs] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/orbit' + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: firmware-artifact + path: build + - name: Rename and Deploy Firmware + run: | + DEVICE_TYPE="orbit" + VERSIONED_FILENAME="VortexEngine-${DEVICE_TYPE}-${{ needs.setup.outputs.vortex_version_number }}.uf2" + mv build/VortexEngine.ino.uf2 build/$VERSIONED_FILENAME + echo "Version is ${{ needs.setup.outputs.vortex_version_number }}" + echo "Filename is is $VERSIONED_FILENAME" + curl -X POST \ + -F "file=@build/$VERSIONED_FILENAME" \ + -F "device=$DEVICE_TYPE" \ + -F "version=${{ needs.setup.outputs.vortex_version_number }}" \ + -F "category=firmware" \ + -F "clientApiKey=${{ secrets.VORTEX_COMMUNITY_API_KEY }}" \ + https://vortex.community/firmware/upload + diff --git a/Doxyfile b/Doxyfile index 9db6ecc800..670539be0c 100644 --- a/Doxyfile +++ b/Doxyfile @@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "Vortex Engine" +PROJECT_NAME = "Vortex Orbit" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = docs/core +OUTPUT_DIRECTORY = docs/orbit # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..b97579d506 --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ +.PHONY: all install build upload clean compute_version + +ARDUINO_CLI = ./bin/arduino-cli --verbose +BOARD = adafruit:samd:adafruit_trinket_m0 +PORT = /dev/ttyACM0 +PROJECT_NAME = VortexEngine/VortexEngine.ino +BUILD_PATH = build +CONFIG_FILE = $(HOME)/.arduino15/arduino-cli.yaml + +# The branch/tag suffix for this device +BRANCH_SUFFIX=o + +DEFINES=\ + -D VORTEX_VERSION_MAJOR=$(VORTEX_VERSION_MAJOR) \ + -D VORTEX_VERSION_MINOR=$(VORTEX_VERSION_MINOR) \ + -D VORTEX_BUILD_NUMBER=$(VORTEX_BUILD_NUMBER) \ + -D VORTEX_VERSION_NUMBER=$(VORTEX_VERSION_NUMBER) + +# Default target +all: build + +update-index: + $(ARDUINO_CLI) core update-index + +install: + sudo apt-get update + sudo apt-get install -y build-essential + mkdir -p $(HOME)/.arduino15 + if ! command -v $(ARDUINO_CLI) &> /dev/null ; then \ + curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sudo sh ; \ + fi + echo 'board_manager: \n additional_urls: \n - https://adafruit.github.io/arduino-board-index/package_adafruit_index.json' | sudo tee $(CONFIG_FILE) + $(ARDUINO_CLI) core update-index --config-file $(CONFIG_FILE) + if ! $(ARDUINO_CLI) core list --config-file $(CONFIG_FILE) | grep -q '$(BOARD)' ; then \ + $(ARDUINO_CLI) core install adafruit:samd --config-file $(CONFIG_FILE) ; \ + fi + wget https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2conv.py + wget https://raw.githubusercontent.com/microsoft/uf2/master/utils/uf2families.json + chmod +x uf2conv.py uf2families.json + chmod +x rewrite_trinket_source.sh + ./rewrite_trinket_source.sh + +build: compute_version + $(ARDUINO_CLI) compile --fqbn $(BOARD) $(PROJECT_NAME) \ + --config-file $(CONFIG_FILE) \ + --build-path $(BUILD_PATH) \ + --build-property compiler.cpp.extra_flags="$(DEFINES)" \ + --build-property compiler.c.extra_flags="$(DEFINES)" + python3 uf2conv.py -c -b 0x2000 build/VortexEngine.ino.bin -o build/VortexEngine.ino.uf2 + @echo "== Success building Orbit v$(VORTEX_VERSION_NUMBER) ==" + +upload: + $(ARDUINO_CLI) upload -p $(PORT) --fqbn $(BOARD) $(PROJECT_NAME) --config-file $(CONFIG_FILE) + +core-list: + $(ARDUINO_CLI) core list + +clean: + rm -rf $(BUILD_PATH) + +# calculate the version number of the build +compute_version: + $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list "*$(BRANCH_SUFFIX)" | sort -V | tail -n1)) + $(eval VORTEX_VERSION_MAJOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f1)) + $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | sed 's/$(BRANCH_SUFFIX)$$//' | cut -d. -f2)) + $(eval VORTEX_BUILD_NUMBER ?= $(shell git rev-list --count $(LATEST_TAG)..HEAD)) + $(eval VORTEX_VERSION_MAJOR := $(if $(VORTEX_VERSION_MAJOR),$(VORTEX_VERSION_MAJOR),0)) + $(eval VORTEX_VERSION_MINOR := $(if $(VORTEX_VERSION_MINOR),$(VORTEX_VERSION_MINOR),1)) + $(eval VORTEX_BUILD_NUMBER := $(if $(VORTEX_BUILD_NUMBER),$(VORTEX_BUILD_NUMBER),0)) + $(eval VORTEX_VERSION_NUMBER := $(VORTEX_VERSION_MAJOR).$(VORTEX_VERSION_MINOR).$(VORTEX_BUILD_NUMBER)) diff --git a/VortexEngine/VortexCLI/Makefile b/VortexEngine/VortexCLI/Makefile index b4b2f2b541..71570f298a 100644 --- a/VortexEngine/VortexCLI/Makefile +++ b/VortexEngine/VortexCLI/Makefile @@ -19,6 +19,9 @@ RANLIB=ranlib CFLAGS=-O2 -g -Wall +# The branch/tag suffix for this device +BRANCH_SUFFIX=o + # compiler defines DEFINES=\ -D VORTEX_LIB \ @@ -135,9 +138,9 @@ clean: # calculate the version number of the build compute_version: - $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list | grep --invert-match '[a-zA-Z]' | sort -V | tail -n1)) + $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list "*$(BRANCH_SUFFIX)" | sort -V | tail -n1)) $(eval VORTEX_VERSION_MAJOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f1)) - $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f2)) + $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | sed 's/$(BRANCH_SUFFIX)$$//' | cut -d. -f2)) $(eval VORTEX_BUILD_NUMBER ?= $(shell git rev-list --count $(LATEST_TAG)..HEAD)) $(eval VORTEX_VERSION_MAJOR := $(if $(VORTEX_VERSION_MAJOR),$(VORTEX_VERSION_MAJOR),0)) $(eval VORTEX_VERSION_MINOR := $(if $(VORTEX_VERSION_MINOR),$(VORTEX_VERSION_MINOR),1)) diff --git a/VortexEngine/VortexEngine.ino b/VortexEngine/VortexEngine.ino new file mode 100644 index 0000000000..014b37a8af --- /dev/null +++ b/VortexEngine/VortexEngine.ino @@ -0,0 +1,15 @@ +#include + +#include "src/VortexEngine.h" + +void setup() +{ + if (!VortexEngine::init()) { + // uhoh + } +} + +void loop() +{ + VortexEngine::tick(); +} diff --git a/VortexEngine/VortexLib/Makefile b/VortexEngine/VortexLib/Makefile index 98900f08b8..affef838b5 100644 --- a/VortexEngine/VortexLib/Makefile +++ b/VortexEngine/VortexLib/Makefile @@ -24,6 +24,9 @@ ifndef WASM CFLAGS += -g endif +# The branch/tag suffix for this device +BRANCH_SUFFIX=o + # compiler defines DEFINES=\ -D VORTEX_LIB \ @@ -146,9 +149,9 @@ clean: # calculate the version number of the build compute_version: - $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list | grep --invert-match '[a-zA-Z]' | sort -V | tail -n1)) + $(eval LATEST_TAG ?= $(shell git fetch --depth=1 origin +refs/tags/*:refs/tags/* &> /dev/null && git tag --list "*$(BRANCH_SUFFIX)" | sort -V | tail -n1)) $(eval VORTEX_VERSION_MAJOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f1)) - $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | cut -d. -f2)) + $(eval VORTEX_VERSION_MINOR ?= $(shell echo $(LATEST_TAG) | sed 's/$(BRANCH_SUFFIX)$$//' | cut -d. -f2)) $(eval VORTEX_BUILD_NUMBER ?= $(shell git rev-list --count $(LATEST_TAG)..HEAD)) $(eval VORTEX_VERSION_MAJOR := $(if $(VORTEX_VERSION_MAJOR),$(VORTEX_VERSION_MAJOR),0)) $(eval VORTEX_VERSION_MINOR := $(if $(VORTEX_VERSION_MINOR),$(VORTEX_VERSION_MINOR),1)) diff --git a/VortexEngine/VortexLib/VortexLib.cpp b/VortexEngine/VortexLib/VortexLib.cpp index 09e5e61c77..5bb25921ba 100644 --- a/VortexEngine/VortexLib/VortexLib.cpp +++ b/VortexEngine/VortexLib/VortexLib.cpp @@ -219,6 +219,24 @@ EMSCRIPTEN_BINDINGS(Vortex) { .value("LED_7", LedPos::LED_7) .value("LED_8", LedPos::LED_8) .value("LED_9", LedPos::LED_9) + .value("LED_10", LedPos::LED_10) + .value("LED_11", LedPos::LED_11) + .value("LED_12", LedPos::LED_12) + .value("LED_13", LedPos::LED_13) + .value("LED_14", LedPos::LED_14) + .value("LED_15", LedPos::LED_15) + .value("LED_16", LedPos::LED_16) + .value("LED_17", LedPos::LED_17) + .value("LED_18", LedPos::LED_18) + .value("LED_19", LedPos::LED_19) + .value("LED_20", LedPos::LED_20) + .value("LED_21", LedPos::LED_21) + .value("LED_22", LedPos::LED_22) + .value("LED_23", LedPos::LED_23) + .value("LED_24", LedPos::LED_24) + .value("LED_25", LedPos::LED_25) + .value("LED_26", LedPos::LED_26) + .value("LED_27", LedPos::LED_27) .value("LED_COUNT", LedPos::LED_COUNT) .value("LED_LAST", LedPos::LED_LAST) .value("LED_ALL", LedPos::LED_ALL) diff --git a/VortexEngine/appmain.cpp b/VortexEngine/appmain.cpp deleted file mode 100644 index a80363ed5b..0000000000 --- a/VortexEngine/appmain.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "src/VortexEngine.h" - -int main() -{ - VortexEngine::init(); - for (;;) { - VortexEngine::tick(); - } - return 0; -} diff --git a/VortexEngine/src/Buttons/Button.cpp b/VortexEngine/src/Buttons/Button.cpp index e565dbbb5c..51466ff02d 100644 --- a/VortexEngine/src/Buttons/Button.cpp +++ b/VortexEngine/src/Buttons/Button.cpp @@ -8,6 +8,10 @@ #include "VortexLib.h" #endif +#ifdef VORTEX_EMBEDDED +#include +#endif + Button::Button() : m_pinNum(0), m_pressTime(0), @@ -46,12 +50,19 @@ bool Button::init(uint8_t pin) m_longClick = false; m_pinNum = pin; +#ifdef VORTEX_EMBEDDED + pinMode(m_pinNum, INPUT_PULLUP); +#endif return true; } bool Button::check() { +#ifdef VORTEX_EMBEDDED + return (digitalRead(m_pinNum) == 0); +#else return (Vortex::vcallbacks()->checkPinHook(m_pinNum) == 0); +#endif } void Button::update() diff --git a/VortexEngine/src/Buttons/Button.h b/VortexEngine/src/Buttons/Button.h index 16b6248d78..72d501feb9 100644 --- a/VortexEngine/src/Buttons/Button.h +++ b/VortexEngine/src/Buttons/Button.h @@ -99,5 +99,6 @@ class Button // See Button.cpp for info about this extern Button *g_pButton; +extern Button *g_pButton2; #endif diff --git a/VortexEngine/src/Buttons/Buttons.cpp b/VortexEngine/src/Buttons/Buttons.cpp index 466b22920c..e9a6eeb562 100644 --- a/VortexEngine/src/Buttons/Buttons.cpp +++ b/VortexEngine/src/Buttons/Buttons.cpp @@ -14,6 +14,7 @@ // elegant way to make the button accessible but not global. // This will simply point at Buttons::m_button. Button *g_pButton = nullptr; +Button *g_pButton2 = nullptr; // static members Button Buttons::m_buttons[NUM_BUTTONS]; @@ -21,10 +22,11 @@ Button Buttons::m_buttons[NUM_BUTTONS]; bool Buttons::init() { // initialize the button on pin 1 - if (!m_buttons[0].init(1)) { + if (!m_buttons[0].init(19) || !m_buttons[1].init(20)) { return false; } g_pButton = &m_buttons[0]; + g_pButton2 = &m_buttons[1]; return true; } diff --git a/VortexEngine/src/Buttons/Buttons.h b/VortexEngine/src/Buttons/Buttons.h index 7f02e34c73..a1315ca6bd 100644 --- a/VortexEngine/src/Buttons/Buttons.h +++ b/VortexEngine/src/Buttons/Buttons.h @@ -7,7 +7,7 @@ // changing it won't really work without updating other things // like which pins the buttons are attached to. So this is more // of a hardcoded constant than a configuration setting -#define NUM_BUTTONS 1 +#define NUM_BUTTONS 2 class Buttons { @@ -31,5 +31,6 @@ class Buttons // best way I think extern Button *g_pButton; +extern Button *g_pButton2; #endif diff --git a/VortexEngine/src/Leds/LedTypes.h b/VortexEngine/src/Leds/LedTypes.h index 73bcce80f3..b5a7d57835 100644 --- a/VortexEngine/src/Leds/LedTypes.h +++ b/VortexEngine/src/Leds/LedTypes.h @@ -12,18 +12,58 @@ enum LedPos : uint8_t // this should always be first LED_FIRST = 0, - // LED constants for each led + // the first quadrant middle to outside LED_0 = LED_FIRST, LED_1, LED_2, + + // first quadrant tip LED_3, + + // the first quadrant outside to middle LED_4, LED_5, LED_6, + + // the second quadrant middle to outside LED_7, LED_8, LED_9, + // the second quadrant tip + LED_10, + + // the second quadrant outside to middle + LED_11, + LED_12, + LED_13, + + // the third quadrant middle to outside + LED_14, + LED_15, + LED_16, + + // the third quadrant tip + LED_17, + + // the third quadrant outside to middle + LED_18, + LED_19, + LED_20, + + // the fourth quadrant middle to outside + LED_21, + LED_22, + LED_23, + + // the fourth quadrant tip + LED_24, + + // the fourth quadrant outside to middle + LED_25, + LED_26, + LED_27, + // the number of entries above LED_COUNT, @@ -63,34 +103,191 @@ enum LedPos : uint8_t // LED_ODDS = (LED_COUNT + 3), }; +enum Quadrant : uint8_t +{ + QUADRANT_FIRST = 0, + + QUADRANT_1 = QUADRANT_FIRST, + QUADRANT_2, + QUADRANT_3, + QUADRANT_4, + QUADRANT_5, // This may look confusing but the extra "quadrant" improves compatibility. The name could improve + + QUADRANT_COUNT, // 5 + QUADRANT_LAST = (QUADRANT_COUNT - 1) +}; + +enum Ring : uint8_t +{ + RING_FIRST = 0, + RING_1 = RING_FIRST, + RING_2, + RING_3, + RING_4, + RING_COUNT, + RING_LAST = (RING_COUNT - 1) +}; + enum Pair : uint8_t { PAIR_FIRST = 0, - // one pair for each pair of leds, adjust this to be 2x the LED_COUNT + // Quadrant 1 PAIR_0 = PAIR_FIRST, PAIR_1, PAIR_2, PAIR_3, + // Quadrant 2 PAIR_4, + PAIR_5, + PAIR_6, + PAIR_7, + // Quadrant 3 + PAIR_8, + PAIR_9, + PAIR_10, + PAIR_11, + // Quadrant 4 + PAIR_12, + PAIR_13, + PAIR_14, + PAIR_15, PAIR_COUNT, PAIR_LAST = (PAIR_COUNT - 1), }; // Compile-time check on the number of pairs and leds -static_assert(LED_COUNT == (PAIR_COUNT * 2), "Incorrect number of Pairs for Leds! Adjust the Led enum or Pair enum to match"); +// Orbit has different definition of a 'pair' since the edge +// leds are in their own pairs as both the 'even and odd' led +// it causes there to be more pairs than expected +static_assert(LED_COUNT == ((PAIR_COUNT * 2) - 4), "Incorrect number of Pairs for Leds! Adjust the Led enum or Pair enum to match"); // check if an led is even or odd #define isEven(pos) ((pos % 2) == 0) #define isOdd(pos) ((pos % 2) != 0) +// check if an led is orbit even +#define isOrbitEven(pos) ((MAP_LED(pos) & MAP_RINGS_EVEN) == MAP_LED(pos)) + // convert a pair to even or odd led position #define pairEven(pair) (LedPos)((uint32_t)pair * 2) #define pairOdd(pair) (LedPos)(((uint32_t)pair * 2) + 1) +// check if led is on the top side +inline bool isPairTop(LedPos pos) +{ + switch (pos) { + case LED_0: case LED_1: case LED_2: + case LED_11: case LED_12: case LED_13: + case LED_14: case LED_15: case LED_16: + case LED_25: case LED_26: case LED_27: + return true; + default: + return false; + } +} + +// check if led is on the bottom side +inline bool isPairBot(LedPos pos) +{ + switch (pos) { + case LED_4: case LED_5: case LED_6: + case LED_7: case LED_8: case LED_9: + case LED_18: case LED_19: case LED_20: + case LED_21: case LED_22: case LED_23: + return true; + default: + return false; + } +} + +// check if led is on the side +inline bool isPairSide(LedPos pos) +{ + switch (pos) { + case LED_3: case LED_10: case LED_17: case LED_24: + return true; + default: + return false; + } +} + // convert an led position to a pair -#define ledToPair(pos) (Pair)((uint32_t)pos / 2) +inline Pair ledToPair(LedPos pos) +{ + switch (pos) { + // Quadrant 1 + case LED_0: case LED_6: return PAIR_0; + case LED_1: case LED_5: return PAIR_1; + case LED_2: case LED_4: return PAIR_2; + case LED_3: return PAIR_3; + // Quadrant 2 + case LED_7: case LED_13: return PAIR_4; + case LED_8: case LED_12: return PAIR_5; + case LED_9: case LED_11: return PAIR_6; + case LED_10: return PAIR_7; + // Quadrant 3 + case LED_14: case LED_20: return PAIR_8; + case LED_15: case LED_19: return PAIR_9; + case LED_16: case LED_18: return PAIR_10; + case LED_17: return PAIR_11; + // Quadrant 4 + case LED_21: case LED_27: return PAIR_12; + case LED_22: case LED_26: return PAIR_13; + case LED_23: case LED_25: return PAIR_14; + case LED_24: return PAIR_15; + default: return PAIR_FIRST; + } +} + +// get the top led from the pair +inline LedPos pairTop(Pair pair) +{ + switch (((int)pair) / 4) { + case 0: return (LedPos)pair; // pair 0 1 2 3 -> 0 1 2 3 + case 1: return (LedPos)(17 - pair); // pair 4 5 6 7 -> 13 12 11 10 + case 2: return (LedPos)(6 + pair); // pair 8 9 10 11 -> 14 15 16 17 + case 3: return (LedPos)(39 - pair); // pair 12 13 14 15 -> 27 26 25 24 + default: return LED_FIRST; + } +} + +// get the bottom led from the pair +inline LedPos pairBot(Pair pair) +{ + switch (((int)pair) / 4) { + case 0: return (LedPos)(6 - pair); // pair 0 1 2 3 -> 6 5 4 3 + case 1: return (LedPos)(3 + pair); // pair 4 5 6 7 -> 7 8 9 10 + case 2: return (LedPos)(28 - pair); // pair 8 9 10 11 -> 20 19 18 17 + case 3: return (LedPos)(9 + pair); // pair 12 13 14 15 -> 21 22 23 24 + default: return LED_FIRST; + } +} + +// get the quadrant an led is in +inline Quadrant ledToQuadrant(LedPos led) +{ + return (Quadrant)(led / 7); +} + +// get the first led in the quadrant +inline LedPos quadrantFirstLed(Quadrant quadrant) +{ + return (LedPos)((uint32_t)quadrant * 7); +} + +// get the middle led in the quadrant +inline LedPos quadrantMiddleLed(Quadrant quadrant) +{ + return (LedPos)(((uint32_t)quadrant * 7) + 3); +} + +// get the last led in the quadrant +inline LedPos quadrantLastLed(Quadrant quadrant) +{ + return (LedPos)(((uint32_t)quadrant * 7) + 6); +} // LedMap is a bitmap of leds, used for expressing whether to turn certain leds on // or off with a single integer @@ -98,6 +295,8 @@ typedef uint64_t LedMap; // various macros for mapping leds to an LedMap #define MAP_LED(led) (LedMap)((uint64_t)1 << led) +#define MAP_PAIR_TOP(pair) MAP_LED(pairTop(pair)) +#define MAP_PAIR_BOT(pair) MAP_LED(pairBot(pair)) #define MAP_PAIR_EVEN(pair) MAP_LED(pairEven(pair)) #define MAP_PAIR_ODD(pair) MAP_LED(pairOdd(pair)) #define MAP_PAIR(pair) (MAP_PAIR_EVEN(pair) | MAP_PAIR_ODD(pair)) @@ -159,16 +358,56 @@ inline LedPos ledmapGetNextLed(LedMap map, LedPos pos) #define MAP_PAIR_EVEN_EVENS (MAP_PAIR_EVEN(PAIR_3) | MAP_PAIR_EVEN(PAIR_1)) #define MAP_PAIR_EVEN_ODDS (MAP_PAIR_ODD(PAIR_3) | MAP_PAIR_ODD(PAIR_1)) +// all top leds +#define MAP_ALL_TOP (MAP_LED(LED_0) | MAP_LED(LED_1) | MAP_LED(LED_2) | MAP_LED(LED_11) | \ + MAP_LED(LED_12) | MAP_LED(LED_13) | MAP_LED(LED_14) | MAP_LED(LED_15) | \ + MAP_LED(LED_16) | MAP_LED(LED_25) | MAP_LED(LED_26) | MAP_LED(LED_27)) + +// all bottom leds +#define MAP_ALL_BOT (MAP_LED(LED_4) | MAP_LED(LED_5) | MAP_LED(LED_6) | MAP_LED(LED_7) | \ + MAP_LED(LED_8) | MAP_LED(LED_9) | MAP_LED(LED_18) | MAP_LED(LED_19) | \ + MAP_LED(LED_20) | MAP_LED(LED_21) | MAP_LED(LED_22) | MAP_LED(LED_23)) + +// all face leds +#define MAP_ALL_FACE (MAP_ALL_TOP | MAP_ALL_BOT) + +// led ring maps +#define MAP_RING_INNER (MAP_LED(LED_0) | MAP_LED(LED_6) | MAP_LED(LED_7) | MAP_LED(LED_13) | \ + MAP_LED(LED_14) | MAP_LED(LED_20) | MAP_LED(LED_21) | MAP_LED(LED_27)) +#define MAP_RING_MIDDLE (MAP_LED(LED_1) | MAP_LED(LED_5) | MAP_LED(LED_8) | MAP_LED(LED_12) | \ + MAP_LED(LED_15) | MAP_LED(LED_19) | MAP_LED(LED_22) | MAP_LED(LED_26)) +#define MAP_RING_OUTER (MAP_LED(LED_2) | MAP_LED(LED_4) | MAP_LED(LED_9) | MAP_LED(LED_11) | \ + MAP_LED(LED_16) | MAP_LED(LED_18) | MAP_LED(LED_23) | MAP_LED(LED_25)) +#define MAP_RING_EDGE (MAP_LED(LED_3) | MAP_LED(LED_10) | MAP_LED(LED_17) | MAP_LED(LED_24)) + +#define MAP_RINGS_EVEN (MAP_RING_INNER | MAP_RING_OUTER) +#define MAP_RINGS_ODD (MAP_RING_MIDDLE | MAP_RING_EDGE) + +// led quadrant maps +#define MAP_QUADRANT_1 (MAP_LED(LED_0) | MAP_LED(LED_1) | MAP_LED(LED_2) | MAP_LED(LED_3) | \ + MAP_LED(LED_4) | MAP_LED(LED_5) | MAP_LED(LED_6)) +#define MAP_QUADRANT_2 (MAP_LED(LED_7) | MAP_LED(LED_8) | MAP_LED(LED_9) | MAP_LED(LED_10) | \ + MAP_LED(LED_11) | MAP_LED(LED_12) | MAP_LED(LED_13)) +#define MAP_QUADRANT_3 (MAP_LED(LED_14) | MAP_LED(LED_15) | MAP_LED(LED_16) | MAP_LED(LED_17) | \ + MAP_LED(LED_18) | MAP_LED(LED_19) | MAP_LED(LED_20)) +#define MAP_QUADRANT_4 (MAP_LED(LED_21) | MAP_LED(LED_22) | MAP_LED(LED_23) | MAP_LED(LED_24) | \ + MAP_LED(LED_25) | MAP_LED(LED_26) | MAP_LED(LED_27)) +#define MAP_LINE_1 (MAP_QUADRANT_1 | MAP_QUADRANT_3) +#define MAP_LINE_2 (MAP_QUADRANT_2 | MAP_QUADRANT_4) + + + // set a single led inline void ledmapSetLed(LedMap &map, LedPos pos) { if (pos < LED_COUNT) map |= (1ull << pos); } + // set a single pair inline void ledmapSetPair(LedMap &map, Pair pair) { - ledmapSetLed(map, pairEven(pair)); - ledmapSetLed(map, pairOdd(pair)); + ledmapSetLed(map, pairTop(pair)); + ledmapSetLed(map, pairBot(pair)); } // check if an led is set in the map @@ -176,10 +415,11 @@ inline bool ledmapCheckLed(LedMap map, LedPos pos) { return ((map & (1ull << pos)) != 0); } + // check if a pair is set in the map (both leds) inline bool ledmapCheckPair(LedMap map, Pair pair) { - return ledmapCheckLed(map, pairEven(pair)) && ledmapCheckLed(map, pairOdd(pair)); + return ledmapCheckLed(map, pairTop(pair)) && ledmapCheckLed(map, pairBot(pair)); } // LedPos operators @@ -213,7 +453,75 @@ inline LedPos &operator-=(LedPos &c, int b) return c; } -// pair operators +// Quadrant operators +inline Quadrant &operator++(Quadrant &c) +{ + c = Quadrant(((uint32_t)c) + 1); + return c; +} +inline Quadrant operator++(Quadrant &c, int) +{ + Quadrant temp = c; + ++c; + return temp; +} +inline Quadrant operator+(Quadrant &c, int b) +{ + return (Quadrant)((uint32_t)c + b); +} + +inline Quadrant &operator+=(Quadrant &c, int b) +{ + c = Quadrant(((uint32_t)c) + b); + return c; +} + +inline Quadrant operator-(Quadrant &c, int b) +{ + return (Quadrant)((uint32_t)c - b); +} + +inline Quadrant &operator-=(Quadrant &c, int b) +{ + c = Quadrant(((uint32_t)c) - b); + return c; +} + +// Ring operators +inline Ring &operator++(Ring &c) +{ + c = Ring(((uint32_t)c) + 1); + return c; +} +inline Ring operator++(Ring &c, int) +{ + Ring temp = c; + ++c; + return temp; +} +inline Ring operator+(Ring &c, int b) +{ + return (Ring)((uint32_t)c + b); +} + +inline Ring &operator+=(Ring &c, int b) +{ + c = Ring(((uint32_t)c) + b); + return c; +} + +inline Ring operator-(Ring &c, int b) +{ + return (Ring)((uint32_t)c - b); +} + +inline Ring &operator-=(Ring &c, int b) +{ + c = Ring(((uint32_t)c) - b); + return c; +} + +// Pair operators inline Pair &operator++(Pair &c) { c = Pair(((uint32_t)c) + 1); diff --git a/VortexEngine/src/Leds/Leds.cpp b/VortexEngine/src/Leds/Leds.cpp index 0d1dd4c400..d07df9a667 100644 --- a/VortexEngine/src/Leds/Leds.cpp +++ b/VortexEngine/src/Leds/Leds.cpp @@ -12,6 +12,49 @@ #include "../../VortexLib/VortexLib.h" #endif +#ifdef VORTEX_EMBEDDED +#include +#define ONBOARD_LED_SCK 8 +#define ONBOARD_LED_MOSI 7 +static void transfer(uint8_t byte) +{ + uint8_t startbit = 0x80; + bool lastmosi = !(byte & startbit); + for (uint8_t b = startbit; b != 0; b = b >> 1) { + delayMicroseconds(4); + bool towrite = byte & b; + if (lastmosi != towrite) { + digitalWrite(ONBOARD_LED_MOSI, towrite); + lastmosi = towrite; + } + digitalWrite(ONBOARD_LED_SCK, HIGH); + delayMicroseconds(4); + digitalWrite(ONBOARD_LED_SCK, LOW); + } +} +static void turnOffOnboardLED() +{ + // spi device begin + pinMode(ONBOARD_LED_SCK, OUTPUT); + digitalWrite(ONBOARD_LED_SCK, LOW); + pinMode(ONBOARD_LED_MOSI, OUTPUT); + digitalWrite(ONBOARD_LED_MOSI, HIGH); + + // Begin transaction, setting SPI frequency + static const SPISettings mySPISettings(8000000, MSBFIRST, SPI_MODE0); + SPI.beginTransaction(mySPISettings); + for (uint8_t i = 0; i < 4; i++) { + transfer(0x00); // begin frame + } + transfer(0xFF); // Pixel start + for (uint8_t i = 0; i < 3; i++) { + transfer(0x00); // R,G,B + } + transfer(0xFF); // end frame + SPI.endTransaction(); +} +#endif + // global brightness uint8_t Leds::m_brightness = DEFAULT_BRIGHTNESS; // array of led color values @@ -19,6 +62,10 @@ RGBColor Leds::m_ledColors[LED_COUNT] = { RGB_OFF }; bool Leds::init() { +#ifdef VORTEX_EMBEDDED + turnOffOnboardLED(); + SPI.begin(); +#endif #ifdef VORTEX_LIB Vortex::vcallbacks()->ledsInit(m_ledColors, LED_COUNT); #endif @@ -27,6 +74,9 @@ bool Leds::init() void Leds::cleanup() { +#ifdef VORTEX_EMBEDDED + SPI.end(); +#endif for (uint8_t i = 0; i < LED_COUNT; ++i) { m_ledColors[i].clear(); } @@ -55,69 +105,99 @@ void Leds::setAll(RGBColor col) void Leds::setPair(Pair pair, RGBColor col) { - // start from tip and go to top - setRange(pairEven(pair), pairOdd(pair), col); + setIndex(pairTop(pair), col); + setIndex(pairBot(pair), col); } void Leds::setPairs(Pair first, Pair last, RGBColor col) { // start from tip and go to top - setRange(pairEven(first), pairOdd(last), col); + for (Pair p = first; p < last; ++p) { + setPair(p, col); + } } -void Leds::setRangeEvens(Pair first, Pair last, RGBColor col) +void Leds::setAllEvens(RGBColor col) { - for (Pair pos = first; pos <= last; pos++) { - setIndex(pairEven(pos), col); + for (LedPos i = LED_FIRST; i < LED_COUNT; ++i) { + if ((i % 2) == 0) { + setIndex(i, col); + } } } -void Leds::setAllEvens(RGBColor col) +void Leds::setAllOdds(RGBColor col) { - for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { - setIndex(pairEven(pos), col); + for (LedPos i = LED_FIRST; i < LED_COUNT; ++i) { + if ((i % 2) != 0) { + setIndex(i, col); + } } } -void Leds::setRangeOdds(Pair first, Pair last, RGBColor col) +void Leds::clearAllEvens() { - for (Pair pos = first; pos <= last; pos++) { - setIndex(pairOdd(pos), col); + for (LedPos i = LED_FIRST; i < LED_COUNT; ++i) { + if ((i % 2) == 0) { + clearIndex(i); + } } } -void Leds::setAllOdds(RGBColor col) +void Leds::clearAllOdds() { - for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { - setIndex(pairOdd(pos), col); + for (LedPos i = LED_FIRST; i < LED_COUNT; ++i) { + if ((i % 2) != 0) { + clearIndex(i); + } } } -void Leds::clearRangeEvens(Pair first, Pair last) +void Leds::setQuadrant(Quadrant quadrant, RGBColor col) { - for (Pair pos = first; pos <= last; pos++) { - clearIndex(pairEven(pos)); + if (quadrant == QUADRANT_5) { + led(LED_3) = col; + led(LED_10) = col; + led(LED_17) = col; + led(LED_24) = col; + return; } + // start from tip and go to top + setRange(quadrantFirstLed(quadrant), quadrantLastLed(quadrant), col); } -void Leds::clearAllEvens() +void Leds::setQuadrants(Quadrant first, Quadrant last, RGBColor col) { - for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { - clearIndex(pairEven(pos)); + for (Quadrant quad = first; quad <= last; ++quad) { + setQuadrant(quad, col); } } -void Leds::clearRangeOdds(Pair first, Pair last) +void Leds::setRing(Ring ring, RGBColor col) { - for (Pair pos = first; pos <= last; pos++) { - clearIndex(pairOdd(pos)); + for (Pair i = PAIR_FIRST; i < 4; ++i) { + setPair((Pair)(ring + (4 * i)), col); } } -void Leds::clearAllOdds() +void Leds::setRings(Ring first, Ring last, RGBColor col) { - for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { - clearIndex(pairOdd(pos)); + for (Ring i = first; i <= last; ++i) { + setRing(i, col); + } +} + +void Leds::clearRing(Ring ring) +{ + for (Pair i = PAIR_FIRST; i < 4; ++i) { + clearPair((Pair)(ring + (4 * i))); + } +} + +void Leds::clearRings(Ring first, Ring last) +{ + for (Ring i = first; i <= last; ++i) { + clearRing(i); } } @@ -199,6 +279,20 @@ void Leds::blinkRange(LedPos first, LedPos last, uint16_t offMs, uint16_t onMs, } } +void Leds::blinkQuadrantOffset(Quadrant target, uint32_t time, uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((time % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + setQuadrant(target, col); + } +} + +void Leds::blinkQuadrant(Quadrant target, uint16_t offMs, uint16_t onMs, RGBColor col) +{ + if ((Time::getCurtime() % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { + setQuadrant(target, col); + } +} + void Leds::blinkMap(LedMap targets, uint16_t offMs, uint16_t onMs, RGBColor col) { if ((Time::getCurtime() % MS_TO_TICKS(offMs + onMs)) < MS_TO_TICKS(onMs)) { @@ -251,6 +345,13 @@ void Leds::breatheIndexVal(LedPos target, uint8_t hue, uint32_t variance, uint32 setIndex(target, HSVColor(hue, sat, 255 - (uint8_t)(val + 128 + ((sin(variance * 0.0174533) + 1) * magnitude)))); } +void Leds::breatheQuadrant(Quadrant target, uint32_t hue, uint32_t variance, uint32_t magnitude, uint8_t sat, uint8_t val) +{ + for (uint8_t pos = 0; pos < 7; ++pos) { + setQuadrant(target, HSVColor((uint8_t)(hue + ((sin(variance * 0.0174533) + 1) * magnitude)), sat, val)); + } +} + void Leds::holdAll(RGBColor col) { setAll(col); @@ -260,6 +361,27 @@ void Leds::holdAll(RGBColor col) void Leds::update() { +#ifdef VORTEX_EMBEDDED + // the transaction prevents this from interfering with other communications + // on the pins that are used for SPI, for example IR is on pin 2 + static const SPISettings mySPISettings(12000000, MSBFIRST, SPI_MODE0); + SPI.beginTransaction(mySPISettings); + // Double start frame, normally 4, idk why it's double + for (uint8_t i = 0; i < 8; i++) { + SPI.transfer(0); + } + // Adjust brightness to 5 bits + uint8_t adjustedBrightness = 0b11100000 | ((m_brightness >> 3) & 0b00011111); + // LED frames + for (LedPos pos = LED_FIRST; pos < LED_COUNT; pos++) { + SPI.transfer(adjustedBrightness); // brightness + SPI.transfer(m_ledColors[pos].blue); // blue + SPI.transfer(m_ledColors[pos].green); // green + SPI.transfer(m_ledColors[pos].red); // red + } + // don't need to end the SPI frame apparently, just end transaction + SPI.endTransaction(); +#endif #ifdef VORTEX_LIB Vortex::vcallbacks()->ledsShow(); #endif diff --git a/VortexEngine/src/Leds/Leds.h b/VortexEngine/src/Leds/Leds.h index d7e7cc7c41..b28bca93a3 100644 --- a/VortexEngine/src/Leds/Leds.h +++ b/VortexEngine/src/Leds/Leds.h @@ -37,20 +37,30 @@ class Leds static void clearPair(Pair pair) { setPair(pair, RGB_OFF); } static void clearPairs(Pair first, Pair last) { setPairs(first, last, RGB_OFF); } - // Controll pair evens - static void setRangeEvens(Pair first, Pair last, RGBColor); + // control all evens/odds static void setAllEvens(RGBColor col); - // Controll pair odds - static void setRangeOdds(Pair first, Pair last, RGBColor); static void setAllOdds(RGBColor col); - // Turn off tips - static void clearRangeEvens(Pair first, Pair last); + // clear evens/odds static void clearAllEvens(); - // Turn off tops - static void clearRangeOdds(Pair first, Pair last); static void clearAllOdds(); + // Control full quadrants of Leds + static void setQuadrant(Quadrant quadrant, RGBColor col); + static void setQuadrants(Quadrant first, Quadrant last, RGBColor col); + + // Turn off full quadrants + static void clearQuadrant(Quadrant quadrant) { setQuadrant(quadrant, RGB_OFF); } + static void clearQuadrants(Quadrant first, Quadrant last) { setQuadrants(first, last, RGB_OFF); } + + // set a ring of leds + static void setRing(Ring ring, RGBColor col); + static void setRings(Ring first, Ring last, RGBColor col); + + // clear a ring of leds + static void clearRing(Ring ring); + static void clearRings(Ring first, Ring last); + // Turn on/off a mapping of leds with a color static void setMap(LedMap map, RGBColor col); static void clearMap(LedMap map); @@ -80,6 +90,8 @@ class Leds static void blinkRangeOffset(LedPos first, LedPos last, uint32_t time, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); static void blinkIndex(LedPos target, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); static void blinkRange(LedPos first, LedPos last, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + static void blinkQuadrantOffset(Quadrant target, uint32_t time, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); + static void blinkQuadrant(Quadrant target, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); static void blinkMap(LedMap targets, uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); static void blinkAll(uint16_t offMs = 250, uint16_t onMs = 500, RGBColor col = RGB_OFF); // Blink both LEDs on a pair @@ -96,6 +108,8 @@ class Leds uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); static void breatheIndexVal(LedPos target, uint8_t hue, uint32_t variance, uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); + static void breatheQuadrant(Quadrant target, uint32_t hue, uint32_t variance, + uint32_t magnitude = 15, uint8_t sat = 255, uint8_t val = 210); // a very specialized api to hold all leds on a color for 250ms static void holdAll(RGBColor col); diff --git a/VortexEngine/src/Log/Log.cpp b/VortexEngine/src/Log/Log.cpp index 19877548da..c79ff42b06 100644 --- a/VortexEngine/src/Log/Log.cpp +++ b/VortexEngine/src/Log/Log.cpp @@ -11,12 +11,27 @@ #include "VortexLib.h" #endif +#ifdef VORTEX_EMBEDDED +#include +#endif + #if LOGGING_LEVEL > 0 void InfoMsg(const char *msg, ...) { +#ifdef VORTEX_EMBEDDED + if (!SerialComs::isConnected()) { + return; + } +#endif va_list list; va_start(list, msg); +#ifdef VORTEX_EMBEDDED + char buf[2048] = {0}; + vsnprintf(buf, sizeof(buf), msg, list); + Serial.println(buf); +#else Vortex::printlog(NULL, NULL, 0, msg, list); +#endif va_end(list); } #endif @@ -24,9 +39,22 @@ void InfoMsg(const char *msg, ...) #if LOGGING_LEVEL > 1 void ErrorMsg(const char *func, const char *msg, ...) { +#ifdef VORTEX_EMBEDDED + if (!SerialComs::isConnected()) { + return; + } +#endif va_list list; va_start(list, msg); +#ifdef VORTEX_EMBEDDED + char fmt[2048] = {0}; + snprintf(fmt, sizeof(fmt), "%s(): %s", func, msg); + char buf[2048] = {0}; + vsnprintf(buf, sizeof(buf), fmt, list); + Serial.println(buf); +#else Vortex::printlog(NULL, func, 0, msg, list); +#endif va_end(list); } #endif @@ -34,6 +62,11 @@ void ErrorMsg(const char *func, const char *msg, ...) #if LOGGING_LEVEL > 2 void DebugMsg(const char *file, const char *func, int line, const char *msg, ...) { +#ifdef VORTEX_EMBEDDED + if (!SerialComs::isConnected()) { + return; + } +#endif va_list list; va_start(list, msg); const char *ptr = file + strlen(file); @@ -46,7 +79,15 @@ void DebugMsg(const char *file, const char *func, int line, const char *msg, ... } ptr--; } +#ifdef VORTEX_EMBEDDED + char fmt[2048] = {0}; + snprintf(fmt, sizeof(fmt), "%s:%d %s(): %s", file, line, func, msg); + char buf[2048] = {0}; + vsnprintf(buf, sizeof(buf), fmt, list); + Serial.println(buf); +#else Vortex::printlog(file, func, line, msg, list); +#endif va_end(list); } #endif diff --git a/VortexEngine/src/Menus/Menu.cpp b/VortexEngine/src/Menus/Menu.cpp index abfa81f77b..39def89706 100644 --- a/VortexEngine/src/Menus/Menu.cpp +++ b/VortexEngine/src/Menus/Menu.cpp @@ -9,11 +9,63 @@ #include "../Leds/Leds.h" #include "../Log/Log.h" +// this is an array of possible LED maps for LED selection +static LedMap ledPermutations[] = { + MAP_LED_ALL, + MAP_LED(LED_MULTI), + MAP_RINGS_EVEN, + MAP_RINGS_ODD, + MAP_RING_EDGE, + MAP_RING_OUTER, + MAP_RING_MIDDLE, + MAP_RING_INNER, + MAP_ALL_FACE, + MAP_ALL_TOP, + MAP_ALL_BOT, + MAP_LINE_1, + MAP_LINE_2, + MAP_QUADRANT_1, + MAP_QUADRANT_2, + MAP_QUADRANT_3, + MAP_QUADRANT_4, + MAP_LED(LED_0), + MAP_LED(LED_1), + MAP_LED(LED_2), + MAP_LED(LED_3), + MAP_LED(LED_4), + MAP_LED(LED_5), + MAP_LED(LED_6), + MAP_LED(LED_7), + MAP_LED(LED_8), + MAP_LED(LED_9), + MAP_LED(LED_10), + MAP_LED(LED_11), + MAP_LED(LED_12), + MAP_LED(LED_13), + MAP_LED(LED_14), + MAP_LED(LED_15), + MAP_LED(LED_16), + MAP_LED(LED_17), + MAP_LED(LED_18), + MAP_LED(LED_19), + MAP_LED(LED_20), + MAP_LED(LED_21), + MAP_LED(LED_22), + MAP_LED(LED_23), + MAP_LED(LED_24), + MAP_LED(LED_25), + MAP_LED(LED_26), + MAP_LED(LED_27) +}; + +#define NUM_PERMUTATIONS (sizeof(ledPermutations)/ sizeof(ledPermutations[0])) + Menu::Menu(const RGBColor &col, bool advanced) : m_previewMode(), m_menuColor(col), - m_targetLeds(MAP_LED_ALL), - m_curSelection(0), + m_targetLeds(MAP_LED_NONE), + m_ledSelection(0), + m_curSelection(QUADRANT_FIRST), m_ledSelected(false), m_advanced(advanced), m_shouldClose(false) @@ -42,6 +94,8 @@ bool Menu::init() return false; } } + // reset the current selection + m_curSelection = QUADRANT_FIRST; // copy the current mode into the demo mode and initialize it m_previewMode = *Modes::curMode(); m_previewMode.init(); @@ -69,14 +123,22 @@ Menu::MenuAction Menu::run() // there is no guarantee the child class will call the parent // class's onShortClick and onLongClick functions so - // every time the button is clicked, change the target led + // every time a button is clicked, change the led selection if (g_pButton->onShortClick()) { do { - nextBulbSelection(); - } while (!isValidLedSelection(m_targetLeds)); + m_ledSelection = (m_ledSelection + 1) % NUM_PERMUTATIONS; + } while (!isValidLedSelection(ledPermutations[m_ledSelection])); + } + if (g_pButton2->onShortClick()) { + do { + m_ledSelection = (m_ledSelection > 0) ? (m_ledSelection - 1) : (NUM_PERMUTATIONS - 1); + } while (!isValidLedSelection(ledPermutations[m_ledSelection])); } // on a long press of the button, lock in the target led if (g_pButton->onLongClick()) { + if (m_targetLeds == MAP_LED_NONE) { + addSelectionMask(); + } m_ledSelected = true; // call led selected callback onLedSelected(); @@ -86,7 +148,10 @@ Menu::MenuAction Menu::run() : (m_targetLeds == MAP_LED_ALL) ? "all" : "some singles"); } - + // on a long press of the 2nd button, add to selection + if (g_pButton2->onLongClick()) { + addSelectionMask(); + } // render the bulb selection showBulbSelection(); @@ -99,11 +164,15 @@ Menu::MenuAction Menu::run() void Menu::showBulbSelection() { Leds::clearAll(); - if (m_targetLeds == MAP_LED(LED_MULTI)) { + if (ledPermutations[m_ledSelection] == MAP_LED(LED_MULTI)) { LedPos pos = (LedPos)((Time::getCurtime() / 30) % LED_COUNT); - Leds::blinkIndexOffset(pos, pos * 10, 50, 500, RGB_MAGENTA1); + for (int dots = 0; dots < 4; ++dots) { + LedPos dotPos = (LedPos)((pos + (dots * (LED_COUNT / 4))) % LED_COUNT); + Leds::blinkIndexOffset(dotPos, dotPos * 10, 50, 500, RGB_MAGENTA1); + } } else { - Leds::blinkMap(m_targetLeds, BULB_SELECT_OFF_MS, BULB_SELECT_ON_MS, RGB_MAGENTA1); + Leds::setMap(m_targetLeds, RGB_ORANGE); + Leds::blinkMap(ledPermutations[m_ledSelection], BULB_SELECT_OFF_MS, BULB_SELECT_ON_MS, RGB_MAGENTA1); } // blink when selecting Menus::showSelection(RGB_MAGENTA1); @@ -115,53 +184,19 @@ void Menu::showExit() Leds::setAll(RGB_RED); return; } - Leds::clearAll(); - Leds::setAll(RGB_WHITE0); - Leds::blinkAll(EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, RGB_RED0); + Leds::clearQuadrant(QUADRANT_5); + Leds::blinkQuadrant(QUADRANT_5, 250, 500, RGB_RED0); } -void Menu::nextBulbSelection() +void Menu::onLedSelected() { - Mode *cur = Modes::curMode(); - // The target led can be 0 through LED_COUNT to represent any led or all leds - // modulo by LED_COUNT + 1 to include LED_COUNT (all) as a target - switch (m_targetLeds) { - case MAP_LED_ALL: - if (cur->isMultiLed()) { - // do not allow multi led to select anything else - //break; - } - m_targetLeds = MAP_LED(LED_FIRST); - break; - case MAP_LED(LED_LAST): - m_targetLeds = MAP_PAIR_EVENS; - break; - case MAP_PAIR_EVENS: - m_targetLeds = MAP_PAIR_ODDS; - break; - case MAP_PAIR_ODDS: - m_targetLeds = MAP_LED(LED_MULTI); - break; - case MAP_LED(LED_MULTI): - m_targetLeds = MAP_LED_ALL; - break; - default: // LED_FIRST through LED_LAST - // do not allow multi led to select anything else - if (cur->isMultiLed()) { - //m_targetLeds = MAP_LED_ALL; - //break; - } - // iterate as normal - m_targetLeds = MAP_LED(((ledmapGetFirstLed(m_targetLeds) + 1) % (LED_COUNT + 1))); - break; - } } -void Menu::onLedSelected() +void Menu::onShortClick() { } -void Menu::onShortClick() +void Menu::onShortClick2() { } @@ -170,6 +205,11 @@ void Menu::onLongClick() leaveMenu(false); } +void Menu::onLongClick2() +{ + leaveMenu(false); +} + void Menu::leaveMenu(bool doSave) { m_shouldClose = true; @@ -177,3 +217,54 @@ void Menu::leaveMenu(bool doSave) Modes::saveStorage(); } } + +void Menu::blinkSelection(uint32_t offMs, uint32_t onMs) +{ + uint32_t blinkCol = RGB_OFF; + if (g_pButton->isPressed() && g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS) { + // blink green if long pressing on a selection + blinkCol = RGB_WHITE6; + } + switch (m_curSelection) { + case QUADRANT_LAST: + // exit thumb breathes red on the tip and is either blank or red on the top + // depending on whether you've held for the short click threshold or not + if (g_pButton->isPressed() && g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS) { + Leds::setQuadrant(QUADRANT_5, RGB_WHITE); + } else { + Leds::setQuadrant(QUADRANT_5, RGB_RED); + Leds::blinkQuadrant(QUADRANT_5, 250, 500, RGB_WHITE0); + } + break; + case QUADRANT_COUNT: + // special selection clause 'select all' do nothing + break; + default: + // otherwise just blink the selected finger to off from whatever + // color or pattern it's currently displaying + if (blinkCol == RGB_OFF && Leds::getLed(quadrantFirstLed(m_curSelection)).empty()) { + // if the blink color is 'off' and the led is a blank then we + // need to blink to a different color + blinkCol = RGB_WHITE0; + } + // blink the target finger to the target color + Leds::blinkQuadrantOffset(m_curSelection, + g_pButton->isPressed() ? g_pButton->holdDuration() : Time::getCurtime(), + offMs, onMs, blinkCol); + break; + } +} + +// this adds the currently targeted ledPermutation to the selected leds +void Menu::addSelectionMask() { + // if selecting any of the individual leds then toggle + LedMap mask = ledPermutations[m_ledSelection]; + // checks if only 1 b it is set in the target mask + if ((mask & (mask - 1)) == 0) { + // if there's only one bit set then toggle that location + m_targetLeds ^= mask; + } else { + // otherwise just add the mask whatever it is + m_targetLeds |= mask; + } +} diff --git a/VortexEngine/src/Menus/Menu.h b/VortexEngine/src/Menus/Menu.h index 67d49e929b..24cf52ae32 100644 --- a/VortexEngine/src/Menus/Menu.h +++ b/VortexEngine/src/Menus/Menu.h @@ -32,17 +32,19 @@ class Menu // optional handlers for clicks virtual void onShortClick(); + virtual void onShortClick2(); virtual void onLongClick(); + virtual void onLongClick2(); // close the current menu virtual void leaveMenu(bool doSave = false); protected: void showBulbSelection(); - void showExit(); + virtual void showExit(); - // iterate to next bulb selection - void nextBulbSelection(); + // blink the selected finger + virtual void blinkSelection(uint32_t offMs = 250, uint32_t onMs = 500); // an overridable api that allows derived menus to decide which led selections // should be available before they have actually opened @@ -55,8 +57,10 @@ class Menu // tracks the targetted leds for this menu // note this is an led map LedMap m_targetLeds; - // all menus have a 'current selection' - uint8_t m_curSelection; + // current index of led maps + uint16_t m_ledSelection; + // all menus have a 'current selection which can point at any led + Quadrant m_curSelection; // true once a an led is selected bool m_ledSelected; // whether advanced menu was activated @@ -65,6 +69,8 @@ class Menu private: // internal flag to close the menu bool m_shouldClose; + // add to the current selection of leds + void addSelectionMask(); #ifdef VORTEX_LIB friend class Vortex; diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.cpp b/VortexEngine/src/Menus/MenuList/ColorSelect.cpp index 50a069f254..451d83baa8 100644 --- a/VortexEngine/src/Menus/MenuList/ColorSelect.cpp +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.cpp @@ -1,7 +1,6 @@ #include "ColorSelect.h" #include "../../Time/TimeControl.h" -#include "../../Patterns/Pattern.h" #include "../../Colors/Colorset.h" #include "../../Buttons/Button.h" #include "../../Time/Timings.h" @@ -11,13 +10,18 @@ #include "../../Leds/Leds.h" #include "../../Log/Log.h" +// the number of slots in a page +#define PAGE_SIZE 4 +// the number of pages +#define NUM_PAGES 2 + ColorSelect::ColorSelect(const RGBColor &col, bool advanced) : Menu(col, advanced), m_state(STATE_PICK_SLOT), - m_newColor(), - m_colorset(), - m_targetSlot(0), - m_targetHue1(0) + m_curPage(0), + m_slot(0), + m_quadrant(0), + m_newColor() { // NOTE! Specifically using hsv_to_rgb_rainbow instead of generic because // it will generate nicer looking colors and a nicer rainbow to select @@ -62,12 +66,24 @@ Menu::MenuAction ColorSelect::run() // all states start with a blank slate Leds::clearAll(); + switch (m_state) { case STATE_INIT: - // reset all the selection members + // this is separate from the init function because the target led + // hasn't been chosen yet at the time of the init function running + // where as this will run after the target led has been chosen and + // we can fetch the correct colorset to work with + //m_newColor.clear(); m_newColor = HSVColor(0, 255, 255); - m_targetHue1 = 0; - m_targetSlot = 0; + m_curPage = 0; + m_slot = 0; + m_quadrant = 0; + // grab the colorset from our selected target led + if (m_targetLeds == MAP_LED_ALL) { + m_colorset = Modes::curMode()->getColorset(); + } else { + m_colorset = Modes::curMode()->getColorset(ledmapGetFirstLed(m_targetLeds)); + } // move on to picking slot m_state = STATE_PICK_SLOT; break; @@ -81,184 +97,313 @@ Menu::MenuAction ColorSelect::run() showSelection(m_state); break; } - // show selections - Menus::showSelection(); + + // blink whichever slot is currently selected regardless of state + blinkSelection(); + return MENU_CONTINUE; } -// callback after the user selects the target led -void ColorSelect::onLedSelected() +void ColorSelect::onShortClick() { - Mode *cur = Modes::curMode(); - // grab the colorset from our selected target led - if (m_targetLeds == MAP_LED_ALL) { - m_colorset = cur->getColorset(); - } else { - m_colorset = cur->getColorset(ledmapGetFirstLed(m_targetLeds)); + // keep track of pages when in slot selection + if (m_state == STATE_PICK_SLOT) { + // if the current selection is on the index finger then it's at the + // end of the current page and we might need to go to the next page + if ((m_curSelection == QUADRANT_4 && (m_curPage == 0 && m_colorset.numColors() > 3))) { + // increase the page number to 1 + m_curPage = (m_curPage + 1) % NUM_PAGES; + // skip past the thumb if we're on index + m_curSelection = QUADRANT_LAST; + // clear all leds because we went to the next page + Leds::clearAll(); + } else if (m_curSelection == QUADRANT_LAST && (m_curPage == 1 || (m_curPage == 0 && m_colorset.numColors() <= 3))) { + m_curPage = 0; + // skip past the thumb if we're on index + m_curSelection = QUADRANT_LAST; + // clear all leds because we went to the next page + Leds::clearAll(); + } + } + // iterate selection forward and wrap after the thumb + m_curSelection = (Quadrant)((m_curSelection + 1) % (QUADRANT_LAST + 1)); + // only when we're not on thumb calculate the current 'slot' based on page + if (m_curSelection != QUADRANT_LAST && m_state == STATE_PICK_SLOT) { + // the slot is an index in the colorset, where as curselection is a finger index + m_slot = (uint32_t)m_curSelection + (m_curPage * PAGE_SIZE); + } + if (m_slot > m_colorset.numColors()) { + if (m_curSelection != QUADRANT_LAST) { + m_curSelection = QUADRANT_LAST; + } else { + m_curPage = m_slot = 0; + m_curSelection = QUADRANT_FIRST; + } } } -void ColorSelect::onShortClick() +void ColorSelect::onShortClick2() { - // increment selection - m_curSelection++; + // keep track of pages when in slot selection if (m_state == STATE_PICK_SLOT) { - m_curSelection %= (m_colorset.numColors() + 1 + (m_colorset.numColors() < MAX_COLOR_SLOTS)); + // if the current selection is on the index finger then it's at the + // end of the current page and we might need to go to the next page + if (m_curSelection == QUADRANT_1 && m_curPage > 0) { + // increase the page number to 1 + if (!m_curPage) { + m_curPage = NUM_PAGES - 1; + } else { + --m_curPage; + } + // skip past the + m_curSelection = QUADRANT_5; + // clear all leds because we went to the next page + Leds::clearAll(); + } else if (m_curSelection == QUADRANT_1 && (m_curPage > 0 || (m_curPage == 0 && m_colorset.numColors() > 3))) { + if (!m_curPage) { + m_curPage = NUM_PAGES - 1; + } else { + --m_curPage; + } + // skip past the thumb if we're on index + m_curSelection = QUADRANT_5; + // clear all leds because we went to the next page + Leds::clearAll(); + // if we are moving backwards from the first slot in a 4 color set + } else if (m_curSelection == QUADRANT_5) { + m_curSelection = (Quadrant)((m_colorset.numColors() + 1) % PAGE_SIZE); + Leds::clearAll(); + } + } + // iterate selection backward and wrap after the thumb + if (!m_curSelection) { + if (!m_curPage) { + m_curSelection = QUADRANT_5; + } else { + m_curSelection = QUADRANT_LAST; + } } else { - m_curSelection %= 5; + m_curSelection = m_curSelection - 1; + } + if (m_curSelection != QUADRANT_5 && m_state == STATE_PICK_SLOT) { + // the slot is an index in the colorset, where as curselection is a finger index + m_slot = (uint32_t)m_curSelection + (m_curPage * PAGE_SIZE); + } + if (m_slot > m_colorset.numColors()) { + if (m_curSelection != QUADRANT_5) { + m_curSelection = QUADRANT_5; + } else { + m_curPage = m_slot = 0; + m_curSelection = QUADRANT_FIRST; + } } } void ColorSelect::onLongClick() { - // if we're on 'exit' and we're on any menu past the slot selection - if (m_curSelection == 4 && m_state > STATE_PICK_SLOT) { - // move back to the previous selection - m_state = (ColorSelectState)(m_state - 1); - // if we're back to the slot selection then set our position back to the - // slot we selected, otherwise it's more visually appealing to just return - // to the first selection on the previous menu - m_curSelection = (m_state == STATE_PICK_SLOT) ? m_targetSlot : 0; - return; + bool needsSave = false; + // if we're exiting a menu + if (m_curSelection == QUADRANT_LAST) { + Mode *cur = Modes::curMode(); + // leaving a menu, clear everything + Leds::clearAll(); + switch (m_state) { + case STATE_PICK_SLOT: + default: + // if we're targetting more than one led then screw + // checking if the colorset has changed because it's + // not worth the effort + needsSave = (!MAP_IS_ONE_LED(m_targetLeds) || + !m_colorset.equals(cur->getColorset(ledmapGetFirstLed(m_targetLeds)))); + // if we need to save, then actually update the colorset + if (needsSave) { + // save the colorset + cur->setColorsetMap(m_targetLeds, m_colorset); + cur->init(); + } + // leave menu and save if we made changes + leaveMenu(needsSave); + return; + case STATE_PICK_HUE1: + m_state = STATE_PICK_SLOT; + // reset selection and page based on chosen slot + m_curSelection = (Quadrant)((m_slot % PAGE_SIZE)); + m_curPage = m_slot / PAGE_SIZE; + return; + case STATE_PICK_HUE2: + // save the quadrant of hue that was selected so that if + // they navigate backwards we know where to place them + m_curSelection = (Quadrant)m_quadrant; + m_state = (ColorSelectState)(m_state - 1); + return; + case STATE_PICK_SAT: + case STATE_PICK_VAL: + m_state = (ColorSelectState)(m_state - 1); + m_curSelection = QUADRANT_FIRST; + return; + } } - // reuse these variables lots - uint8_t numColors = m_colorset.numColors(); - uint32_t holdDur = g_pButton->holdDuration(); switch (m_state) { - case STATE_INIT: - // nothing - return; + default: + break; case STATE_PICK_SLOT: - // if the exit is selected then save and exit, this depends on the number - // of colors in the colorset. If the colorset is full then the exit is - // just the number of colors (8) but if it's not full then the exit is the - // number of colors + 1. Example: with 4 cols, cols are on 0, 1, 2, 3, - // add-color is 4, and exit is 5 - if (m_curSelection == numColors + (numColors < MAX_COLOR_SLOTS)) { - Mode *cur = Modes::curMode(); - cur->setColorsetMap(m_targetLeds, m_colorset); - cur->init(); - leaveMenu(true); - return; - } - // handle if user releases during the delete option - if (m_curSelection < numColors && - holdDur >= DELETE_THRESHOLD_TICKS && - (holdDur % (DELETE_CYCLE_TICKS * 2)) > (DELETE_CYCLE_TICKS)) { + if (m_slot < m_colorset.numColors() && + g_pButton->holdDuration() >= DELETE_THRESHOLD_TICKS && + (g_pButton->holdDuration() % (DELETE_CYCLE_TICKS * 2)) >(DELETE_CYCLE_TICKS)) { // delete current slot - m_colorset.removeColor(m_curSelection); - if (m_curSelection > numColors) { - m_curSelection--; + m_colorset.removeColor(m_slot); + if (m_slot > m_colorset.numColors()) { + m_slot--; } + m_curSelection = (Quadrant)((m_slot % PAGE_SIZE)); + m_curPage = m_slot / PAGE_SIZE; return; } - // otherwise store the target slot continue onto the hue selection - m_targetSlot = m_curSelection; + m_state = STATE_PICK_HUE1; + // the page is only used for slot selection so reset current page + // for next time they use the color select + m_curPage = 0; + m_quadrant = 0; break; case STATE_PICK_HUE1: - m_targetHue1 = m_curSelection; - m_newColor.hue = m_targetHue1 * (255 / 4); + // save the quadrant of hue that was selected so that if + // they navigate backwards we know where to place them + m_quadrant = m_curSelection; + // pick a hue1 + m_newColor.hue = m_quadrant * (256 / 4); + m_state = STATE_PICK_HUE2; break; case STATE_PICK_HUE2: - m_newColor.hue = (m_targetHue1 * (255 / 4)) + m_curSelection * (255 / 16); + // pick a hue2 + m_newColor.hue = (m_quadrant * (256 / 4)) + (m_curSelection * (256 / 16)); + m_state = STATE_PICK_SAT; break; case STATE_PICK_SAT: + // pick a saturation m_newColor.sat = sats[m_curSelection]; + m_state = STATE_PICK_VAL; break; case STATE_PICK_VAL: - // no m_targetVal because you can't go back after this + // pick a value m_newColor.val = vals[m_curSelection]; - // specifically using hsv to rgb rainbow to generate the color - m_colorset.set(m_targetSlot, m_newColor); - m_curSelection = m_targetSlot; + // replace the slot with the new color, this might + // result in adding a new color if the slot is at + // hightest color index + 1 + m_colorset.set(m_slot, m_newColor); + // go back to beginning for next time m_state = STATE_PICK_SLOT; - return; - } - m_state = (ColorSelectState)(m_state + 1); - m_curSelection = 0; -} + // reset the color + m_newColor.clear(); + // go back to the slot we just added + m_curSelection = (Quadrant)((m_slot % PAGE_SIZE)); + m_curPage = m_slot / PAGE_SIZE; -void ColorSelect::showSlotSelection() -{ - uint8_t exitIndex = m_colorset.numColors(); - uint32_t holdDur = g_pButton->holdDuration(); - bool withinNumColors = m_curSelection < exitIndex; - bool holdDurationCheck = g_pButton->isPressed() && holdDur >= DELETE_THRESHOLD_TICKS; - bool holdDurationModCheck = (holdDur % (DELETE_CYCLE_TICKS * 2)) > DELETE_CYCLE_TICKS; - const RGBColor &col = m_colorset[m_curSelection]; - if (withinNumColors && holdDurationCheck && holdDurationModCheck) { - // breath red for delete slot - Leds::breatheIndex(LED_ALL, 0, holdDur); - } else if (withinNumColors) { - if (col.empty()) { - Leds::setAll(RGB_WHITE0); - } - // blink the selected slot color - Leds::blinkAll(150, 650, col); - } else if (exitIndex < MAX_COLOR_SLOTS) { - if (m_curSelection == exitIndex) { - // blink both leds and blink faster to indicate 'add' new color - Leds::blinkAll(100, 150, RGB_WHITE2); - } - exitIndex++; - } - if (m_curSelection == exitIndex) { - showFullSet(50, 100); + // return instead of break so the cur selection isn't reset + return; } + // reset selection after choosing anything + m_curSelection = QUADRANT_FIRST; } -void ColorSelect::showSelection(ColorSelectState mode) +void ColorSelect::onLongClick2() { - if (m_curSelection >= 4) { - showExit(); - return; - } - uint32_t now = Time::getCurtime(); - uint8_t hue = m_newColor.hue; - uint8_t sat = m_newColor.sat; - uint8_t val = 255; - switch (mode) { + // leaving a menu, clear everything + Leds::clearAll(); + switch (m_state) { + case STATE_PICK_SLOT: default: + // leave menu without saving + leaveMenu(); return; case STATE_PICK_HUE1: - hue = m_curSelection * (255 / 4); - MAP_FOREACH_LED(MAP_PAIR_EVENS) { - Leds::breatheIndex(pos, hue, (now / 2), 22, 255, 180); - } - MAP_FOREACH_LED(MAP_PAIR_ODDS) { - Leds::breatheIndex(pos, hue, (now / 2) + 125, 22, 255, 180); - } - // force sat at hue level1 - sat = 255; - // NOTE: return here + m_state = STATE_PICK_SLOT; + // reset selection and page based on chosen slot + m_curSelection = (Quadrant)((m_slot % PAGE_SIZE)); + m_curPage = m_slot / PAGE_SIZE; return; case STATE_PICK_HUE2: - hue = m_targetHue1 * (255 / 4) + (m_curSelection * (255 / 16)); - Leds::setIndex(LED_1, RGB_WHITE0); - // force sat at hue level2 - sat = 255; - break; case STATE_PICK_SAT: - sat = sats[m_curSelection]; - break; case STATE_PICK_VAL: - val = vals[m_curSelection]; - break; + m_state = (ColorSelectState)(m_state - 1); + return; } - Leds::setMap(MAP_PAIR_EVENS, HSVColor(hue, sat, val)); } -void ColorSelect::showFullSet(uint8_t offMs, uint8_t onMs) +void ColorSelect::showSlotSelection() { - uint8_t numCols = m_colorset.numColors(); - uint8_t offOnMs = MS_TO_TICKS(offMs + onMs); - if (!numCols || !offOnMs) { - return; + // the index of the first color to show changes based on the page + // will be either 0 or 4 for the two page color select + uint32_t idx = (m_curPage * PAGE_SIZE); + for (Quadrant f = QUADRANT_FIRST; f <= QUADRANT_4; ++f) { + // set the current colorset slot color on the current finger + // display the extra slots as solid blank + Leds::setQuadrant(f, (idx >= m_colorset.numColors()) ? RGB_WHITE0 : m_colorset[idx]); + idx++; } - uint32_t now = Time::getCurtime(); - if ((now % offOnMs) < MS_TO_TICKS(onMs)) { - Leds::setAll(m_colorset.get((now / offOnMs) % numCols)); +} + +void ColorSelect::showSelection(ColorSelectState mode) +{ + for (Quadrant f = QUADRANT_FIRST; f <= QUADRANT_4; ++f) { + HSVColor color; + switch (mode) { + case STATE_PICK_HUE1: + if (f != QUADRANT_FIRST) { + return; + } + for (Pair p = PAIR_FIRST; p < PAIR_COUNT; ++p) { + Leds::setPair(p, HSVColor((256 / PAIR_COUNT) * p, 255, 255)); + } + return; + case STATE_PICK_HUE2: + // calculate the hue with the stored 'quadrant' variable because + // they may have gotten here by going back from the next state (pick sat) + // and the m_newColor.hue value may have been irreversibly changed + color = HSVColor((m_quadrant * (256 / 4)) + ((256 / 16) * f), 255, 255); + break; + case STATE_PICK_SAT: + color = HSVColor(m_newColor.hue, sats[f], 255); + break; + case STATE_PICK_VAL: + color = HSVColor(m_newColor.hue, m_newColor.sat, vals[f]); + break; + default: + return; + } + Leds::setQuadrant(f, color); + } + if (m_curSelection >= 4) { + showExit(); + } +} + +void ColorSelect::blinkSelection(uint32_t offMs, uint32_t onMs) +{ + // if we're in the slot selection + if (m_state == STATE_PICK_SLOT) { + // and the current selected slot is the end slot, except for when we're on the thumb + if (m_slot == m_colorset.numColors() && m_curSelection != QUADRANT_LAST) { + // clear the finger so it turns off, then blink this slot to either + // white or dim white to indicate we can add a color here + Leds::clearQuadrant(m_curSelection); + Leds::blinkQuadrant(m_curSelection, 150, 350, + g_pButton->isPressed() ? RGB_WHITE6 : RGB_WHITE4); + return; + } else if (m_slot < m_colorset.numColors() && + g_pButton->isPressed() && + g_pButton->holdDuration() >= DELETE_THRESHOLD_TICKS) { + // if we're pressing down on a slot then glow the tip white/red + if ((g_pButton->holdDuration() % (DELETE_CYCLE_TICKS * 2)) > DELETE_CYCLE_TICKS) { + // breath red instead of white blink + Leds::breatheQuadrant(m_curSelection, 0, g_pButton->holdDuration()); + return; + } + } else if (m_slot == (uint32_t)(m_colorset.numColors() + 1)) { + Leds::blinkQuadrant(QUADRANT_5, 150, 350, RGB_WHITE); + } } + // otherwise run the default blink logic + Menu::blinkSelection(offMs, onMs); } bool ColorSelect::isValidLedSelection(LedMap selection) const diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.h b/VortexEngine/src/Menus/MenuList/ColorSelect.h index 6bda0fd1c3..aed7d038ce 100644 --- a/VortexEngine/src/Menus/MenuList/ColorSelect.h +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.h @@ -14,14 +14,16 @@ class ColorSelect : public Menu bool init() override; MenuAction run() override; - // callback after the user selects the target led - void onLedSelected() override; - // handlers for clicks void onShortClick() override; + void onShortClick2() override; void onLongClick() override; + void onLongClick2() override; private: + // overridden blink logic for the colorselect menu (Controls how m_curSelection blinks) + void blinkSelection(uint32_t offMs = 350, uint32_t onMs = 500) override; + // override the led selection api to choose which led maps can be selected bool isValidLedSelection(LedMap selection) const override; @@ -29,46 +31,60 @@ class ColorSelect : public Menu enum ColorSelectState : uint32_t { STATE_INIT, + + // currently picking the color slot to change STATE_PICK_SLOT, + + // first pick a quadrant 0, 90, 180, 240 STATE_PICK_HUE1, + + // next pick a quadrant within that quadrant 0, 25, 50, 70 STATE_PICK_HUE2, + + // picking a saturation for the color STATE_PICK_SAT, - STATE_PICK_VAL + + // picking a value for the color + STATE_PICK_VAL, }; + // internal routines for the color select void showSlotSelection(); void showSelection(ColorSelectState mode); - void showFullSet(uint8_t offMs, uint8_t onMs); // the options for saturations const uint8_t sats[4] = { - SAT_OPTION_4, - SAT_OPTION_3, - SAT_OPTION_2, - SAT_OPTION_1 + 255, + 170, + 85, + 0 }; // the options for values const uint8_t vals[4] = { - VAL_OPTION_4, - VAL_OPTION_3, - VAL_OPTION_2, - VAL_OPTION_1 + 255, + 170, + 85, + 0 }; - // the state of the color select menu + // the current state of the color selection menu ColorSelectState m_state; - // the new color being built via hue, sat then val - HSVColor m_newColor; - // A copy of the colorset being changed + + // A copy of the colorset from the current mode Colorset m_colorset; - // below are the selection indexes for each level + // the colorselect has multiple pages + uint32_t m_curPage; - // the target values selected at each level to build the color, the value - // selected at the last level isn't stored because you can't go back after - uint8_t m_targetSlot; - uint8_t m_targetHue1; + // the target color slot to change + uint32_t m_slot; + + // the chosen quadrant + uint32_t m_quadrant; + + // the new color to set + HSVColor m_newColor; }; #endif diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp index c8dade7503..4845f4226a 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp @@ -308,11 +308,21 @@ void EditorConnection::onShortClick() m_allowReset = false; } +void EditorConnection::onShortClick2() +{ + sendCurModeVL(); +} + void EditorConnection::onLongClick() { leaveMenu(true); } +void EditorConnection::onLongClick2() +{ + leaveMenu(true); +} + void EditorConnection::leaveMenu(bool doSave) { SerialComs::write(EDITOR_VERB_GOODBYE); diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.h b/VortexEngine/src/Menus/MenuList/EditorConnection.h index a8f5a729ff..2f12509d0b 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.h +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.h @@ -20,7 +20,9 @@ class EditorConnection : public Menu // handlers for clicks void onShortClick() override; + void onShortClick2() override; void onLongClick() override; + void onLongClick2() override; // menu conn void leaveMenu(bool doSave = false) override; diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.cpp b/VortexEngine/src/Menus/MenuList/FactoryReset.cpp index 287af81739..d8232ba6bb 100644 --- a/VortexEngine/src/Menus/MenuList/FactoryReset.cpp +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.cpp @@ -3,9 +3,10 @@ #include "../../VortexEngine.h" #include "../../Modes/DefaultModes.h" #include "../../Time/TimeControl.h" -#include "../../Patterns/Pattern.h" -#include "../../Buttons/Button.h" #include "../../Time/Timings.h" +#include "../../Colors/Colorset.h" +#include "../../Buttons/Button.h" +#include "../../Modes/DefaultModes.h" #include "../../Modes/Modes.h" #include "../../Modes/Mode.h" #include "../../Leds/Leds.h" @@ -41,8 +42,14 @@ bool FactoryReset::init() // skip led selection m_ledSelected = true; } - // Start on exit by default - m_curSelection = false; + // start on exit by default + m_curSelection = QUADRANT_LAST; + // bypass led selection for fac reset if a multi was set on + // the current slot because it doesn't make sense to pick + if (Modes::curMode()->isMultiLed()) { + m_ledSelected = true; + m_targetLeds = MAP_LED(LED_MULTI); + } DEBUG_LOG("Entered factory reset"); return true; } @@ -53,21 +60,45 @@ Menu::MenuAction FactoryReset::run() if (result != MENU_CONTINUE) { return result; } + + // show the reset menu showReset(); + + // blink the selection + blinkSelection(); + + // continue return MENU_CONTINUE; } void FactoryReset::onShortClick() { - m_curSelection = (uint8_t)!m_curSelection; + if (m_curSelection == QUADRANT_LAST) { + m_curSelection = QUADRANT_COUNT; + } else { + m_curSelection = QUADRANT_LAST; + } +} + +void FactoryReset::onShortClick2() +{ + onShortClick(); } void FactoryReset::onLongClick() { - if (m_curSelection == 0) { - // if the selection isn't actually on factory reset then just leave + switch (m_curSelection) { + case QUADRANT_LAST: + default: leaveMenu(); return; + case QUADRANT_COUNT: + // must wait till all empty + one white blink + if (g_pButton->holdDuration() > (FACTORY_RESET_THRESHOLD_TICKS + MS_TO_TICKS(700))) { + Modes::setDefaults(); + leaveMenu(true); + } + break; } // if the button hasn't been held long enough just return if (g_pButton->holdDuration() <= (FACTORY_RESET_THRESHOLD_TICKS + MS_TO_TICKS(10))) { @@ -94,34 +125,56 @@ void FactoryReset::onLongClick() leaveMenu(true); } +void FactoryReset::onLongClick2() +{ + leaveMenu(); +} + void FactoryReset::showReset() { - if (m_curSelection == 0) { - Leds::clearAll(); - Leds::blinkAll(350, 350, RGB_WHITE0); + // if we're on the thumb just set the rest to blank + if (m_curSelection == QUADRANT_LAST) { + Leds::setRange(LED_FIRST, LED_LAST, RGB_WHITE0); return; } - bool isPressed = g_pButton->isPressed(); - if (!isPressed) { - Leds::clearAll(); - Leds::blinkAll(50, 50, RGB_RED4); + // otherwise we're not on thumb, if the button isn't pressed + if (!g_pButton->isPressed()) { + // just idle blink from clear to blank + Leds::clearRange(LED_FIRST, LED_LAST); + Leds::blinkRange(LED_FIRST, LED_LAST, 250, 150, RGB_RED0); return; } - // don't start the fill until the button has been held for a bit - uint32_t holdDur = g_pButton->holdDuration(); - if (holdDur < MS_TO_TICKS(100)) { + + // the button is pressed so show the reset countdown timer + + // the progress is how long the hold duration has been held + // relative to the factory reset threshold time + float progress = (float)g_pButton->holdDuration() / FACTORY_RESET_THRESHOLD_TICKS; + // prevents the countdown timer from showing unless button is held longer than 3% of the reset Threshold (this is for short clicks) + if (progress < 0.03) { return; } - uint16_t progress = ((holdDur * 100) / FACTORY_RESET_THRESHOLD_TICKS); - DEBUG_LOGF("progress: %d", progress); - if (progress >= 100) { - Leds::setAll(RGB_WHITE); + // the ledProgress is just an LED from pinky tip to index top based on progress + LedPos ledProgress = (LedPos)(progress * LED_LAST); + // max the led progress at index top (don't include thumb) + if (ledProgress > LED_LAST) { + // when we reach the end of the progress bar just blink white + Leds::blinkRange(LED_FIRST, LED_LAST, 80, 60, RGB_WHITE6); return; } - uint8_t offMs = 100; - uint8_t onMs = (progress > 60) ? 30 : 100; - uint8_t sat = (uint8_t)((progress * 5) >> 1); // Using bit shift for division by 2 - Leds::clearAll(); - Leds::blinkAll(offMs, onMs, HSVColor(0, 255 - sat, 180)); + + // the off/on ms blink faster based on the progress + uint32_t offMs = 150 - ((65 / LED_COUNT) * ledProgress); + uint32_t onMs = 200 - ((25 / LED_COUNT) * ledProgress); + // the 'endled' is the tip of the reset progress bar, since the progress + // bar starts full red and empties down to the pinky that means it is + // inverted from the 'ledProgress' which starts at 0 and grows + LedPos endLed = (LedPos)(LED_LAST - ledProgress); + // clear all the leds so that 'blinkRange' will blink from off to the designated color + Leds::clearRange(LED_FIRST, LED_LAST); + // blink to the calculated redish hue from pinky to the end led + Leds::blinkRange(LED_FIRST, endLed, offMs, onMs, HSVColor(0, 255 - (progress * 170), 180)); + // and blink the background the regular blank color + Leds::blinkRange((LedPos)(endLed + 1), LED_LAST, offMs, onMs, RGB_WHITE0); } diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.h b/VortexEngine/src/Menus/MenuList/FactoryReset.h index e1fd64414e..b74a6b140b 100644 --- a/VortexEngine/src/Menus/MenuList/FactoryReset.h +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.h @@ -14,7 +14,9 @@ class FactoryReset : public Menu // handlers for clicks void onShortClick() override; + void onShortClick2() override; void onLongClick() override; + void onLongClick2() override; private: void showReset(); diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp b/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp index 8772f88b8c..f7638854b2 100644 --- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp @@ -5,7 +5,6 @@ #include "../../Leds/Leds.h" #include "../../Log/Log.h" -// allow the number of brightness options to be adjusted dynamically #define NUM_BRIGHTNESS_OPTIONS (sizeof(m_brightnessOptions) / sizeof(m_brightnessOptions[0])) GlobalBrightness::GlobalBrightness(const RGBColor &col, bool advanced) : @@ -28,7 +27,7 @@ bool GlobalBrightness::init() for (uint8_t i = 0; i < NUM_BRIGHTNESS_OPTIONS; ++i) { if (m_brightnessOptions[i] == Leds::getBrightness()) { // make sure the default selection matches cur value - m_curSelection = i; + m_curSelection = (Quadrant)i; } } DEBUG_LOG("Entered global brightness"); @@ -41,10 +40,13 @@ Menu::MenuAction GlobalBrightness::run() if (result != MENU_CONTINUE) { return result; } + // show the current brightness showBrightnessSelection(); - // show selections - Menus::showSelection(); + + // blink the current selection + blinkSelection(); + // continue return MENU_CONTINUE; } @@ -52,7 +54,17 @@ Menu::MenuAction GlobalBrightness::run() void GlobalBrightness::onShortClick() { // include one extra option for the exit slot - m_curSelection = (m_curSelection + 1) % (NUM_BRIGHTNESS_OPTIONS + 1); + m_curSelection = (Quadrant)(((uint32_t)m_curSelection + 1) % (NUM_BRIGHTNESS_OPTIONS + 1)); +} + +void GlobalBrightness::onShortClick2() +{ + // four options in global brightness + if (!m_curSelection) { + m_curSelection = QUADRANT_LAST; + } else { + m_curSelection = m_curSelection - 1; + } } void GlobalBrightness::onLongClick() @@ -68,11 +80,15 @@ void GlobalBrightness::onLongClick() leaveMenu(true); } +void GlobalBrightness::onLongClick2() +{ + leaveMenu(); +} + void GlobalBrightness::showBrightnessSelection() { - if (m_curSelection >= NUM_BRIGHTNESS_OPTIONS) { - showExit(); - return; + // display brightnesses on each finger + for (Quadrant f = QUADRANT_FIRST; f <= QUADRANT_4; ++f) { + Leds::setQuadrant(f, HSVColor(0, 0, m_brightnessOptions[f])); } - Leds::setAll(HSVColor(38, 255, m_brightnessOptions[m_curSelection])); } diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h index 34f2b65cfc..f0f7dba62e 100644 --- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h @@ -14,7 +14,9 @@ class GlobalBrightness : public Menu // handlers for clicks void onShortClick() override; + void onShortClick2() override; void onLongClick() override; + void onLongClick2() override; private: void showBrightnessSelection(); diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp index e3391c1df2..4af155cb60 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp @@ -16,7 +16,9 @@ ModeSharing::ModeSharing(const RGBColor &col, bool advanced) : Menu(col, advanced), m_sharingMode(ModeShareState::SHARE_RECEIVE), - m_timeOutStartTime(0) + m_timeOutStartTime(0), + m_lastSendTime(0), + m_shouldEndSend(false) { } @@ -78,6 +80,29 @@ void ModeSharing::onShortClick() beginSendingIR(); DEBUG_LOG("Switched to send mode"); break; + case ModeShareState::SHARE_SEND_IR: + if (!IRSender::isSending()) { + beginReceivingIR(); + DEBUG_LOG("Switched to send mode"); + } else { + m_shouldEndSend = true; + } + break; + default: + break; + } + Leds::clearAll(); +} + +void ModeSharing::onShortClick2() +{ + switch (m_sharingMode) { + case ModeShareState::SHARE_RECEIVE: + // click while on receive -> end receive, start sending + IRReceiver::endReceiving(); + beginSendingVL(); + DEBUG_LOG("Switched to send mode"); + break; default: break; } @@ -90,6 +115,11 @@ void ModeSharing::onLongClick() leaveMenu(true); } +void ModeSharing::onLongClick2() +{ + leaveMenu(); +} + void ModeSharing::beginSendingVL() { // if the sender is sending then cannot start again @@ -115,12 +145,14 @@ void ModeSharing::beginSendingIR() return; } m_sharingMode = ModeShareState::SHARE_SEND_IR; + Leds::clearAll(); + Leds::update(); // initialize it with the current mode data IRSender::loadMode(Modes::curMode()); // send the first chunk of data, leave if we're done if (!IRSender::send()) { - // when send has completed, stores time that last action was completed to calculate interval between sends - beginReceivingIR(); + // just set the last time and wait + m_lastSendTime = Time::getCurtime(); } } @@ -140,11 +172,19 @@ void ModeSharing::continueSendingIR() { // if the sender isn't sending then nothing to do if (!IRSender::isSending()) { + if (m_lastSendTime && m_lastSendTime < (Time::getCurtime() + MS_TO_TICKS(350))) { + if (m_shouldEndSend) { + beginReceivingIR(); + m_shouldEndSend = false; + } else { + beginSendingIR(); + } + } return; } if (!IRSender::send()) { - // when send has completed, stores time that last action was completed to calculate interval between sends - beginReceivingIR(); + // just set the last time and wait + m_lastSendTime = Time::getCurtime(); } } @@ -193,7 +233,7 @@ void ModeSharing::showSendModeVL() void ModeSharing::showSendModeIR() { // show a dim color when not sending - Leds::clearAll(); + Leds::setAll(RGB_CYAN1); } void ModeSharing::showReceiveMode() diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.h b/VortexEngine/src/Menus/MenuList/ModeSharing.h index dc5adf2357..66ccc4fe3c 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.h +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.h @@ -14,7 +14,9 @@ class ModeSharing : public Menu // handlers for clicks void onShortClick() override; + void onShortClick2() override; void onLongClick() override; + void onLongClick2() override; private: void beginSendingVL(); @@ -38,6 +40,10 @@ class ModeSharing : public Menu // the start time when checking for timing out uint32_t m_timeOutStartTime; + uint32_t m_lastSendTime; + + // whether to end the next send and go back to receive + bool m_shouldEndSend; }; #endif diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.cpp b/VortexEngine/src/Menus/MenuList/PatternSelect.cpp index 9f4e1582af..dc7027707c 100644 --- a/VortexEngine/src/Menus/MenuList/PatternSelect.cpp +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.cpp @@ -1,22 +1,20 @@ #include "PatternSelect.h" #include "../../Patterns/PatternBuilder.h" -#include "../../Patterns/PatternArgs.h" #include "../../Patterns/Pattern.h" -#include "../../Serial/ByteStream.h" #include "../../Time/TimeControl.h" -#include "../../Buttons/Button.h" -#include "../../Random/Random.h" #include "../../Time/Timings.h" -#include "../../Modes/Modes.h" +#include "../../Buttons/Button.h" #include "../../Menus/Menus.h" +#include "../../Modes/Modes.h" +#include "../../Modes/Mode.h" #include "../../Leds/Leds.h" #include "../../Log/Log.h" PatternSelect::PatternSelect(const RGBColor &col, bool advanced) : Menu(col, advanced), + m_state(STATE_PICK_LIST), m_newPatternID(PATTERN_FIRST), - m_srcLed(LED_FIRST), m_started(false) { } @@ -30,6 +28,7 @@ bool PatternSelect::init() if (!Menu::init()) { return false; } + m_state = STATE_PICK_LIST; DEBUG_LOG("Entered pattern select"); return true; } @@ -40,21 +39,85 @@ Menu::MenuAction PatternSelect::run() if (result != MENU_CONTINUE) { return result; } - // run the current mode - m_previewMode.play(); - // show dimmer selections in advanced mode - Menus::showSelection(RGB_WHITE5); + + switch (m_state) { + case STATE_PICK_LIST: + // display lists + showListSelection(); + blinkSelection(); + break; + case STATE_PICK_PATTERN: + // display patterns + showPatternSelection(); + break; + } + + // show selections + Menus::showSelection(); return MENU_CONTINUE; } +void PatternSelect::showListSelection() +{ + Leds::clearAll(); + for (Quadrant quad = QUADRANT_FIRST; quad < QUADRANT_LAST; ++quad) { + // hue split into 4 quadrants of 90 + Leds::breatheRange(quadrantFirstLed(quad), quadrantLastLed(quad), quad * (255/4), (uint32_t)Time::getCurtime() / 3, 10, 255, 255); + } + Leds::setQuadrant(QUADRANT_5, RGB_WHITE6); +} + +void PatternSelect::showPatternSelection() +{ + m_previewMode.play(); + if (g_pButton->isPressed() && g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS) { + Leds::setAll(RGB_WHITE4); + } +} + void PatternSelect::onLedSelected() { - m_srcLed = ledmapGetFirstLed(m_targetLeds); + // need to ready up the preview mode for picking patterns, this can look different based on + // which pattern was already on this mode, and which leds they decided to pick + // for example if they had a multi-led pattern and they are targetting some grouping of singles now + // then we need to convert the multi into singles, maybe in the future we can allow singles to overlay + if (m_previewMode.isMultiLed() && m_targetLeds != MAP_LED_ALL && m_targetLeds != MAP_LED(LED_MULTI)) { + Colorset curSet = m_previewMode.getColorset(); + m_previewMode.setPattern(PATTERN_FIRST, LED_ALL_SINGLE, nullptr, &curSet); + // todo: clear multi a better way, automatically when setting singles? + m_previewMode.clearPattern(LED_MULTI); + m_previewMode.init(); + DEBUG_LOG("Converted existing multi-led pattern to singles for given led selection"); + } } void PatternSelect::onShortClick() { - nextPattern(); + switch (m_state) { + case STATE_PICK_LIST: + // wrap at the index back to pinkie + m_curSelection = (Quadrant)((m_curSelection + 1) % QUADRANT_COUNT); + break; + case STATE_PICK_PATTERN: + nextPattern(); + break; + } +} + +void PatternSelect::onShortClick2() +{ + switch (m_state) { + case STATE_PICK_LIST: + if (m_curSelection > QUADRANT_FIRST) { + m_curSelection = (Quadrant)((m_curSelection - 1)); + } else { + m_curSelection = QUADRANT_LAST; + } + break; + case STATE_PICK_PATTERN: + previousPattern(); + break; + } } void PatternSelect::nextPatternID() @@ -101,16 +164,101 @@ void PatternSelect::nextPattern() DEBUG_LOGF("Iterated to pattern id %d", m_newPatternID); } +void PatternSelect::previousPatternID() +{ + // increment to next pattern + PatternID endList = PATTERN_SINGLE_LAST; + PatternID beginList = PATTERN_SINGLE_FIRST; +#if VORTEX_SLIM == 0 + // if targeted multi led or all singles, iterate through multis + if ((m_targetLeds == MAP_LED_ALL) || (m_targetLeds == MAP_LED(LED_MULTI))) { + endList = PATTERN_MULTI_LAST; + } + // if targeted multi then start at multis and only iterate multis + if ((m_targetLeds == MAP_LED(LED_MULTI))) { + beginList = PATTERN_MULTI_FIRST; + } +#endif + if (m_newPatternID > beginList) { + m_newPatternID = (PatternID)(m_newPatternID - 1); + } else { + m_newPatternID = endList; + } +} + +void PatternSelect::previousPattern() +{ + if (m_started) { + previousPatternID(); + } else { + m_started = true; + // Do not modify m_newPatternID Here! It has been set in the long click handler + // to be the start of the list we want to iterate + } + // set the new pattern id + if (isMultiLedPatternID(m_newPatternID)) { + m_previewMode.setPattern(m_newPatternID); + } else { + // if the user selected multi then just put singles on all leds + LedMap setLeds = (m_targetLeds == MAP_LED(LED_MULTI)) ? LED_ALL : m_targetLeds; + m_previewMode.setPatternMap(setLeds, m_newPatternID); + // TODO: clear multi a better way + m_previewMode.clearPattern(LED_MULTI); + } + m_previewMode.init(); + DEBUG_LOGF("Iterated to pattern id %d", m_newPatternID); +} + void PatternSelect::onLongClick() { bool needsSave = false; - Mode *cur = Modes::curMode(); - needsSave = !cur || !cur->equals(&m_previewMode); - if (needsSave) { - // update the current mode with the new pattern - Modes::updateCurMode(&m_previewMode); - } - DEBUG_LOGF("Saving pattern %u", m_newPatternID); - // done in the pattern select menu - leaveMenu(needsSave); + switch (m_state) { + case STATE_PICK_LIST: + if (m_curSelection == QUADRANT_5) { + leaveMenu(); + return; + } + // if targeted multi then start at multis and only iterate multis + if ((m_targetLeds == MAP_LED(LED_MULTI))) { + // the selected multi only iterate multis + m_newPatternID = (PatternID)(PATTERN_MULTI_FIRST + (m_curSelection * (PATTERN_MULTI_COUNT / 4))); + } else if ((m_targetLeds != MAP_LED_ALL)) { + // they selected some singles, only iterate single led patterns + m_newPatternID = (PatternID)(PATTERN_SINGLE_FIRST + (m_curSelection * (PATTERN_SINGLE_COUNT / 4))); + } else { + // otherwise they selected all divide the entire list + m_newPatternID = (PatternID)(PATTERN_FIRST + (m_curSelection * (PATTERN_COUNT / 4))); + } + m_state = STATE_PICK_PATTERN; + break; + case STATE_PICK_PATTERN: + // need to save the new pattern if it's different from current + needsSave = !Modes::curMode()->equals(&m_previewMode); + if (needsSave) { + // update the current mode with the new pattern + Modes::updateCurMode(&m_previewMode); + } + DEBUG_LOGF("Saving pattern %u", m_newPatternID); + // go back to beginning for next time + m_state = STATE_PICK_LIST; + // done in the pattern select menu + leaveMenu(needsSave); + break; + } + // reset selection after choosing anything + m_curSelection = QUADRANT_FIRST; +} + +void PatternSelect::onLongClick2() +{ + leaveMenu(false); +} + +void PatternSelect::showExit() +{ + // don't show the exit when picking pattern + if (m_state == STATE_PICK_PATTERN) { + return; + } + Menu::showExit(); } diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.h b/VortexEngine/src/Menus/MenuList/PatternSelect.h index 5355b65e61..ab3e5bf21d 100644 --- a/VortexEngine/src/Menus/MenuList/PatternSelect.h +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.h @@ -21,17 +21,34 @@ class PatternSelect : public Menu // handlers for clicks void onShortClick() override; void onLongClick() override; + void onShortClick2() override; + void onLongClick2() override; private: + void showListSelection(); + void showPatternSelection(); void nextPatternID(); void nextPattern(); + void previousPatternID(); + void previousPattern(); + + void showExit() override; + + // private enumeration for internal state of pattern selection + enum PatternSelectState : uint32_t + { + // currently picking the list of patterns + STATE_PICK_LIST, + // currently picking a pattern in the list + STATE_PICK_PATTERN + }; + + // the current state of the pattern selection menu + PatternSelectState m_state; // the patternid of the current demo PatternID m_newPatternID; - // helpful member - LedPos m_srcLed; - // the pat select starts by showing the current pattern // then the first click begin cycling the list of pats bool m_started; diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.cpp b/VortexEngine/src/Menus/MenuList/Randomizer.cpp index 608cb3980e..263272e429 100644 --- a/VortexEngine/src/Menus/MenuList/Randomizer.cpp +++ b/VortexEngine/src/Menus/MenuList/Randomizer.cpp @@ -14,6 +14,26 @@ #include "../../Leds/Leds.h" #include "../../Log/Log.h" +static LedMap maps[] = { + MAP_ALL_FACE, + MAP_ALL_BOT, + MAP_ALL_TOP, + MAP_LINE_1, + MAP_LINE_2, + MAP_QUADRANT_1, + MAP_QUADRANT_2, + MAP_QUADRANT_3, + MAP_QUADRANT_4, + MAP_RINGS_EVEN, + MAP_RINGS_ODD, + MAP_RING_INNER, + MAP_RING_MIDDLE, + MAP_RING_OUTER, + MAP_RING_EDGE +}; + +#define NUM_MAPS (sizeof(maps) / sizeof(maps[0])) + Randomizer::Randomizer(const RGBColor &col, bool advanced) : Menu(col, advanced), m_lastRandomization(0), @@ -173,6 +193,11 @@ void Randomizer::showRandomizationSelect() #if VORTEX_SLIM == 0 bool Randomizer::reRollMulti() { + // 50% chance to roll a 'split multi' which is a random mapping of singles half + // being one pattern/color and the other half being another pattern/color + if (m_multiRandCtx.next8() > 128) { + return splitMultiRandomize(); + } if (m_flags & RANDOMIZE_PATTERN) { // TODO: Roll custom multi pattern? //if (m_advanced) { @@ -199,6 +224,40 @@ PatternID Randomizer::rollMultiLedPatternID(Random &ctx) { return (PatternID)ctx.next8(PATTERN_MULTI_FIRST, PATTERN_MULTI_LAST); } + +bool Randomizer::splitMultiRandomize() +{ + // clear any existing patterns just in case a multi is set + m_previewMode.clearPattern(); + // generate random bitmap of which leds to set + LedMap randomMap = maps[m_multiRandCtx.next8(0, (NUM_MAPS - 1))]; + // roll all of one side of the map one way + if (!rollSinglesLedMap(m_multiRandCtx, randomMap)) { + ERROR_LOG("Failed to roll first half of split multi map"); + return false; + } + // roll the flipped map with new singles + if (!rollSinglesLedMap(m_multiRandCtx, MAP_INVERSE(randomMap))) { + ERROR_LOG("Failed to roll first half of split multi map"); + return false; + } + return true; +} + +bool Randomizer::rollSinglesLedMap(Random &ctx, LedMap map) +{ + // apply pattern2 and colorset2 to the inverse bitmap + PatternID pat = rollSingleLedPatternID(ctx); + Colorset set = rollColorset(ctx); + MAP_FOREACH_LED(map) { + // apply the pattern and colorset + if (!m_previewMode.setPattern(pat, pos, nullptr, &set)) { + ERROR_LOG("Failed to apply pattern or colorset"); + return false; + } + } + return true; +} #endif bool Randomizer::reRollSingles() diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.h b/VortexEngine/src/Menus/MenuList/Randomizer.h index 0575ab132f..c14e8d9843 100644 --- a/VortexEngine/src/Menus/MenuList/Randomizer.h +++ b/VortexEngine/src/Menus/MenuList/Randomizer.h @@ -66,6 +66,8 @@ class Randomizer : public Menu #if VORTEX_SLIM == 0 bool reRollMulti(); PatternID rollMultiLedPatternID(Random &ctx); + bool splitMultiRandomize(); + bool rollSinglesLedMap(Random &ctx, LedMap map); #endif bool reRollSingles(); PatternID rollSingleLedPatternID(Random &ctx); diff --git a/VortexEngine/src/Menus/Menus.cpp b/VortexEngine/src/Menus/Menus.cpp index abeaffa074..aeeddc4e98 100644 --- a/VortexEngine/src/Menus/Menus.cpp +++ b/VortexEngine/src/Menus/Menus.cpp @@ -120,6 +120,24 @@ bool Menus::runMenuSelection() Leds::clearAll(); return true; } + if (g_pButton2->onShortClick()) { + // increment down the menu list and wrap at 0 + if (!m_selection) { + m_selection = MENU_COUNT - 1; + } else { + --m_selection; + } +#if ENABLE_EDITOR_CONNECTION == 1 + // Hide the editor connection menu because it opens automatically + // TODO: Create a better way to hide this menu color, this menu + // will automatically open when the device is plugged in + if (m_selection == MENU_EDITOR_CONNECTION) { + m_selection--; + } +#endif + Leds::clearAll(); + return true; + } // clear the leds so it always fills instead of replacing Leds::clearAll(); // timings for blink later @@ -151,6 +169,13 @@ bool Menus::runMenuSelection() ontime = HYPERSTROBE_ON_DURATION; } } + // if you long click the 2nd button exit the menu list + if (g_pButton2->onLongClick()) { + // close the current menu when run returns false + closeCurMenu(); + // return false to let the modes play + return false; + } // blink every even/odd of every pair for (Pair p = PAIR_FIRST; p < PAIR_COUNT; ++p) { if (pairEven(p) < LED_COUNT) { @@ -197,6 +222,12 @@ bool Menus::runCurMenu() if (g_pButton->onLongClick()) { m_pCurMenu->onLongClick(); } + if (g_pButton2->onShortClick()) { + m_pCurMenu->onShortClick2(); + } + if (g_pButton2->onLongClick()) { + m_pCurMenu->onLongClick2(); + } break; case Menu::MENU_SKIP: // dont run click handlers in this case diff --git a/VortexEngine/src/Modes/DefaultModes.cpp b/VortexEngine/src/Modes/DefaultModes.cpp index a0f4be8b7c..82c2e75747 100644 --- a/VortexEngine/src/Modes/DefaultModes.cpp +++ b/VortexEngine/src/Modes/DefaultModes.cpp @@ -6,7 +6,14 @@ // the gloveset upon factory reset const default_mode_entry default_modes[MAX_MODES] = { { - PATTERN_DOPS, 5, { + PATTERN_VORTEX, MAP_LED_ALL, 5, { + RGB_RED, + RGB_GREEN, + RGB_BLUE, + 0xABAA00, + 0x5500AB + }, + PATTERN_VORTEX, MAP_LED_NONE, 5, { RGB_RED, RGB_GREEN, RGB_BLUE, @@ -14,41 +21,176 @@ const default_mode_entry default_modes[MAX_MODES] = { 0x5500AB } }, + { - PATTERN_GHOSTCRUSH, 5, { - RGB_WHITE, - RGB_WHITE, - RGB_OFF, - 0x700000, - RGB_OFF, + PATTERN_GAPCYCLE, MAP_LED_ALL, 4, { + 0x00553A, + 0x00CFFF, + 0x55001D, + 0x551200 + }, + PATTERN_GAPCYCLE, MAP_LED_NONE, 4, { + HSV(114, 255, 43), + HSV(159, 255, 128), + HSV(240, 255, 43), + HSV(9, 255, 43) } }, + { - PATTERN_WARPWORM, 2, { - RGB_GREEN, - 0x26004B, + PATTERN_TRACER, MAP_RINGS_EVEN, 3, { + 0x62007F, + 0x62007F, + 0x33FE00 + }, + PATTERN_BLENDSTROBE, MAP_RINGS_ODD, 8, { + 0xFF0019, + 0x553600, + 0xABFF00, + 0x005502, + 0x00FFBA, + 0x003155, + 0x1E00FF, + 0x460055 } }, + { - PATTERN_PULSISH, 3, { - 0x00AB55, - 0x8D1C55, - 0x00001C + PATTERN_BLENDSTROBEGAP, MAP_LINE_1, 8, { + 0xFFAE00, + 0x3D5500, + 0x1BFF00, + 0x00552A, + 0x00E7FF, + 0x001955, + 0x4E00FF, + 0x4e0055 + }, + PATTERN_GHOSTCRUSH, MAP_LINE_2, 8, { + 0xFFAE00, + 0x3D5500, + 0x1BFF00, + 0x00552A, + 0x00E7FF, + 0x001955, + 0x4E00FF, + 0x4e0055 } }, { - PATTERN_ZIGZAG, 6, { - RGB_OFF, - 0x56D400, - 0x5500AB, - RGB_OFF, - RGB_RED, - 0x700000 + PATTERN_BLENDSTROBE, MAP_RINGS_EVEN, 6, { + 0x554A00, + 0x554A00, + 0x00AA90, + 0x00AA90, + 0xD200FF, + 0xD200FF + }, + PATTERN_DOUBLEDOPS, MAP_RINGS_ODD, 8, { + 0xFF0033, + 0xFF0033, + 0xDB2F00, + 0xB76B00, + 0x938D00, + 0x4A6F00, + 0x164B00, + 0x002702 } }, + { - PATTERN_STROBE, 8, { + PATTERN_CHASER, MAP_LED_ALL, 5, { + 0x9FFF00, + 0x00FF66, + 0x009FFF, + 0x5A00FF, + 0xFF009F + }, + PATTERN_CHASER, MAP_LED_NONE, 5, { + HSV(0, 0, 255), + HSV(0, 0, 255), + HSV(0, 0, 0), + HSV(140, 255, 170), + HSV(0, 0, 0) + } + }, + + { + PATTERN_METEOR, MAP_LED_ALL, 5, { + 0x00AA90, + 0x00B1FF, + 0x300055, + 0xFF002D, + 0x541B00 + }, + PATTERN_METEOR, MAP_LED_NONE, 5, { + HSV(0, 0, 255), + HSV(0, 0, 255), + HSV(0, 0, 0), + HSV(170, 255, 170), + HSV(0, 0, 0) + } + }, + + { + PATTERN_DRIP, MAP_LED_ALL, 5, { + 0x0000FF, + 0x400055, + 0x54000B, + 0x525400, + 0x26AA00 + }, + PATTERN_DRIP, MAP_LED_NONE, 5, { + HSV(0, 0, 255), + HSV(0, 0, 255), + HSV(0, 0, 0), + HSV(200, 255, 170), + HSV(0, 0, 0) + } + }, + + { + PATTERN_HYPERSTROBE, MAP_LED_ALL, 2, { + 0xFFF600, + 0x000880 + }, + PATTERN_HYPERSTROBE, MAP_LED_NONE, 5, { + HSV(0, 0, 255), + HSV(0, 0, 255), + HSV(0, 0, 0), + HSV(230, 255, 170), + HSV(0, 0, 0) + } + }, + + { + PATTERN_SPARKLETRACE, MAP_LED_ALL, 6, { + 0x003755, + 0x003755, + 0xAA0072, + 0xAA0072, + 0xA5FF00, + 0xA5FF00 + }, + PATTERN_SPARKLETRACE, MAP_LED_NONE, 5, { + HSV(0, 0, 255), + HSV(0, 0, 255), + HSV(0, 0, 0), + HSV(0, 255, 170), + HSV(0, 0, 0) + } + }, + + { + PATTERN_DASHDOPS, MAP_LED_ALL, 5, { + 0xE2E7FF, + 0x010015, + 0x13090E, + 0x010015, + 0xE2E7FF + }, + PATTERN_DASHDOPS, MAP_LED_NONE, 8, { 0xD4002B, RGB_OFF, 0x0056AA, @@ -59,15 +201,9 @@ const default_mode_entry default_modes[MAX_MODES] = { RGB_OFF } }, + { - PATTERN_SNOWBALL, 3, { - 0x170600, - 0x00840A, - 0x12002A - } - }, - { - PATTERN_ULTRADOPS, 8, { + PATTERN_ULTRADOPS, MAP_LED_ALL, 8, { 0x1C0000, 0x4B2600, 0xABAA00, @@ -76,30 +212,30 @@ const default_mode_entry default_modes[MAX_MODES] = { 0x00001C, 0x26004B, 0x13000A - } - }, - { - PATTERN_VORTEX, 4, { - 0xAA0055, - 0x7070C5, - 0x0A0013, - 0x1C8E55, - } - }, - { - PATTERN_VORTEXWIPE, 8, { - RGB_RED, - 0x00001C, - 0x00001C, - 0x00001C, - 0x00001C, - 0x00001C, - 0x00001C, + }, + PATTERN_ULTRADOPS, MAP_LED_NONE, 8, { + 0x1C0000, + 0x4B2600, + 0xABAA00, + 0x001C00, + 0x00130A, 0x00001C, + 0x26004B, + 0x13000A } }, + { - PATTERN_GHOSTCRUSH, 7, { + PATTERN_GHOSTCRUSH, MAP_LED_ALL, 7, { + 0x26004B, + RGB_OFF, + RGB_GREEN, + RGB_WHITE, + RGB_GREEN, + RGB_OFF, + 0x26004B, + }, + PATTERN_GHOSTCRUSH, MAP_LED_NONE, 7, { 0x26004B, RGB_OFF, RGB_GREEN, @@ -109,15 +245,14 @@ const default_mode_entry default_modes[MAX_MODES] = { 0x26004B, } }, + { - PATTERN_VORTEXWIPE, 3, { - 0x00AB55, - 0x7F0081, - 0xAA381C, - } - }, - { - PATTERN_COMPLEMENTARY_BLEND, 3, { + PATTERN_COMPLEMENTARY_BLEND, MAP_LED_ALL, 3, { + RGB_RED, + RGB_GREEN, + RGB_BLUE + }, + PATTERN_COMPLEMENTARY_BLEND, MAP_LED_NONE, 3, { RGB_RED, RGB_GREEN, RGB_BLUE diff --git a/VortexEngine/src/Modes/DefaultModes.h b/VortexEngine/src/Modes/DefaultModes.h index 888e4fb7bf..a854ed65b6 100644 --- a/VortexEngine/src/Modes/DefaultModes.h +++ b/VortexEngine/src/Modes/DefaultModes.h @@ -3,13 +3,19 @@ #include "../Patterns/Patterns.h" #include "../VortexConfig.h" +#include "../Leds/LedTypes.h" // structure of the entries in the default modes array struct default_mode_entry { PatternID patternID; + LedMap map; uint8_t numColors; uint32_t cols[MAX_COLOR_SLOTS]; + PatternID patternID2; + LedMap map2; + uint8_t numColors2; + uint32_t cols2[MAX_COLOR_SLOTS]; }; // exposed global array of default modes diff --git a/VortexEngine/src/Modes/Modes.cpp b/VortexEngine/src/Modes/Modes.cpp index a81a6d72db..551888be14 100644 --- a/VortexEngine/src/Modes/Modes.cpp +++ b/VortexEngine/src/Modes/Modes.cpp @@ -85,6 +85,10 @@ void Modes::play() if (g_pButton->onShortClick()) { nextMode(); } + // shortclick on button 2 cycles to the previous mode + if (g_pButton2->onShortClick()) { + previousMode(); + } // play the current mode m_pCurModeLink->play(); } @@ -390,8 +394,16 @@ bool Modes::setDefaults() // add each default mode with each of the given colors for (uint8_t i = 0; i < num_default_modes; ++i) { const default_mode_entry &def = default_modes[i]; - Colorset set(def.numColors, def.cols); - addMode(def.patternID, nullptr, &set); + Colorset set1(def.numColors, def.cols); + if (isMultiLedPatternID(def.patternID)) { + addMode(def.patternID, nullptr, &set1); + } else { + Mode tempMode; + tempMode.setPatternMap(def.map, def.patternID, nullptr, &set1); + Colorset set2(def.numColors2, def.cols2); + tempMode.setPatternMap(def.map2, def.patternID2, nullptr, &set2); + addMode(&tempMode); + } } return true; } diff --git a/VortexEngine/src/Patterns/Multi/BouncePattern.cpp b/VortexEngine/src/Patterns/Multi/BouncePattern.cpp index a4afaa1f71..9631005c1d 100644 --- a/VortexEngine/src/Patterns/Multi/BouncePattern.cpp +++ b/VortexEngine/src/Patterns/Multi/BouncePattern.cpp @@ -6,7 +6,7 @@ #include "../../Log/Log.h" // safety to prevent divide by 0 -#define TOTAL_STEPS (((PAIR_COUNT * 2) - 2) ? ((PAIR_COUNT * 2) - 2) : 1) +#define TOTAL_STEPS ((((PAIR_COUNT + 2) * 2) - 2) ? (((PAIR_COUNT + 2) * 2) - 2) : 1) #define HALF_STEPS (TOTAL_STEPS / 2) BouncePattern::BouncePattern(const PatternArgs &args) : @@ -36,10 +36,12 @@ void BouncePattern::init() void BouncePattern::blinkOn() { Leds::setAll(m_colorset.cur()); - if (m_progress < PAIR_COUNT) { - Leds::setPair((Pair)m_progress, m_colorset.peekNext()); - } else { - Leds::setPair((Pair)(TOTAL_STEPS - m_progress), m_colorset.peekNext()); + if (m_progress != 0 && m_progress != HALF_STEPS) { + if (m_progress < (PAIR_COUNT + 1)) { + Leds::setPair((Pair)(m_progress - 1), m_colorset.peekNext()); + } else { + Leds::setPair((Pair)(TOTAL_STEPS - (m_progress + 1)), m_colorset.peekNext()); + } } } diff --git a/VortexEngine/src/Patterns/Multi/CompoundPattern.cpp b/VortexEngine/src/Patterns/Multi/CompoundPattern.cpp index c69c1684ac..5b95168415 100644 --- a/VortexEngine/src/Patterns/Multi/CompoundPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/CompoundPattern.cpp @@ -84,8 +84,8 @@ void CompoundPattern::setEvensOdds(PatternID evenPattern, PatternID oddPattern, { // Set the evenPattern on all evens and oddPattern on all odds for (LedPos p = LED_FIRST; p <= LED_LAST; p++) { - const PatternArgs *args = isEven(p) ? evenArgs : oddArgs; - PatternID id = isEven(p) ? evenPattern : oddPattern; + const PatternArgs *args = isOrbitEven(p) ? evenArgs : oddArgs; + PatternID id = isOrbitEven(p) ? evenPattern : oddPattern; setPatternAt(p, PatternBuilder::makeSingle(id, args)); } } diff --git a/VortexEngine/src/Patterns/Multi/CrossDopsPattern.cpp b/VortexEngine/src/Patterns/Multi/CrossDopsPattern.cpp index 98ecfe4c78..3b4e2faa13 100644 --- a/VortexEngine/src/Patterns/Multi/CrossDopsPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/CrossDopsPattern.cpp @@ -19,7 +19,7 @@ void CrossDopsPattern::init() { BlinkStepPattern::init(); // Alternating evens and odds mapping of leds to turn on/off - m_ledMap = MAP_PAIR_EVENS; + m_ledMap = MAP_LINE_1; // start colorset at index 0 so cur() works m_colorset.setCurIndex(0); } diff --git a/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.cpp b/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.cpp index 577a514adc..57b4d7ca04 100644 --- a/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.cpp +++ b/VortexEngine/src/Patterns/Multi/DoubleStrobePattern.cpp @@ -24,6 +24,6 @@ void DoubleStrobePattern::init() void DoubleStrobePattern::blinkOn() { - Leds::setAllOdds(m_colorset.cur()); - Leds::setAllEvens(m_colorset.peekNext()); + Leds::setMap(MAP_RINGS_ODD, m_colorset.cur()); + Leds::setMap(MAP_RINGS_EVEN, m_colorset.peekNext()); } diff --git a/VortexEngine/src/Patterns/Multi/DripMorphPattern.cpp b/VortexEngine/src/Patterns/Multi/DripMorphPattern.cpp index bc940ba11a..9052381fe7 100644 --- a/VortexEngine/src/Patterns/Multi/DripMorphPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/DripMorphPattern.cpp @@ -74,8 +74,8 @@ void DripMorphPattern::blinkOn() // always over/under shoot m_cur.hue += m_speed * sign; // set the target led with the current HSV color - Leds::setAllEvens(m_cur); - Leds::setAllOdds(m_colorset.cur()); + Leds::setMap(MAP_RINGS_EVEN, m_cur); + Leds::setMap(MAP_RINGS_ODD, m_colorset.cur()); } void DripMorphPattern::blinkOff() diff --git a/VortexEngine/src/Patterns/Multi/DripPattern.cpp b/VortexEngine/src/Patterns/Multi/DripPattern.cpp index a907b641cf..91a29382ff 100644 --- a/VortexEngine/src/Patterns/Multi/DripPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/DripPattern.cpp @@ -26,8 +26,8 @@ void DripPattern::init() void DripPattern::blinkOn() { if (!m_sync) { - Leds::setAllEvens(m_colorset.cur()); - Leds::setAllOdds(m_colorset.peekNext()); + Leds::setMap(MAP_RINGS_EVEN, m_colorset.cur()); + Leds::setMap(MAP_RINGS_ODD, m_colorset.peekNext()); } else { Leds::setAll(m_colorset.cur()); } diff --git a/VortexEngine/src/Patterns/Multi/FillPattern.cpp b/VortexEngine/src/Patterns/Multi/FillPattern.cpp index 87e034e4c1..d5bf230a09 100644 --- a/VortexEngine/src/Patterns/Multi/FillPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/FillPattern.cpp @@ -28,13 +28,14 @@ void FillPattern::init() void FillPattern::blinkOn() { - Leds::setPairs(PAIR_FIRST, (Pair)m_progress, m_colorset.peekNext()); - Leds::setPairs((Pair)m_progress, PAIR_COUNT, m_colorset.cur()); + LedPos edgePos = (LedPos)(m_progress * (LED_COUNT / 4)); + Leds::setRange(LED_FIRST, edgePos, m_colorset.peekNext()); + Leds::setRange(edgePos, LED_LAST, m_colorset.cur()); } void FillPattern::poststep() { - m_progress = (m_progress + 1) % PAIR_COUNT; + m_progress = (m_progress + 1) % 4; if (m_progress == 0) { m_colorset.getNext(); } diff --git a/VortexEngine/src/Patterns/Multi/TheaterChasePattern.cpp b/VortexEngine/src/Patterns/Multi/TheaterChasePattern.cpp index fbd527c7db..14fb294406 100644 --- a/VortexEngine/src/Patterns/Multi/TheaterChasePattern.cpp +++ b/VortexEngine/src/Patterns/Multi/TheaterChasePattern.cpp @@ -2,7 +2,7 @@ #include "../../Leds/Leds.h" -#define THEATER_CHASE_STEPS 10 +#define THEATER_CHASE_STEPS LED_COUNT TheaterChasePattern::TheaterChasePattern(const PatternArgs &args) : BlinkStepPattern(args), @@ -21,7 +21,7 @@ void TheaterChasePattern::init() { BlinkStepPattern::init(); // starts on odd evens - m_ledPositions = MAP_PAIR_ODD_EVENS; + m_ledPositions = MAP_PAIR_EVENS; m_stepCounter = 0; } @@ -32,13 +32,8 @@ void TheaterChasePattern::blinkOn() void TheaterChasePattern::poststep() { - // the first 5 steps are odd evens/odds alternating each step - if (m_stepCounter < 5) { - m_ledPositions = (m_stepCounter % 2) ? MAP_PAIR_ODD_ODDS : MAP_PAIR_ODD_EVENS; - } else { - // the end 5 steps are even evens/odds alternating each step - m_ledPositions = (m_stepCounter % 2) ? MAP_PAIR_EVEN_ODDS : MAP_PAIR_EVEN_EVENS; - } + // the steps are odd evens/odds alternating each step + m_ledPositions = (m_stepCounter % 2) ? MAP_PAIR_ODDS : MAP_PAIR_EVENS; // increment step counter m_stepCounter = (m_stepCounter + 1) % THEATER_CHASE_STEPS; } diff --git a/VortexEngine/src/Patterns/Multi/VortexPattern.cpp b/VortexEngine/src/Patterns/Multi/VortexPattern.cpp index b879687b11..8e94d12019 100644 --- a/VortexEngine/src/Patterns/Multi/VortexPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/VortexPattern.cpp @@ -32,17 +32,13 @@ void VortexPattern::init() void VortexPattern::blinkOn() { - // Sets an LED at opposite ends of the strip and progresses towards the center - Leds::setIndex((LedPos)m_progress, m_colorset.peekNext()); - Leds::setIndex((LedPos)(LED_LAST - m_progress), m_colorset.peekNext()); + Leds::setRing((Ring)(RING_LAST - m_progress), m_colorset.peekNext()); } void VortexPattern::poststep() { - // step till the middle point - m_progress = (m_progress + 1) % MIDDLE_POINT; - // each cycle progress to the next color - if (m_progress == 0) { + m_progress = (m_progress + 1) % RING_COUNT; + if (RING_COUNT - m_progress == RING_COUNT) { m_colorset.getNext(); } } diff --git a/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp b/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp index 1a2b19b185..cffd40d080 100644 --- a/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp +++ b/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp @@ -5,20 +5,6 @@ #include "../../Leds/Leds.h" #include "../../Log/Log.h" -const LedPos VortexWipePattern::ledStepPositions[] = { - LED_9, - LED_7, - LED_5, - LED_3, - LED_1, - - LED_0, - LED_2, - LED_4, - LED_6, - LED_8 -}; - VortexWipePattern::VortexWipePattern(const PatternArgs &args) : BlinkStepPattern(args), m_progress(0) @@ -44,16 +30,16 @@ void VortexWipePattern::init() void VortexWipePattern::blinkOn() { for (int index = 0; index < m_progress; ++index) { - Leds::setIndex(ledStepPositions[index], m_colorset.peekNext()); + Leds::setRing((Ring)index, m_colorset.peekNext()); } - for (int index = m_progress; index < LED_COUNT; ++index) { - Leds::setIndex(ledStepPositions[index], m_colorset.cur()); + for (int index = m_progress; index < RING_COUNT; ++index) { + Leds::setRing((Ring)index, m_colorset.cur()); } } void VortexWipePattern::poststep() { - m_progress = (m_progress + 1) % LED_COUNT; + m_progress = (m_progress + 1) % RING_COUNT; if (m_progress == 0) { m_colorset.getNext(); } diff --git a/VortexEngine/src/Patterns/Multi/VortexWipePattern.h b/VortexEngine/src/Patterns/Multi/VortexWipePattern.h index 17030f3f6d..4795802380 100644 --- a/VortexEngine/src/Patterns/Multi/VortexWipePattern.h +++ b/VortexEngine/src/Patterns/Multi/VortexWipePattern.h @@ -19,9 +19,6 @@ class VortexWipePattern : public BlinkStepPattern virtual void poststep() override; private: - // path for leds to take, index this with m_step up to LED_COUNT steps - static const LedPos ledStepPositions[]; - // how much the fill has progressed uint8_t m_progress; }; diff --git a/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp b/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp index 02062917eb..e11121a3d3 100644 --- a/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp @@ -4,25 +4,8 @@ #include "../../Leds/Leds.h" #include "../../Log/Log.h" -// Mapping of LED positions to steps. -// The lights runs across evens, then back across odds. -// Index this array with m_step in order to get correct LedPos -const LedPos ZigzagPattern::ledStepPositions[] = { - LED_1, - LED_3, - LED_5, - LED_7, - LED_9, - - LED_8, - LED_6, - LED_4, - LED_2, - LED_0, -}; - // There just happens to be LED_COUNT steps in the pattern -#define NUM_ZIGZAG_STEPS (sizeof(ledStepPositions) / sizeof(ledStepPositions[0])) +#define NUM_ZIGZAG_STEPS LED_COUNT #define HALF_ZIGZAG_STEPS (NUM_ZIGZAG_STEPS / 2) ZigzagPattern::ZigzagPattern(const PatternArgs &args) : @@ -60,8 +43,8 @@ void ZigzagPattern::init() m_stepTimer.start(); // initialize the snakes with dops timing - m_snake1.init(m_onDuration, m_offDuration, m_colorset, 0, 0, m_snakeSize, m_fadeAmount, 3); - m_snake2.init(m_onDuration, m_offDuration, m_colorset, 1, HALF_ZIGZAG_STEPS, m_snakeSize, m_fadeAmount, 8); + m_snake1.init(m_onDuration, m_offDuration, m_colorset, 0, 0, m_snakeSize, m_fadeAmount, 4); + m_snake2.init(m_onDuration, m_offDuration, m_colorset, 1, HALF_ZIGZAG_STEPS, m_snakeSize, m_fadeAmount, 7); } // pure virtual must override the play function @@ -159,7 +142,7 @@ void ZigzagPattern::Snake::drawSnake() col.adjustBrightness(m_fadeAmount * segment); } // lookup the target in the step positions array and turn it on with given color/brightness - Leds::setIndex(ledStepPositions[segment_position], col); + Leds::setIndex((LedPos)segment_position, col); // if this segment is on the step where the color changes if (segment_position == m_changeBoundary) { // then decrement the color index for the rest of the snake so that the diff --git a/VortexEngine/src/Patterns/Multi/ZigzagPattern.h b/VortexEngine/src/Patterns/Multi/ZigzagPattern.h index e4e0859daa..863175d1a4 100644 --- a/VortexEngine/src/Patterns/Multi/ZigzagPattern.h +++ b/VortexEngine/src/Patterns/Multi/ZigzagPattern.h @@ -39,10 +39,6 @@ class ZigzagPattern : public MultiLedPattern uint8_t m_fadeAmount; uint8_t m_changeBoundary; }; - - // path for leds to take, index this with m_step up to LED_COUNT steps - static const LedPos ledStepPositions[]; - // blink on duration uint8_t m_onDuration; // blink off duration diff --git a/VortexEngine/src/Serial/Serial.cpp b/VortexEngine/src/Serial/Serial.cpp index 5079e80455..e22067e1d5 100644 --- a/VortexEngine/src/Serial/Serial.cpp +++ b/VortexEngine/src/Serial/Serial.cpp @@ -3,6 +3,7 @@ #include "../Serial/ByteStream.h" #include "../Time/TimeControl.h" #include "../Time/Timings.h" +#include "../Menus/Menus.h" #include "../Log/Log.h" #include "../VortexEngine.h" @@ -12,14 +13,19 @@ #include #endif +#ifdef VORTEX_EMBEDDED +#include +#endif + bool SerialComs::m_serialConnected = false; uint32_t SerialComs::m_lastCheck = 0; +uint32_t SerialComs::m_lastConnected = 0; // init serial bool SerialComs::init() { // Try connecting serial ? - //checkSerial(); + checkSerial(); return true; } @@ -29,9 +35,33 @@ void SerialComs::cleanup() bool SerialComs::isConnected() { +#ifdef VORTEX_EMBEDDED + if (!Serial) { + m_serialConnected = false; + return false; + } + if (!isConnectedReal()) { + return false; + } +#endif return m_serialConnected; } +bool SerialComs::isConnectedReal() +{ +#ifdef VORTEX_EMBEDDED + uint32_t now = Time::getCurtime(); + if (!Serial.usb.connected()) { + m_lastConnected = now; + } else { + if (m_lastConnected && (now - m_lastConnected) > 1800) { + return false; + } + } +#endif + return true; +} + // check for any serial connection or messages bool SerialComs::checkSerial() { @@ -40,6 +70,9 @@ bool SerialComs::checkSerial() // already connected return true; } + if (m_serialConnected) { + return isConnectedReal(); + } uint32_t now = Time::getCurtime(); // don't check for serial too fast if (m_lastCheck && (now - m_lastCheck) < MAX_SERIAL_CHECK_INTERVAL) { @@ -54,17 +87,22 @@ bool SerialComs::checkSerial() Vortex::vcallbacks()->serialBegin(SERIAL_BAUD_RATE); #else // This will check if the serial communication is open - if (!Serial.available()) { + if (!Serial) { // serial is not connected return false; } - // Begin serial communications + // Begin serial communications (turns out this is actually a NO-OP in trinket source) Serial.begin(SERIAL_BAUD_RATE); + if (Menus::curMenuID() != MENU_EDITOR_CONNECTION) { + // directly open the editor connection menu because we are connected to USB serial + Menus::openMenu(MENU_EDITOR_CONNECTION); + } #endif #endif // serial is now connected m_serialConnected = true; - return true; + // rely on the low level 'real' connection now + return isConnectedReal(); } void SerialComs::write(const char *msg, ...) diff --git a/VortexEngine/src/Serial/Serial.h b/VortexEngine/src/Serial/Serial.h index 0121b5bbb0..7ce393361f 100644 --- a/VortexEngine/src/Serial/Serial.h +++ b/VortexEngine/src/Serial/Serial.h @@ -17,6 +17,9 @@ class SerialComs // whether serial is initialized static bool isConnected(); + // why do I need this + static bool isConnectedReal(); + // check for any serial connection or messages static bool checkSerial(); @@ -36,6 +39,7 @@ class SerialComs // whether serial communications are initialized static bool m_serialConnected; static uint32_t m_lastCheck; + static uint32_t m_lastConnected; }; #endif diff --git a/VortexEngine/src/Storage/Storage.cpp b/VortexEngine/src/Storage/Storage.cpp index 357cc6bd3c..a9c51fb41b 100644 --- a/VortexEngine/src/Storage/Storage.cpp +++ b/VortexEngine/src/Storage/Storage.cpp @@ -9,7 +9,7 @@ #include "../Log/Log.h" #ifdef VORTEX_LIB -#include "../VortexLib/VortexLib.h" +#include "VortexLib.h" #endif #ifndef VORTEX_EMBEDDED @@ -29,6 +29,13 @@ std::string Storage::m_storageFilename; #define STORAGE_FILENAME DEFAULT_STORAGE_FILENAME #endif +#ifdef VORTEX_EMBEDDED +#include +#define PAGE_SIZE 64 +__attribute__((__aligned__(256))) +const uint8_t _storagedata[(STORAGE_SIZE+255)/256*256] = { }; +#endif + uint32_t Storage::m_lastSaveSize = 0; Storage::Storage() @@ -69,7 +76,52 @@ bool Storage::write(uint16_t slot, ByteStream &buffer) // just in case buffer.recalcCRC(); #ifdef VORTEX_EMBEDDED - // implement device storage here + // the target slot to store in + uint32_t storage_slot = (uint32_t)_storagedata + (slot * MAX_MODE_SIZE); + + // Number of rows to erase for one slot + uint16_t rows_to_erase = MAX_MODE_SIZE / (PAGE_SIZE * 4); + // Erase only the rows containing the slot + for (uint16_t i = 0; i < rows_to_erase; ++i) { + // Set the address for the row to erase + NVMCTRL->ADDR.reg = (uint32_t)(storage_slot + (i * PAGE_SIZE * 4)) / 2; + // Execute the erase command + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER; + // Wait for the erase operation to complete + while (!NVMCTRL->INTFLAG.bit.READY) {} + } + + // set the last save size + m_lastSaveSize = buffer.size(); + + // write out the buffer to storage + // Calculate data boundaries + uint32_t size = (buffer.rawSize() + 3) / 4; + volatile uint32_t *dst_addr = (volatile uint32_t *)storage_slot; + const uint8_t *src_addr = (uint8_t *)buffer.rawData(); + + // Disable automatic page write + NVMCTRL->CTRLB.bit.MANW = 1; + + // Do writes in pages + while (size) { + // Execute "PBC" Page Buffer Clear + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC; + while (NVMCTRL->INTFLAG.bit.READY == 0) {} + + // Fill page buffer + uint32_t i; + for (i = 0; i < (PAGE_SIZE / 4) && size; i++) { + *dst_addr = *(uint32_t *)(src_addr); + src_addr += sizeof(uint32_t); + dst_addr++; + size--; + } + + // Execute "WP" Write Page + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP; + while (NVMCTRL->INTFLAG.bit.READY == 0) {} + } #elif defined(_WIN32) HANDLE hFile = CreateFile(STORAGE_FILENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { @@ -118,7 +170,8 @@ bool Storage::read(uint16_t slot, ByteStream &buffer) return false; } #ifdef VORTEX_EMBEDDED - // implement device storage here + // read directly into the raw data of the byte array + memcpy(buffer.rawData(), (const void *)(_storagedata + (slot * MAX_MODE_SIZE)), size); #elif defined(_WIN32) HANDLE hFile = CreateFile(STORAGE_FILENAME, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { diff --git a/VortexEngine/src/Time/TimeControl.cpp b/VortexEngine/src/Time/TimeControl.cpp index 8493deb667..4a7a374041 100644 --- a/VortexEngine/src/Time/TimeControl.cpp +++ b/VortexEngine/src/Time/TimeControl.cpp @@ -9,6 +9,10 @@ #include "../Leds/Leds.h" +#ifdef VORTEX_EMBEDDED +#include +#endif + #if !defined(_WIN32) || defined(WASM) #include #include @@ -190,7 +194,7 @@ uint32_t Time::microseconds() void Time::delayMicroseconds(uint32_t us) { -#ifdef _WIN32 +#if defined(VORTEX_EMBEDDED) || defined(_WIN32) uint32_t newtime = microseconds() + us; while (microseconds() < newtime) { // busy loop diff --git a/VortexEngine/src/VortexConfig.h b/VortexEngine/src/VortexConfig.h index dc47583a5c..ef70a3e07f 100644 --- a/VortexEngine/src/VortexConfig.h +++ b/VortexEngine/src/VortexConfig.h @@ -38,7 +38,7 @@ // the engine flavour, this should change for each device/flavour // of the engine that branches off from the main indefinitely -#define VORTEX_NAME "Core" +#define VORTEX_NAME "Orbit" // the full name of this build for ex: // Vortex Engine v1.0 'Igneous' (built Tue Jan 31 19:03:55 2023) @@ -158,7 +158,7 @@ // // The starting default global brightness if there is no savefile // present The maximum value is 255 -#define DEFAULT_BRIGHTNESS 185 +#define DEFAULT_BRIGHTNESS 20 // Max Modes // @@ -176,7 +176,7 @@ // This should not be set to 0, it should be a specific maximum for // each separate device // -#define MAX_MODES 13 +#define MAX_MODES 14 // Default Tickrate in Ticks Per Second (TPS) // @@ -288,9 +288,9 @@ // // These are the four options available in the global brightness menu // There is only four options, be careful not to go too low -#define BRIGHTNESS_OPTION_1 40 -#define BRIGHTNESS_OPTION_2 120 -#define BRIGHTNESS_OPTION_3 185 +#define BRIGHTNESS_OPTION_1 20 +#define BRIGHTNESS_OPTION_2 80 +#define BRIGHTNESS_OPTION_3 160 #define BRIGHTNESS_OPTION_4 255 // Saturation Options @@ -536,7 +536,7 @@ // These are the various storage space constants of the vortex device // maximum size of a mode here -#define MAX_MODE_SIZE 512 +#define MAX_MODE_SIZE 1024 // the number of storage slots for modes, add 1 for the header #define NUM_MODE_SLOTS (MAX_MODES + 1) diff --git a/VortexEngine/src/VortexEngine.cpp b/VortexEngine/src/VortexEngine.cpp index 087931c7d9..bfd51e3b1b 100644 --- a/VortexEngine/src/VortexEngine.cpp +++ b/VortexEngine/src/VortexEngine.cpp @@ -178,6 +178,9 @@ void VortexEngine::runMainLogic() return; } + // check if the device has been plugged in + SerialComs::checkSerial(); + // if the menus are open and running then just return if (Menus::run()) { return; diff --git a/VortexEngine/src/Wireless/IRReceiver.cpp b/VortexEngine/src/Wireless/IRReceiver.cpp index 47ec50ef4d..19f83e1b50 100644 --- a/VortexEngine/src/Wireless/IRReceiver.cpp +++ b/VortexEngine/src/Wireless/IRReceiver.cpp @@ -9,6 +9,10 @@ #include "../Modes/Mode.h" #include "../Log/Log.h" +#ifdef VORTEX_EMBEDDED +#include +#endif + BitStream IRReceiver::m_irData; IRReceiver::RecvState IRReceiver::m_recvState = WAITING_HEADER_MARK; uint32_t IRReceiver::m_prevTime = 0; @@ -17,6 +21,9 @@ uint32_t IRReceiver::m_previousBytes = 0; bool IRReceiver::init() { +#ifdef VORTEX_EMBEDDED + pinMode(IR_RECEIVER_PIN, INPUT_PULLUP); +#endif m_irData.init(IR_RECV_BUF_SIZE); return true; } @@ -83,12 +90,18 @@ bool IRReceiver::receiveMode(Mode *pMode) bool IRReceiver::beginReceiving() { +#ifdef VORTEX_EMBEDDED + attachInterrupt(digitalPinToInterrupt(IR_RECEIVER_PIN), IRReceiver::recvPCIHandler, CHANGE); +#endif resetIRState(); return true; } bool IRReceiver::endReceiving() { +#ifdef VORTEX_EMBEDDED + detachInterrupt(digitalPinToInterrupt(IR_RECEIVER_PIN)); +#endif resetIRState(); return true; } diff --git a/VortexEngine/src/Wireless/IRSender.cpp b/VortexEngine/src/Wireless/IRSender.cpp index 1d127a1978..5ec181188c 100644 --- a/VortexEngine/src/Wireless/IRSender.cpp +++ b/VortexEngine/src/Wireless/IRSender.cpp @@ -11,6 +11,10 @@ #include "VortexLib.h" #endif +#ifdef VORTEX_EMBEDDED +#include +#endif + // the serial buffer for the data ByteStream IRSender::m_serialBuf; // a bit walker for the serial data @@ -30,8 +34,15 @@ uint32_t IRSender::m_blockSize = 0; // write total uint32_t IRSender::m_writeCounter = 0; +#if defined(VORTEX_EMBEDDED) +// Timer used for PWM, is initialized in initpwm() +Tcc *IR_TCCx; +#endif + bool IRSender::init() { + // initialize the IR device + initPWM(); return true; } @@ -111,6 +122,8 @@ void IRSender::beginSend() m_isSending = true; DEBUG_LOGF("[%zu] Beginning send size %u (blocks: %u remainder: %u blocksize: %u)", Time::microseconds(), m_size, m_numBlocks, m_remainder, m_blockSize); + // init sender before writing, is this necessary here? I think so + initPWM(); // wakeup the other receiver with a very quick mark/space sendMark(50); sendSpace(100); @@ -144,6 +157,9 @@ void IRSender::sendMark(uint16_t time) #ifdef VORTEX_LIB // send mark timing over socket Vortex::vcallbacks()->infraredWrite(true, time); +#else + startPWM(); + Time::delayMicroseconds(time); #endif } @@ -152,6 +168,80 @@ void IRSender::sendSpace(uint16_t time) #ifdef VORTEX_LIB // send space timing over socket Vortex::vcallbacks()->infraredWrite(false, time); +#else + stopPWM(); + Time::delayMicroseconds(time); +#endif +} + +// shamelessly stolen from IRLib2, thanks +void IRSender::initPWM() +{ +#if defined(VORTEX_EMBEDDED) + // initialize the output pin + pinMode(IR_SEND_PWM_PIN, OUTPUT); + digitalWrite(IR_SEND_PWM_PIN, LOW); + // setup the PWM + uint8_t port = g_APinDescription[IR_SEND_PWM_PIN].ulPort; // 0 + uint8_t pin = g_APinDescription[IR_SEND_PWM_PIN].ulPin; // 8 + ETCChannel IR_TCC_Channel = TCC0_CH0; + int8_t IR_PER_EorF = PORT_PMUX_PMUXE_E; + //println();Serial.print("Port:"); Serial.print(port,DEC); Serial.print(" Pin:"); Serial.println(pin,DEC); + // Enable the port multiplexer for the PWM channel on pin + PORT->Group[port].PINCFG[pin].bit.PMUXEN = 1; + + // Connect the TCC timer to the port outputs - port pins are paired odd PMUXO and even PMUXEII + // F & E peripherals specify the timers: TCC0, TCC1 and TCC2 + PORT->Group[port].PMUX[pin >> 1].reg |= IR_PER_EorF; + +// pinPeripheral (IR_SEND_PWM_PIN,PIO_TIMER_ALT); + // Feed GCLK0 to TCC0 and TCC1 + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK0 to TCC0 and TCC1 + GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 + GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK0 to TCC0 and TCC1 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + // Normal (single slope) PWM operation: timers countinuously count up to PER + // register value and then is reset to 0 + IR_TCCx = (Tcc*) GetTC(IR_TCC_Channel); + IR_TCCx->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM; // Setup single slope PWM on TCCx + while (IR_TCCx->SYNCBUSY.bit.WAVE); // Wait for synchronization + + // Each timer counts up to a maximum or TOP value set by the PER register, + // this determines the frequency of the PWM operation. + uint32_t cc = F_CPU/(38*1000) - 1; + IR_TCCx->PER.reg = cc; // Set the frequency of the PWM on IR_TCCx + while(IR_TCCx->SYNCBUSY.bit.PER); + + // The CCx register value corresponds to the pulsewidth in microseconds (us) + // Set the duty cycle of the PWM on TCC0 to 33% + IR_TCCx->CC[GetTCChannelNumber(IR_TCC_Channel)].reg = cc/3; + while (IR_TCCx->SYNCBUSY.reg & TCC_SYNCBUSY_MASK); + //while(IR_TCCx->SYNCBUSY.bit.CC3); + + // Enable IR_TCCx timer but do not turn on PWM yet. Will turn it on later. + IR_TCCx->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1; // Divide GCLK0 by 1 + while (IR_TCCx->SYNCBUSY.bit.ENABLE); + IR_TCCx->CTRLA.reg &= ~TCC_CTRLA_ENABLE; //initially off will turn on later + while (IR_TCCx->SYNCBUSY.bit.ENABLE); +#endif +} + +void IRSender::startPWM() +{ +#if defined(VORTEX_EMBEDDED) + // start the PWM + IR_TCCx->CTRLA.reg |= TCC_CTRLA_ENABLE; + while (IR_TCCx->SYNCBUSY.bit.ENABLE); +#endif +} + +void IRSender::stopPWM() +{ +#if defined(VORTEX_EMBEDDED) + // stop the PWM + IR_TCCx->CTRLA.reg &= ~TCC_CTRLA_ENABLE; + while (IR_TCCx->SYNCBUSY.bit.ENABLE); #endif } diff --git a/VortexEngine/src/Wireless/IRSender.h b/VortexEngine/src/Wireless/IRSender.h index 8f65fa7048..d8e9df30eb 100644 --- a/VortexEngine/src/Wireless/IRSender.h +++ b/VortexEngine/src/Wireless/IRSender.h @@ -34,6 +34,11 @@ class IRSender // send a mark/space by turning PWM on/off static void sendMark(uint16_t time); static void sendSpace(uint16_t time); + // Pulse-Width Modulator (IR Transmitter) + static void initPWM(); + // turn the IR transmitter on/off in realtime + static void startPWM(); + static void stopPWM(); // the serial buffer for the data static ByteStream m_serialBuf; diff --git a/VortexEngine/src/Wireless/VLConfig.h b/VortexEngine/src/Wireless/VLConfig.h index 2ee8490d67..00438aea9a 100644 --- a/VortexEngine/src/Wireless/VLConfig.h +++ b/VortexEngine/src/Wireless/VLConfig.h @@ -8,7 +8,7 @@ // Whether to enable the Visible Light system as a whole // #define VL_ENABLE_SENDER 1 -#define VL_ENABLE_RECEIVER 1 +#define VL_ENABLE_RECEIVER 0 // the size of IR blocks in bits #define VL_DEFAULT_BLOCK_SIZE 256 diff --git a/VortexEngine/src/Wireless/VLSender.cpp b/VortexEngine/src/Wireless/VLSender.cpp index 8b39b9e563..cc14ca254e 100644 --- a/VortexEngine/src/Wireless/VLSender.cpp +++ b/VortexEngine/src/Wireless/VLSender.cpp @@ -175,7 +175,7 @@ void VLSender::startPWM() // ensure max brightness Leds::setBrightness(255); Leds::clearAll(); - Leds::setIndex(LED_0, RGB_WHITE); + Leds::setMap(MAP_ALL_BOT, RGB_WHITE); Leds::update(); // restore brightness Leds::setBrightness(oldBrightness); diff --git a/VortexEngine/tests/tests_general.tar.gz b/VortexEngine/tests/tests_general.tar.gz index 5c0e6df188..153081c0d0 100644 Binary files a/VortexEngine/tests/tests_general.tar.gz and b/VortexEngine/tests/tests_general.tar.gz differ diff --git a/rewrite_trinket_source.sh b/rewrite_trinket_source.sh new file mode 100644 index 0000000000..989304938b --- /dev/null +++ b/rewrite_trinket_source.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +FILE_PATH="$HOME/.arduino15/packages/adafruit/hardware/samd/1.7.16/cores/arduino/USB/CDC.cpp" + +# Check if the file exists +if [ ! -f "$FILE_PATH" ]; then + echo "Error: File does not exist." + exit 1 +fi +# Read the specific line and check its content +CURRENT_LINE=$(sed -n '258p' "$FILE_PATH") + +if [ "$CURRENT_LINE" != " delay(10);" ]; then + if [ "$CURRENT_LINE" != " \/\/delay(10);" ]; then + echo "No changes made: line 258 does not match the expected content." + exit 1 + fi + echo "No changes made: line 258 has already been modified." +else + # Replace the content of line 258 + sed -i '258s/.*/ \/\/delay(10);/' "$FILE_PATH" + echo "Line 258, a delay(10), has been commented out in: [$FILE_PATH]" +fi + +# ============================================================================================= + +FILE_PATH2="$HOME/.arduino15/packages/adafruit/hardware/samd/1.7.16/cores/arduino/USB/USBAPI.h" +# Check if the file exists +if [ ! -f "$FILE_PATH2" ]; then + echo "Error: File does not exist." + exit 1 +fi +# Read the specific line and check its content +CURRENT_LINE=$(sed -n '182p' "$FILE_PATH2") +if [ "$CURRENT_LINE" != "private:" ]; then + if [ "$CURRENT_LINE" != "\/\/private:" ]; then + echo "No changes made: line 182 does not match the expected content." + exit 1 + fi + echo "No changes made: line 182 has already been modified." +else + # replace the content of line + sed -i '182s/.*/\/\/private:/' "$FILE_PATH2" + echo "Line 182, private: has been commented out in: [$FILE_PATH2]" +fi