diff --git a/.github/workflows/chromadeck_build.yml b/.github/workflows/chromadeck_build.yml new file mode 100644 index 0000000000..a6f8ce107d --- /dev/null +++ b/.github/workflows/chromadeck_build.yml @@ -0,0 +1,181 @@ +name: Chromadeck Build + +on: + push: + branches: [ "chromadeck" ] + pull_request: + branches: [ "chromadeck" ] + 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="c" + # 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: Zip firmware files + run: | + zip ChromadeckFirmware.zip \ + build/VortexEngine.ino.bootloader.bin \ + build/VortexEngine.ino.partitions.bin \ + ~/.arduino15/packages/esp32/hardware/esp32/3.0.4/tools/partitions/boot_app0.bin \ + build/VortexEngine.ino.bin + - name: Archive firmware zip + uses: actions/upload-artifact@v4 + with: + name: chromadeck-firmware-zip + path: ChromadeckFirmware.zip + + 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: + #todo: fix the depends to be setup, test, embedded, wasm + needs: [setup, test] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/chromadeck' + 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/chromadeck + doxygen Doxyfile + echo "Listing contents of docs/chromadeck:" + ls -R docs/chromadeck || echo "No files found in docs/chromadeck" + - name: Upload Doxygen Documentation as Artifact + uses: actions/upload-artifact@v3 + with: + name: doxygen-docs-chromadeck + path: docs/chromadeck + + deploy: + needs: [setup, test, embedded, wasm, docs] + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/chromadeck' + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: chromadeck-firmware-zip + path: build + - name: Rename and Deploy Firmware + run: | + DEVICE_TYPE="chromadeck" + VERSIONED_FILENAME="VortexEngine-${DEVICE_TYPE}-${{ needs.setup.outputs.vortex_version_number }}.zip" + mv build/ChromadeckFirmware.zip build/$VERSIONED_FILENAME + echo "Version is ${{ needs.setup.outputs.vortex_version_number }}" + echo "Filename 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/.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/Doxyfile b/Doxyfile index 9db6ecc800..bb4f13fed6 100644 --- a/Doxyfile +++ b/Doxyfile @@ -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/chromadeck # 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..8d71cc1532 --- /dev/null +++ b/Makefile @@ -0,0 +1,66 @@ +.PHONY: all install build upload clean compute_version + +ARDUINO_CLI = ./bin/arduino-cli --verbose +BOARD = esp32:esp32:XIAO_ESP32C3 +PORT = COMx # Replace 'x' with the appropriate COM port number +PROJECT_NAME = VortexEngine/VortexEngine.ino +BUILD_PATH = build +CONFIG_FILE = $(HOME)/.arduino15/arduino-cli.yaml + +# The branch/tag suffix for this device +BRANCH_SUFFIX=c + +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) \ + -MMD -c #due to a bug need the -MMD and -c otherwise esp won't build + +# Default target +all: build + +update-index: + $(ARDUINO_CLI) core update-index + +install: + sudo apt-get update + sudo apt-get install -y build-essential + pip install pyserial + 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://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json' | sudo tee $(CONFIG_FILE) + $(ARDUINO_CLI) lib update-index + $(ARDUINO_CLI) core update-index --config-file $(CONFIG_FILE) + $(ARDUINO_CLI) core install esp32:esp32 --config-file $(CONFIG_FILE) + $(ARDUINO_CLI) lib install FastLED@3.7.6 + +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)" + @echo "== Success building Chromadeck 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..033466cc91 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=c + # 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/VortexEngine.vcxproj b/VortexEngine/VortexEngine.vcxproj index f268bb4ba6..7b3ec18bfb 100644 --- a/VortexEngine/VortexEngine.vcxproj +++ b/VortexEngine/VortexEngine.vcxproj @@ -149,6 +149,8 @@ + + @@ -218,6 +220,8 @@ + + diff --git a/VortexEngine/VortexEngine.vcxproj.filters b/VortexEngine/VortexEngine.vcxproj.filters index 6176710557..d34b25504b 100644 --- a/VortexEngine/VortexEngine.vcxproj.filters +++ b/VortexEngine/VortexEngine.vcxproj.filters @@ -127,6 +127,12 @@ {53027bd8-836b-491d-9b40-25ea0061bce9} + + {358f1fe9-69d8-4d92-8e1f-a7a448f525ba} + + + {37d407a1-9a53-43a6-894d-6df585846653} + @@ -327,6 +333,12 @@ Source Files\Wireless + + Source Files\Menus + + + Source Files + @@ -551,5 +563,11 @@ Header Files\Wireless + + Header Files\Menus + + + Header Files + \ No newline at end of file diff --git a/VortexEngine/VortexLib/Makefile b/VortexEngine/VortexLib/Makefile index 98900f08b8..73e2b601b5 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=c + # 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..c3882b1475 100644 --- a/VortexEngine/VortexLib/VortexLib.cpp +++ b/VortexEngine/VortexLib/VortexLib.cpp @@ -219,6 +219,16 @@ 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_COUNT", LedPos::LED_COUNT) .value("LED_LAST", LedPos::LED_LAST) .value("LED_ALL", LedPos::LED_ALL) @@ -470,8 +480,12 @@ EMSCRIPTEN_BINDINGS(Vortex) { .function("init", &Menu::init) .function("run", &Menu::run) .function("onLedSelected", &Menu::onLedSelected) - .function("onShortClick", &Menu::onShortClick) - .function("onLongClick", &Menu::onLongClick) + .function("onShortClickL", &Menu::onShortClickL) + .function("onShortClickM", &Menu::onShortClickM) + .function("onShortClickR", &Menu::onShortClickR) + .function("onLongClickL", &Menu::onLongClickL) + .function("onLongClickM", &Menu::onLongClickM) + .function("onLongClickR", &Menu::onLongClickR) .function("leaveMenu", &Menu::leaveMenu); //.function("setTargetLeds", &Menu::setTargetLeds); diff --git a/VortexEngine/src/Buttons/Button.cpp b/VortexEngine/src/Buttons/Button.cpp index e565dbbb5c..b8ca93e271 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) == LOW; +#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..94a0a571b1 100644 --- a/VortexEngine/src/Buttons/Button.h +++ b/VortexEngine/src/Buttons/Button.h @@ -97,7 +97,11 @@ class Button #endif }; -// See Button.cpp for info about this -extern Button *g_pButton; +// Button Left +extern Button *g_pButtonL; +// Button Mid +extern Button *g_pButtonM; +// Button Right +extern Button *g_pButtonR; #endif diff --git a/VortexEngine/src/Buttons/Buttons.cpp b/VortexEngine/src/Buttons/Buttons.cpp index 466b22920c..d1d4b9f1f6 100644 --- a/VortexEngine/src/Buttons/Buttons.cpp +++ b/VortexEngine/src/Buttons/Buttons.cpp @@ -13,18 +13,28 @@ // still allowing for a second instance to be added. I wish there was a more // elegant way to make the button accessible but not global. // This will simply point at Buttons::m_button. -Button *g_pButton = nullptr; + +// Button Left +Button *g_pButtonL = nullptr; +// Button Mid +Button *g_pButtonM = nullptr; +// Button Right +Button *g_pButtonR = nullptr; // static members Button Buttons::m_buttons[NUM_BUTTONS]; bool Buttons::init() { - // initialize the button on pin 1 - if (!m_buttons[0].init(1)) { + // initialize the button on pins 5/6/7 + if (!m_buttons[0].init(5) || + !m_buttons[1].init(6) || + !m_buttons[2].init(7)) { return false; } - g_pButton = &m_buttons[0]; + g_pButtonL = &m_buttons[0]; + g_pButtonM = &m_buttons[1]; + g_pButtonR = &m_buttons[2]; return true; } diff --git a/VortexEngine/src/Buttons/Buttons.h b/VortexEngine/src/Buttons/Buttons.h index 7f02e34c73..ab7f253be1 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 3 class Buttons { @@ -29,7 +29,11 @@ class Buttons static Button m_buttons[NUM_BUTTONS]; }; -// best way I think -extern Button *g_pButton; +// Button Left +extern Button *g_pButtonL; +// Button Mid +extern Button *g_pButtonM; +// Button Right +extern Button *g_pButtonR; #endif diff --git a/VortexEngine/src/Leds/LedTypes.h b/VortexEngine/src/Leds/LedTypes.h index 73bcce80f3..59f519be88 100644 --- a/VortexEngine/src/Leds/LedTypes.h +++ b/VortexEngine/src/Leds/LedTypes.h @@ -24,6 +24,18 @@ enum LedPos : uint8_t LED_8, LED_9, + // inner circle of leds + LED_10, + LED_11, + LED_12, + LED_13, + LED_14, + LED_15, + LED_16, + LED_17, + LED_18, + LED_19, + // the number of entries above LED_COUNT, @@ -74,6 +86,12 @@ enum Pair : uint8_t PAIR_3, PAIR_4, + PAIR_5, + PAIR_6, + PAIR_7, + PAIR_8, + PAIR_9, + PAIR_COUNT, PAIR_LAST = (PAIR_COUNT - 1), }; @@ -152,6 +170,9 @@ inline LedPos ledmapGetNextLed(LedMap map, LedPos pos) #define MAP_PAIR_EVENS (((1 << LED_COUNT) - 1) & 0x55555555) #define MAP_PAIR_ODDS (((1 << LED_COUNT) - 1) & 0xAAAAAAAA) +#define MAP_OUTER_RING ((((1 << LED_COUNT) - 1) >> (LED_COUNT / 2)) << (LED_COUNT / 2)) +#define MAP_INNER_RING ((((1 << LED_COUNT) - 1) << (LED_COUNT / 2)) >> (LED_COUNT / 2)) + // Some preset bitmaps for pair groupings #define MAP_PAIR_ODD_EVENS (MAP_PAIR_EVEN(PAIR_0) | MAP_PAIR_EVEN(PAIR_2) | MAP_PAIR_EVEN(PAIR_4)) #define MAP_PAIR_ODD_ODDS (MAP_PAIR_ODD(PAIR_0) | MAP_PAIR_ODD(PAIR_2) | MAP_PAIR_ODD(PAIR_4)) @@ -159,6 +180,31 @@ 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)) +#define MAP_RING_INNER (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)) +#define MAP_RING_OUTER (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)) + +#define MAP_RING_INNER_EVEN (MAP_RING_INNER & 0xAAAAAAAA) +#define MAP_RING_INNER_ODD (MAP_RING_INNER & 0x55555555) +#define MAP_RING_OUTER_EVEN (MAP_RING_OUTER & 0xAAAAAAAA) +#define MAP_RING_OUTER_ODD (MAP_RING_OUTER & 0x55555555) + +#define MAP_LINE_1 (MAP_LED(LED_0) | MAP_LED(LED_10) | MAP_LED(LED_15) | MAP_LED(LED_5)) +#define MAP_LINE_2 (MAP_LED(LED_1) | MAP_LED(LED_11) | MAP_LED(LED_16) | MAP_LED(LED_6)) +#define MAP_LINE_3 (MAP_LED(LED_2) | MAP_LED(LED_12) | MAP_LED(LED_17) | MAP_LED(LED_7)) +#define MAP_LINE_4 (MAP_LED(LED_3) | MAP_LED(LED_13) | MAP_LED(LED_18) | MAP_LED(LED_8)) +#define MAP_LINE_5 (MAP_LED(LED_4) | MAP_LED(LED_14) | MAP_LED(LED_19) | MAP_LED(LED_9)) + +//Chromadeck bitmap +#define MAP_OPPOSITES_1 (MAP_LED(LED_0) | MAP_LED(LED_5) | MAP_LED(LED_10) | MAP_LED(LED_15)) +#define MAP_OPPOSITES_2 (MAP_LED(LED_1) | MAP_LED(LED_6) | MAP_LED(LED_11) | MAP_LED(LED_16)) +#define MAP_OPPOSITES_3 (MAP_LED(LED_2) | MAP_LED(LED_7) | MAP_LED(LED_12) | MAP_LED(LED_17)) +#define MAP_OPPOSITES_4 (MAP_LED(LED_3) | MAP_LED(LED_8) | MAP_LED(LED_13) | MAP_LED(LED_18)) +#define MAP_OPPOSITES_5 (MAP_LED(LED_4) | MAP_LED(LED_9) | MAP_LED(LED_14) | MAP_LED(LED_19)) + // set a single led inline void ledmapSetLed(LedMap &map, LedPos pos) { diff --git a/VortexEngine/src/Leds/Leds.cpp b/VortexEngine/src/Leds/Leds.cpp index 0d1dd4c400..38851915d6 100644 --- a/VortexEngine/src/Leds/Leds.cpp +++ b/VortexEngine/src/Leds/Leds.cpp @@ -12,6 +12,12 @@ #include "../../VortexLib/VortexLib.h" #endif +#ifdef VORTEX_EMBEDDED +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#include +#define LED_PIN 0 +#endif + // global brightness uint8_t Leds::m_brightness = DEFAULT_BRIGHTNESS; // array of led color values @@ -19,6 +25,10 @@ RGBColor Leds::m_ledColors[LED_COUNT] = { RGB_OFF }; bool Leds::init() { +#ifdef VORTEX_EMBEDDED + FastLED.addLeds((CRGB *)m_ledColors, LED_COUNT); + FastLED.setMaxRefreshRate(0); +#endif #ifdef VORTEX_LIB Vortex::vcallbacks()->ledsInit(m_ledColors, LED_COUNT); #endif @@ -260,6 +270,9 @@ void Leds::holdAll(RGBColor col) void Leds::update() { +#ifdef VORTEX_EMBEDDED + FastLED.show(m_brightness); +#endif #ifdef VORTEX_LIB Vortex::vcallbacks()->ledsShow(); #endif 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/Memory/Memory.cpp b/VortexEngine/src/Memory/Memory.cpp index 9172488167..ff3381cd78 100644 --- a/VortexEngine/src/Memory/Memory.cpp +++ b/VortexEngine/src/Memory/Memory.cpp @@ -120,14 +120,14 @@ uint32_t cur_memory_usage_total() #ifndef VORTEX_LIB // for C++11 need the following: -void *operator new (size_t size) { return vmalloc(size); } -void *operator new[](size_t size) { return vmalloc(size); } -void operator delete (void *ptr) { vfree(ptr); } -void operator delete[](void *ptr) { vfree(ptr); } -void *operator new (size_t size, void *ptr) noexcept { return ptr; } -void *operator new[](size_t size, void *ptr) noexcept { return ptr; } -void operator delete (void *ptr, size_t size) noexcept { vfree(ptr); } -void operator delete[](void *ptr, size_t size) noexcept { vfree(ptr); } +//void *operator new (size_t size) { return vmalloc(size); } +//void *operator new[](size_t size) { return vmalloc(size); } +//void operator delete (void *ptr) { vfree(ptr); } +//void operator delete[](void *ptr) { vfree(ptr); } +//void *operator new (size_t size, void *ptr) noexcept { return ptr; } +//void *operator new[](size_t size, void *ptr) noexcept { return ptr; } +//void operator delete (void *ptr, size_t size) noexcept { vfree(ptr); } +//void operator delete[](void *ptr, size_t size) noexcept { vfree(ptr); } //void *operator new (size_t size, std::align_val_t al) { return vmalloc(size); } //void *operator new[](size_t size, std::align_val_t al) { return vmalloc(size); } //void operator delete (void *ptr, std::align_val_t al) noexcept { vfree(ptr); } @@ -136,7 +136,7 @@ void operator delete[](void *ptr, size_t size) noexcept { vfree(ptr); } //void operator delete[](void *ptr, size_t size, std::align_val_t al) noexcept { vfree(ptr); } // needed for C++ virtual functions -extern "C" void __cxa_pure_virtual(void) {} -extern "C" void __cxa_deleted_virtual(void) {} +//extern "C" void __cxa_pure_virtual(void) {} +//extern "C" void __cxa_deleted_virtual(void) {} #endif diff --git a/VortexEngine/src/Memory/Memory.h b/VortexEngine/src/Memory/Memory.h index b851d781ec..faf042e82a 100644 --- a/VortexEngine/src/Memory/Memory.h +++ b/VortexEngine/src/Memory/Memory.h @@ -37,14 +37,14 @@ uint32_t cur_memory_usage_total(); #endif #ifndef VORTEX_LIB -void *operator new (size_t size); -void *operator new[](size_t size); -void operator delete (void *ptr); -void operator delete[](void *ptr); -void *operator new (size_t size, void *ptr) noexcept; -void *operator new[](size_t size, void *ptr) noexcept; -void operator delete (void *ptr, size_t size) noexcept; -void operator delete[](void *ptr, size_t size) noexcept; +//void *operator new (size_t size); +//void *operator new[](size_t size); +//void operator delete (void *ptr); +//void operator delete[](void *ptr); +//void *operator new (size_t size, void *ptr) noexcept; +//void *operator new[](size_t size, void *ptr) noexcept; +//void operator delete (void *ptr, size_t size) noexcept; +//void operator delete[](void *ptr, size_t size) noexcept; //void *operator new (size_t size, std::align_val_t al); //void *operator new[](size_t size, std::align_val_t al); //void operator delete (void *ptr, std::align_val_t al) noexcept; diff --git a/VortexEngine/src/Menus/MainMenu.cpp b/VortexEngine/src/Menus/MainMenu.cpp new file mode 100644 index 0000000000..138a2a0d14 --- /dev/null +++ b/VortexEngine/src/Menus/MainMenu.cpp @@ -0,0 +1,108 @@ +#include "MainMenu.h" + +#include "../Time/TimeControl.h" +#include "../Storage/Storage.h" +#include "../Buttons/Buttons.h" +#include "../Leds/LedTypes.h" +#include "../Modes/Modes.h" +#include "../Leds/Leds.h" +#include "../Log/Log.h" + +bool MainMenu::m_isOpen = true; +uint8_t MainMenu::m_curSelection = 0; + +#define NUM_SELECTIONS (LED_COUNT / 2) + +bool MainMenu::init() +{ + // main menus start open + m_isOpen = true; + m_curSelection = 0; + return true; +} + +bool MainMenu::run() +{ + // if the main menus aren't open then nothing to do here + if (!m_isOpen) { + return false; + } + + // press < + if (g_pButtonL->onShortClick()) { + pressLeft(); + } + // press o + if (g_pButtonM->onShortClick()) { + select(); + } + if (g_pButtonM->onLongClick()) { + select(); + } + // press > + if (g_pButtonR->onShortClick()) { + pressRight(); + } + + // render + show(); + return true; +} + +void MainMenu::show() +{ + Leds::clearAll(); + // render the main menu + uint8_t hue = 0; + uint32_t now = Time::getCurtime(); + MAP_FOREACH_LED(MAP_OUTER_RING) { + Leds::breatheIndex(pos, hue, (now / 2), 8, 255, 180); + hue += (255 / (LED_COUNT / 2)); + } + hue = 0; + MAP_FOREACH_LED(MAP_INNER_RING) { + Leds::breatheIndex(pos, hue, (now / 2), 8, 255, 180); + hue += (255 / (LED_COUNT / 2)); + } + Leds::blinkIndex((LedPos)m_curSelection); + Leds::blinkIndex((LedPos)(m_curSelection + 10)); +} + +void MainMenu::open() +{ + m_isOpen = true; +} + +void MainMenu::close() +{ + m_isOpen = false; +} + +bool MainMenu::isOpen() +{ + return m_isOpen; +} + +void MainMenu::pressLeft() +{ + if (!m_curSelection) { + m_curSelection = NUM_SELECTIONS - 1; + } else { + m_curSelection--; + } +} + +void MainMenu::pressRight() +{ + m_curSelection = (m_curSelection + 1) % NUM_SELECTIONS; +} + +void MainMenu::select() +{ + m_isOpen = false; + Storage::setStoragePage(m_curSelection); + if (!Modes::loadStorage()) { + Modes::setDefaults(); + } + DEBUG_LOGF("Selected storage page: %u", m_curSelection); +} diff --git a/VortexEngine/src/Menus/MainMenu.h b/VortexEngine/src/Menus/MainMenu.h new file mode 100644 index 0000000000..4da3867846 --- /dev/null +++ b/VortexEngine/src/Menus/MainMenu.h @@ -0,0 +1,26 @@ +#ifndef MAINMENU_H +#define MAINMENU_H + +#include + +class MainMenu +{ +public: + static bool init(); + static bool run(); + static void show(); + + // open the main menu + static void open(); + static void close(); + static bool isOpen(); + static void pressLeft(); + static void pressRight(); + static void select(); + +private: + static bool m_isOpen; + static uint8_t m_curSelection; +}; + +#endif diff --git a/VortexEngine/src/Menus/Menu.cpp b/VortexEngine/src/Menus/Menu.cpp index abfa81f77b..9deb117cd6 100644 --- a/VortexEngine/src/Menus/Menu.cpp +++ b/VortexEngine/src/Menus/Menu.cpp @@ -9,10 +9,53 @@ #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_RING_INNER, + MAP_RING_OUTER, + + MAP_RING_INNER_EVEN, + MAP_RING_INNER_ODD, + MAP_RING_OUTER_EVEN, + MAP_RING_OUTER_ODD, + + MAP_LINE_1, + MAP_LINE_2, + MAP_LINE_3, + MAP_LINE_4, + MAP_LINE_5, + + 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), +}; + +#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_targetLeds(MAP_LED_NONE), + m_ledSelection(0), m_curSelection(0), m_ledSelected(false), m_advanced(advanced), @@ -42,6 +85,8 @@ bool Menu::init() return false; } } + // reset the current selection + m_curSelection = 0; // copy the current mode into the demo mode and initialize it m_previewMode = *Modes::curMode(); m_previewMode.init(); @@ -69,14 +114,20 @@ 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 - if (g_pButton->onShortClick()) { - do { - nextBulbSelection(); - } while (!isValidLedSelection(m_targetLeds)); + // every time a button is clicked, change the led selection + if (g_pButtonR->onShortClick()) { + m_ledSelection = (m_ledSelection + 1) % NUM_PERMUTATIONS; + } + if (g_pButtonL->onShortClick()) { + m_ledSelection = (m_ledSelection > 0) ? (m_ledSelection - 1) : (NUM_PERMUTATIONS - 1); } // on a long press of the button, lock in the target led - if (g_pButton->onLongClick()) { + if (g_pButtonM->onLongClick() || g_pButtonM->onShortClick()) { + // if no target, set at least cur mask + if (m_targetLeds == 0) { + //if (m_targetLeds == MAP_LED_NONE) { + addSelectionMask(); + } m_ledSelected = true; // call led selected callback onLedSelected(); @@ -86,7 +137,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_pButtonR->onLongClick()) { + addSelectionMask(); + } // render the bulb selection showBulbSelection(); @@ -99,11 +153,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); @@ -111,7 +169,7 @@ void Menu::showBulbSelection() void Menu::showExit() { - if (g_pButton->isPressed() && g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS) { + if (g_pButtonM->isPressed() && g_pButtonM->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS) { Leds::setAll(RGB_RED); return; } @@ -120,56 +178,36 @@ void Menu::showExit() Leds::blinkAll(EXIT_MENU_OFF_MS, EXIT_MENU_ON_MS, 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::onShortClickL() { } -void Menu::onShortClick() +void Menu::onShortClickM() { } -void Menu::onLongClick() +void Menu::onShortClickR() +{ +} + +void Menu::onLongClickL() +{ + leaveMenu(false); +} + +void Menu::onLongClickM() { leaveMenu(false); } +void Menu::onLongClickR() +{ +} + void Menu::leaveMenu(bool doSave) { m_shouldClose = true; @@ -177,3 +215,17 @@ void Menu::leaveMenu(bool doSave) Modes::saveStorage(); } } + +// this adds the currently targeted ledPermutation to the selected leds +void Menu::addSelectionMask() { + // if selecting any of the individual leds then toggle + uint32_t 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..34bb7715ef 100644 --- a/VortexEngine/src/Menus/Menu.h +++ b/VortexEngine/src/Menus/Menu.h @@ -31,8 +31,12 @@ class Menu virtual void onLedSelected(); // optional handlers for clicks - virtual void onShortClick(); - virtual void onLongClick(); + virtual void onShortClickL(); + virtual void onShortClickM(); + virtual void onShortClickR(); + virtual void onLongClickL(); + virtual void onLongClickM(); + virtual void onLongClickR(); // close the current menu virtual void leaveMenu(bool doSave = false); @@ -43,6 +47,7 @@ class Menu // iterate to next bulb selection void nextBulbSelection(); + void prevBulbSelection(); // an overridable api that allows derived menus to decide which led selections // should be available before they have actually opened @@ -55,6 +60,8 @@ class Menu // tracks the targetted leds for this menu // note this is an led map LedMap m_targetLeds; + // current index of led maps + uint16_t m_ledSelection; // all menus have a 'current selection' uint8_t m_curSelection; // true once a an led is selected @@ -65,6 +72,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..e1ce189e96 100644 --- a/VortexEngine/src/Menus/MenuList/ColorSelect.cpp +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.cpp @@ -98,7 +98,7 @@ void ColorSelect::onLedSelected() } } -void ColorSelect::onShortClick() +void ColorSelect::onShortClickM() { // increment selection m_curSelection++; @@ -109,7 +109,7 @@ void ColorSelect::onShortClick() } } -void ColorSelect::onLongClick() +void ColorSelect::onLongClickM() { // if we're on 'exit' and we're on any menu past the slot selection if (m_curSelection == 4 && m_state > STATE_PICK_SLOT) { @@ -123,7 +123,7 @@ void ColorSelect::onLongClick() } // reuse these variables lots uint8_t numColors = m_colorset.numColors(); - uint32_t holdDur = g_pButton->holdDuration(); + uint32_t holdDur = g_pButtonM->holdDuration(); switch (m_state) { case STATE_INIT: // nothing @@ -181,9 +181,9 @@ void ColorSelect::onLongClick() void ColorSelect::showSlotSelection() { uint8_t exitIndex = m_colorset.numColors(); - uint32_t holdDur = g_pButton->holdDuration(); + uint32_t holdDur = g_pButtonM->holdDuration(); bool withinNumColors = m_curSelection < exitIndex; - bool holdDurationCheck = g_pButton->isPressed() && holdDur >= DELETE_THRESHOLD_TICKS; + bool holdDurationCheck = g_pButtonM->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) { diff --git a/VortexEngine/src/Menus/MenuList/ColorSelect.h b/VortexEngine/src/Menus/MenuList/ColorSelect.h index 6bda0fd1c3..c28cd89de8 100644 --- a/VortexEngine/src/Menus/MenuList/ColorSelect.h +++ b/VortexEngine/src/Menus/MenuList/ColorSelect.h @@ -18,8 +18,8 @@ class ColorSelect : public Menu void onLedSelected() override; // handlers for clicks - void onShortClick() override; - void onLongClick() override; + void onShortClickM() override; + void onLongClickM() override; private: // override the led selection api to choose which led maps can be selected diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp index c8dade7503..a8be3f8be6 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp @@ -5,21 +5,31 @@ #include "../../Serial/Serial.h" #include "../../Storage/Storage.h" #include "../../Wireless/VLSender.h" +#include "../../Wireless/VLReceiver.h" #include "../../Time/TimeControl.h" +#include "../../Time/Timings.h" #include "../../Colors/Colorset.h" #include "../../Modes/Modes.h" #include "../../Modes/Mode.h" #include "../../Leds/Leds.h" +#include "../../UPDI/updi.h" #include "../../Log/Log.h" #include +#define FIRMWARE_TRANSFER_BLOCK_SIZE 512 + EditorConnection::EditorConnection(const RGBColor &col, bool advanced) : Menu(col, advanced), m_state(STATE_DISCONNECTED), + m_timeOutStartTime(0), + m_chromaModeIdx(0), m_allowReset(true), m_previousModeIndex(0), - m_numModesToReceive(0) + m_numModesToReceive(0), + m_curStep(0), + m_firmwareSize(0), + m_firmwareOffset(0) { } @@ -36,6 +46,7 @@ bool EditorConnection::init() // skip led selection m_ledSelected = true; clearDemo(); + DEBUG_LOG("Entering Editor Connection"); return true; } @@ -65,14 +76,6 @@ bool EditorConnection::receiveMessage(const char *message) return true; } -void EditorConnection::clearDemo() -{ - Colorset set(RGB_WHITE0); - PatternArgs args(1, 0, 0); - m_previewMode.setPattern(PATTERN_STROBE, LED_ALL, &args, &set); - m_previewMode.init(); -} - Menu::MenuAction EditorConnection::run() { MenuAction result = Menu::run(); @@ -84,8 +87,17 @@ Menu::MenuAction EditorConnection::run() showEditor(); // receive any data from serial into the receive buffer receiveData(); + // handle the current state + handleState(); + return MENU_CONTINUE; +} + +void EditorConnection::handleState() +{ // operate on the state of the editor connection switch (m_state) { + // ------------------------------- + // Disconnected case STATE_DISCONNECTED: default: // not connected yet so check for connections @@ -98,21 +110,30 @@ Menu::MenuAction EditorConnection::run() // a connection was found, say hello m_state = STATE_GREETING; break; + + // ------------------------------- + // Send Greeting case STATE_GREETING: m_receiveBuffer.clear(); // send the hello greeting with our version number and build time SerialComs::write(EDITOR_VERB_GREETING); m_state = STATE_IDLE; break; + + // ------------------------------- + // Chillin case STATE_IDLE: // parse the receive buffer for any commands from the editor handleCommand(); // watch for disconnects - if (!SerialComs::isConnected()) { - Leds::holdAll(RGB_GREEN); + if (!SerialComs::isConnectedReal()) { + Leds::holdAll(RGB_RED); leaveMenu(true); } break; + + // ------------------------------- + // Send Modes to PC case STATE_PULL_MODES: // editor requested pull modes, send the modes sendModes(); @@ -131,6 +152,9 @@ Menu::MenuAction EditorConnection::run() // go idle m_state = STATE_IDLE; break; + + // ------------------------------- + // Receive Modes from PC case STATE_PUSH_MODES: // editor requested to push modes, clear first and reset first m_receiveBuffer.clear(); @@ -152,6 +176,9 @@ Menu::MenuAction EditorConnection::run() SerialComs::write(EDITOR_VERB_PUSH_MODES_ACK); m_state = STATE_IDLE; break; + + // ------------------------------- + // Demo Mode from PC case STATE_DEMO_MODE: // editor requested to push modes, clear first and reset first m_receiveBuffer.clear(); @@ -173,12 +200,18 @@ Menu::MenuAction EditorConnection::run() SerialComs::write(EDITOR_VERB_DEMO_MODE_ACK); m_state = STATE_IDLE; break; + + // ------------------------------- + // Reset Demo to Nothing case STATE_CLEAR_DEMO: clearDemo(); m_receiveBuffer.clear(); SerialComs::write(EDITOR_VERB_CLEAR_DEMO_ACK); m_state = STATE_IDLE; break; + + // ------------------------------- + // Send Mode to Duo case STATE_TRANSMIT_MODE_VL: #if VL_ENABLE_SENDER == 1 // if still sending and the send command indicated more data @@ -196,6 +229,22 @@ Menu::MenuAction EditorConnection::run() SerialComs::write(EDITOR_VERB_TRANSMIT_VL_ACK); m_state = STATE_IDLE; break; + + // ------------------------------- + // Receive Mode from Duo + case STATE_LISTEN_MODE_VL: + showReceiveModeVL(); + receiveModeVL(); + break; + case STATE_LISTEN_MODE_VL_DONE: + // done transmitting + m_receiveBuffer.clear(); + SerialComs::write(EDITOR_VERB_LISTEN_VL_ACK); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Send Modes to PC Safer case STATE_PULL_EACH_MODE: // editor requested pull modes, send the modes m_receiveBuffer.clear(); @@ -242,6 +291,9 @@ Menu::MenuAction EditorConnection::run() // go idle m_state = STATE_IDLE; break; + + // ------------------------------- + // Receive Modes from PC Safer case STATE_PUSH_EACH_MODE: // editor requested to push modes, find out how many m_receiveBuffer.clear(); @@ -279,8 +331,128 @@ Menu::MenuAction EditorConnection::run() // on lightshow.lol so just skip to IDLE m_state = STATE_IDLE; break; + + // ------------------------------- + // Get Chromalinked Duo Header + case STATE_PULL_HEADER_CHROMALINK: + if (!pullHeaderChromalink()) { + // error? + break; + } + // done + m_receiveBuffer.clear(); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Get Chromalinked Duo Mode + case STATE_PULL_MODE_CHROMALINK: + // now say we are ready + m_receiveBuffer.clear(); + SerialComs::write(EDITOR_VERB_READY); + m_state = STATE_PULL_MODE_CHROMALINK_SEND; + break; + case STATE_PULL_MODE_CHROMALINK_SEND: + // send the stuff + if (!pullModeChromalink()) { + break; + } + // done + m_curStep = 0; + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Set Chromalinked Duo Header + case STATE_PUSH_HEADER_CHROMALINK: + // editor requested to push modes, clear first and reset first + m_receiveBuffer.clear(); + // now say we are ready + SerialComs::write(EDITOR_VERB_READY); + // move to receiving + m_state = STATE_PUSH_HEADER_CHROMALINK_RECEIVE; + break; + case STATE_PUSH_HEADER_CHROMALINK_RECEIVE: + // receive the modes into the receive buffer + if (!pushHeaderChromalink()) { + break; + } + // the trick is to send header after the modes so the reset comes at the end + UPDI::reset(); + // success modes were received send the done + SerialComs::write(EDITOR_VERB_PUSH_CHROMA_HDR_ACK); + m_receiveBuffer.clear(); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Set Chromalinked Duo Mode + case STATE_PUSH_MODE_CHROMALINK: + // editor requested to push modes, clear first and reset first + m_receiveBuffer.clear(); + // now say we are ready + SerialComs::write(EDITOR_VERB_READY); + // move to receiving + m_state = STATE_PUSH_MODE_CHROMALINK_RECEIVE_IDX; + break; + case STATE_PUSH_MODE_CHROMALINK_RECEIVE_IDX: + if (!receiveModeIdx(m_chromaModeIdx)) { + break; + } + m_receiveBuffer.clear(); + SerialComs::write(EDITOR_VERB_READY); + m_state = STATE_PUSH_MODE_CHROMALINK_RECEIVE; + break; + case STATE_PUSH_MODE_CHROMALINK_RECEIVE: + if (!pushModeChromalink()) { + break; + } + SerialComs::write(EDITOR_VERB_PUSH_CHROMA_MODE_ACK); + // done + m_receiveBuffer.clear(); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Flash Chromalinked Duo + case STATE_CHROMALINK_FLASH_FIRMWARE: + // editor requested to push modes, clear first and reset first + m_receiveBuffer.clear(); + // now say we are ready + SerialComs::write(EDITOR_VERB_READY); + // move to receiving + m_state = STATE_CHROMALINK_FLASH_FIRMWARE_RECEIVE_SIZE; + break; + case STATE_CHROMALINK_FLASH_FIRMWARE_RECEIVE_SIZE: + if (!receiveFirmwareSize(m_firmwareSize)) { + break; + } + UPDI::eraseMemory(); + + + m_curStep = 0; + m_firmwareOffset = 0; + m_receiveBuffer.clear(); + Leds::setAll(RGB_YELLOW3); + SerialComs::write(EDITOR_VERB_READY); + m_state = STATE_CHROMALINK_FLASH_FIRMWARE_RECEIVE; + break; + case STATE_CHROMALINK_FLASH_FIRMWARE_RECEIVE: + // receive and write a chunk of firwmare + if (!writeDuoFirmware()) { + break; + } + // send ack + SerialComs::write(EDITOR_VERB_FLASH_FIRMWARE_ACK); + // only once the entire firmware is written + if (m_firmwareOffset >= m_firmwareSize) { + // then done + m_receiveBuffer.clear(); + m_curStep = 0; + m_state = STATE_IDLE; + } + break; } - return MENU_CONTINUE; } void EditorConnection::sendCurModeVL() @@ -293,8 +465,92 @@ void EditorConnection::sendCurModeVL() m_state = STATE_TRANSMIT_MODE_VL; } -// handlers for clicks -void EditorConnection::onShortClick() +void EditorConnection::listenModeVL() +{ +#if VL_ENABLE_SENDER == 1 + // immediately load the mode and send it now + VLReceiver::beginReceiving(); +#endif + m_state = STATE_LISTEN_MODE_VL; +} + +bool EditorConnection::pullHeaderChromalink() +{ + // first read the duo save header + ByteStream saveHeader; + // doesn't matter if reading the header fails, we still need to send it + bool success = UPDI::readHeader(saveHeader); + // send whatever we read, might be empty buffer if it failed + SerialComs::write(saveHeader); + // return whether reading the header was successful + return success; +} + +bool EditorConnection::pushHeaderChromalink() +{ + // wait for the header then write it via updi + ByteStream buf; + if (!receiveBuffer(buf)) { + return false; + } + if (!UPDI::writeHeader(buf)) { + return false; + } + return true; +} + +// pull/push through the chromalink +bool EditorConnection::pullModeChromalink() +{ + // try to receive the mode index + uint8_t modeIdx = 0; + // only 9 modes on duo, maybe this should be a macro or something + if (!receiveModeIdx(modeIdx) || modeIdx >= 9) { + return false; + } + ByteStream modeBuffer; + // same doesn't matter if this fails still need to send + bool success = UPDI::readMode(modeIdx, modeBuffer); + // send the mode, could be empty buffer if reading failed + SerialComs::write(modeBuffer); + // return whether reading the mode was successful + return success; +} + +bool EditorConnection::pushModeChromalink() +{ + // wait for the mode then write it via updi + ByteStream buf; + if (!receiveBuffer(buf)) { + return false; + } + if (!UPDI::writeMode(m_chromaModeIdx, buf)) { + return false; + } + return true; +} + +bool EditorConnection::writeDuoFirmware() +{ + // wait for the mode then write it via updi + ByteStream buf; + if (!receiveBuffer(buf)) { + return false; + } + if (!UPDI::writeFirmware(m_firmwareOffset, buf)) { + return false; + } + m_firmwareOffset += buf.size(); + if (m_firmwareOffset >= m_firmwareSize) { + UPDI::reset(); + } + // create a progress bar I guess + Leds::setAll(RGB_RED0); + Leds::setRange(LED_0, (LedPos)((m_firmwareOffset / (float)m_firmwareSize) * LED_COUNT), RGB_GREEN3); + return true; +} + +void EditorConnection::onShortClickM() { // if the device has received any commands do not reset! if (!m_allowReset) { @@ -308,17 +564,49 @@ void EditorConnection::onShortClick() m_allowReset = false; } -void EditorConnection::onLongClick() +void EditorConnection::onLongClickM() { leaveMenu(true); } +// handlers for clicks void EditorConnection::leaveMenu(bool doSave) { SerialComs::write(EDITOR_VERB_GOODBYE); Menu::leaveMenu(true); } +void EditorConnection::handleCommand() +{ + if (receiveMessage(EDITOR_VERB_PULL_MODES)) { + m_state = STATE_PULL_MODES; + } else if (receiveMessage(EDITOR_VERB_PUSH_MODES)) { + m_state = STATE_PUSH_MODES; + } else if (receiveMessage(EDITOR_VERB_DEMO_MODE)) { + m_state = STATE_DEMO_MODE; + } else if (receiveMessage(EDITOR_VERB_CLEAR_DEMO)) { + m_state = STATE_CLEAR_DEMO; + } else if (receiveMessage(EDITOR_VERB_PULL_EACH_MODE)) { + m_state = STATE_PULL_EACH_MODE; + } else if (receiveMessage(EDITOR_VERB_PUSH_EACH_MODE)) { + m_state = STATE_PUSH_EACH_MODE; + } else if (receiveMessage(EDITOR_VERB_TRANSMIT_VL)) { + sendCurModeVL(); + } else if (receiveMessage(EDITOR_VERB_LISTEN_VL)) { + listenModeVL(); + } else if (receiveMessage(EDITOR_VERB_PULL_CHROMA_HDR)) { + m_state = STATE_PULL_HEADER_CHROMALINK; + } else if (receiveMessage(EDITOR_VERB_PUSH_CHROMA_HDR)) { + m_state = STATE_PUSH_HEADER_CHROMALINK; + } else if (receiveMessage(EDITOR_VERB_PULL_CHROMA_MODE)) { + m_state = STATE_PULL_MODE_CHROMALINK; + } else if (receiveMessage(EDITOR_VERB_PUSH_CHROMA_MODE)) { + m_state = STATE_PUSH_MODE_CHROMALINK; + } else if (receiveMessage(EDITOR_VERB_FLASH_FIRMWARE)) { + m_state = STATE_CHROMALINK_FLASH_FIRMWARE; + } +} + void EditorConnection::showEditor() { switch (m_state) { @@ -327,7 +615,9 @@ void EditorConnection::showEditor() Leds::blinkAll(250, 150, RGB_WHITE0); break; case STATE_IDLE: - m_previewMode.play(); + if (m_curStep == 0) { + m_previewMode.play(); + } break; default: // do nothing! @@ -372,7 +662,7 @@ void EditorConnection::sendCurMode() SerialComs::write(modeBuffer); } -bool EditorConnection::receiveModes() +bool EditorConnection::receiveBuffer(ByteStream &buffer) { // need at least the buffer size first uint32_t size = 0; @@ -392,13 +682,46 @@ bool EditorConnection::receiveModes() return false; } // create a new ByteStream that will hold the full buffer of data - ByteStream buf(m_receiveBuffer.rawSize()); + buffer.init(m_receiveBuffer.rawSize()); // then copy everything from the receive buffer into the rawdata // which is going to overwrite the crc/size/flags of the ByteStream - memcpy(buf.rawData(), m_receiveBuffer.data() + sizeof(size), + memcpy(buffer.rawData(), m_receiveBuffer.data() + sizeof(size), + m_receiveBuffer.size() - sizeof(size)); + // clear the receive buffer + m_receiveBuffer.clear(); + if (!buffer.checkCRC()) { + return false; + } + return true; +} + +bool EditorConnection::receiveFirmwareChunk(ByteStream &buffer) +{ + // need at least the buffer size first + uint32_t size = 0; + // read the 140 byte chunk + SerialComs::readAmount(144, m_receiveBuffer); + // create a new ByteStream that will hold the full buffer of data + buffer.init(m_receiveBuffer.rawSize()); + // then copy everything from the receive buffer into the rawdata + // which is going to overwrite the crc/size/flags of the ByteStream + memcpy(buffer.rawData(), m_receiveBuffer.data() + sizeof(size), m_receiveBuffer.size() - sizeof(size)); // clear the receive buffer m_receiveBuffer.clear(); + if (!buffer.checkCRC()) { + return false; + } + return true; +} + +bool EditorConnection::receiveModes() +{ + // create a new ByteStream that will hold the full buffer of data + ByteStream buf; + if (!receiveBuffer(buf)) { + return false; + } Modes::loadFromBuffer(buf); Modes::saveStorage(); return true; @@ -475,54 +798,95 @@ bool EditorConnection::receiveMode() } bool EditorConnection::receiveDemoMode() +{ + // create a new ByteStream that will hold the full buffer of data + ByteStream buf; + if (!receiveBuffer(buf)) { + return false; + } + // unserialize the mode into the demo mode + if (!m_previewMode.loadFromBuffer(buf)) { + // failure + } + return true; +} + +void EditorConnection::clearDemo() +{ + Colorset set(RGB_WHITE0); + PatternArgs args(1, 0, 0); + m_previewMode.setPattern(PATTERN_STROBE, LED_ALL, &args, &set); + m_previewMode.init(); +} + +void EditorConnection::receiveModeVL() +{ + // if reveiving new data set our last data time + if (VLReceiver::onNewData()) { + m_timeOutStartTime = Time::getCurtime(); + // if our last data was more than time out duration reset the recveiver + } else if (m_timeOutStartTime > 0 && (m_timeOutStartTime + MAX_TIMEOUT_DURATION) < Time::getCurtime()) { + VLReceiver::resetVLState(); + m_timeOutStartTime = 0; + return; + } + // check if the VLReceiver has a full packet available + if (!VLReceiver::dataReady()) { + // nothing available yet + return; + } + DEBUG_LOG("Mode ready to receive! Receiving..."); + // receive the VL mode into the current mode + if (!VLReceiver::receiveMode(&m_previewMode)) { + ERROR_LOG("Failed to receive mode"); + return; + } + DEBUG_LOGF("Success receiving mode: %u", m_previewMode.getPatternID()); + Modes::updateCurMode(&m_previewMode); + ByteStream modeBuffer; + m_previewMode.saveToBuffer(modeBuffer); + SerialComs::write(modeBuffer); + m_state = STATE_LISTEN_MODE_VL_DONE; +} + +void EditorConnection::showReceiveModeVL() +{ + if (VLReceiver::isReceiving()) { + // using uint32_t to avoid overflow, the result should be within 10 to 255 + //Leds::setAll(RGBColor(0, VLReceiver::percentReceived(), 0)); + Leds::setRange(LED_0, (LedPos)(VLReceiver::percentReceived() / 10), RGB_GREEN6); + Leds::setRange(LED_10, (LedPos)(LED_10 + (VLReceiver::percentReceived() / 10)), RGB_GREEN6); + } else { + Leds::setAll(RGB_WHITE0); + } +} + +bool EditorConnection::receiveModeIdx(uint8_t &idx) { // need at least the buffer size first - uint32_t size = 0; - if (m_receiveBuffer.size() < sizeof(size)) { + if (m_receiveBuffer.size() < sizeof(idx)) { // wait, not enough data available yet return false; } - // grab the size out of the start m_receiveBuffer.resetUnserializer(); - size = m_receiveBuffer.peek32(); - if (m_receiveBuffer.size() < (size + sizeof(size))) { - // don't unserialize yet, not ready - return false; - } // okay unserialize now, first unserialize the size - if (!m_receiveBuffer.unserialize32(&size)) { + if (!m_receiveBuffer.unserialize8(&idx)) { return false; } - // create a new ByteStream that will hold the full buffer of data - ByteStream buf(m_receiveBuffer.rawSize()); - // then copy everything from the receive buffer into the rawdata - // which is going to overwrite the crc/size/flags of the ByteStream - memcpy(buf.rawData(), m_receiveBuffer.data() + sizeof(size), - m_receiveBuffer.size() - sizeof(size)); - // clear the receive buffer - m_receiveBuffer.clear(); - // unserialize the mode into the demo mode - if (!m_previewMode.loadFromBuffer(buf)) { - // failure - } return true; } -void EditorConnection::handleCommand() +bool EditorConnection::receiveFirmwareSize(uint32_t &size) { - if (receiveMessage(EDITOR_VERB_PULL_MODES)) { - m_state = STATE_PULL_MODES; - } else if (receiveMessage(EDITOR_VERB_PUSH_MODES)) { - m_state = STATE_PUSH_MODES; - } else if (receiveMessage(EDITOR_VERB_DEMO_MODE)) { - m_state = STATE_DEMO_MODE; - } else if (receiveMessage(EDITOR_VERB_CLEAR_DEMO)) { - m_state = STATE_CLEAR_DEMO; - } else if (receiveMessage(EDITOR_VERB_PULL_EACH_MODE)) { - m_state = STATE_PULL_EACH_MODE; - } else if (receiveMessage(EDITOR_VERB_PUSH_EACH_MODE)) { - m_state = STATE_PUSH_EACH_MODE; - } else if (receiveMessage(EDITOR_VERB_TRANSMIT_VL)) { - sendCurModeVL(); + // need at least the buffer size first + if (m_receiveBuffer.size() < sizeof(size)) { + // wait, not enough data available yet + return false; + } + m_receiveBuffer.resetUnserializer(); + // okay unserialize now, first unserialize the size + if (!m_receiveBuffer.unserialize32(&size)) { + return false; } + return true; } diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.h b/VortexEngine/src/Menus/MenuList/EditorConnection.h index a8f5a729ff..e73e724cfc 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.h +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.h @@ -17,27 +17,42 @@ class EditorConnection : public Menu // broadcast the current preview mode over VL void sendCurModeVL(); + void listenModeVL(); + + // pull/push through the chromalink + bool pullHeaderChromalink(); + bool pushHeaderChromalink(); + bool pullModeChromalink(); + bool pushModeChromalink(); + bool writeDuoFirmware(); // handlers for clicks - void onShortClick() override; - void onLongClick() override; + void onShortClickM() override; + void onLongClickM() override; // menu conn void leaveMenu(bool doSave = false) override; private: + void handleCommand(); void showEditor(); void receiveData(); + void handleState(); void sendModes(); void sendModeCount(); void sendCurMode(); + bool receiveBuffer(ByteStream &buffer); + bool receiveFirmwareChunk(ByteStream &buffer); bool receiveModes(); bool receiveModeCount(); bool receiveMode(); bool receiveDemoMode(); - void handleCommand(); bool receiveMessage(const char *message); void clearDemo(); + void receiveModeVL(); + void showReceiveModeVL(); + bool receiveModeIdx(uint8_t &idx); + bool receiveFirmwareSize(uint32_t &idx); enum EditorConnectionState { // the editor is not connected @@ -71,6 +86,10 @@ class EditorConnection : public Menu STATE_TRANSMIT_MODE_VL, STATE_TRANSMIT_MODE_VL_DONE, + // receive a mode over VL + STATE_LISTEN_MODE_VL, + STATE_LISTEN_MODE_VL_DONE, + // editor pulls the modes from device (safer version) STATE_PULL_EACH_MODE, STATE_PULL_EACH_MODE_COUNT, @@ -84,18 +103,50 @@ class EditorConnection : public Menu STATE_PUSH_EACH_MODE_RECEIVE, STATE_PUSH_EACH_MODE_WAIT, STATE_PUSH_EACH_MODE_DONE, + + // pull the header from the chromalinked duo + STATE_PULL_HEADER_CHROMALINK, + + // pull a mode from the chromalinked duo + STATE_PULL_MODE_CHROMALINK, + STATE_PULL_MODE_CHROMALINK_SEND, + + // push the header to the chromalinked duo + STATE_PUSH_HEADER_CHROMALINK, + STATE_PUSH_HEADER_CHROMALINK_RECEIVE, + + // push a mode to the chromalinked duo + STATE_PUSH_MODE_CHROMALINK, + STATE_PUSH_MODE_CHROMALINK_RECEIVE_IDX, + STATE_PUSH_MODE_CHROMALINK_RECEIVE, + + // flash the firmware of the chromalinked duo + STATE_CHROMALINK_FLASH_FIRMWARE, + STATE_CHROMALINK_FLASH_FIRMWARE_RECEIVE_SIZE, + STATE_CHROMALINK_FLASH_FIRMWARE_RECEIVE, }; // state of the editor EditorConnectionState m_state; // the data that is received ByteStream m_receiveBuffer; + // receiver timeout + uint32_t m_timeOutStartTime; + // target chroma mode index for read/write + uint8_t m_chromaModeIdx; // Whether at least one command has been received yet bool m_allowReset; // the mode index to return to after iterating the modes to send them uint8_t m_previousModeIndex; // the number of modes that should be received uint8_t m_numModesToReceive; + + // current step of transfer + uint32_t m_curStep; + // firmware size for flashing duo + uint32_t m_firmwareSize; + // how much firmware written so far + uint32_t m_firmwareOffset; }; #endif diff --git a/VortexEngine/src/Menus/MenuList/FactoryReset.cpp b/VortexEngine/src/Menus/MenuList/FactoryReset.cpp index 287af81739..5106462e02 100644 --- a/VortexEngine/src/Menus/MenuList/FactoryReset.cpp +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.cpp @@ -57,12 +57,17 @@ Menu::MenuAction FactoryReset::run() return MENU_CONTINUE; } -void FactoryReset::onShortClick() +void FactoryReset::onShortClickL() { m_curSelection = (uint8_t)!m_curSelection; } -void FactoryReset::onLongClick() +void FactoryReset::onShortClickR() +{ + onShortClickL(); +} + +void FactoryReset::onLongClickM() { if (m_curSelection == 0) { // if the selection isn't actually on factory reset then just leave @@ -70,7 +75,7 @@ void FactoryReset::onLongClick() return; } // if the button hasn't been held long enough just return - if (g_pButton->holdDuration() <= (FACTORY_RESET_THRESHOLD_TICKS + MS_TO_TICKS(10))) { + if (g_pButtonM->holdDuration() <= (FACTORY_RESET_THRESHOLD_TICKS + MS_TO_TICKS(10))) { return; } // the button was held down long enough so actually perform the factory reset @@ -96,32 +101,50 @@ void FactoryReset::onLongClick() void FactoryReset::showReset() { + // if we're on exit just set the rest to blank if (m_curSelection == 0) { Leds::clearAll(); Leds::blinkAll(350, 350, RGB_WHITE0); return; } - bool isPressed = g_pButton->isPressed(); - if (!isPressed) { - Leds::clearAll(); - Leds::blinkAll(50, 50, RGB_RED4); + // otherwise we're not on exit, if the button isn't pressed + if (!g_pButtonM->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_pButtonM->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..f4469a1af2 100644 --- a/VortexEngine/src/Menus/MenuList/FactoryReset.h +++ b/VortexEngine/src/Menus/MenuList/FactoryReset.h @@ -13,8 +13,9 @@ class FactoryReset : public Menu MenuAction run() override; // handlers for clicks - void onShortClick() override; - void onLongClick() override; + void onShortClickL() override; + void onShortClickR() override; + void onLongClickM() override; private: void showReset(); diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp b/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp index 8772f88b8c..3bcb9b8897 100644 --- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.cpp @@ -49,13 +49,28 @@ Menu::MenuAction GlobalBrightness::run() return MENU_CONTINUE; } -void GlobalBrightness::onShortClick() +void GlobalBrightness::onShortClickR() { // include one extra option for the exit slot m_curSelection = (m_curSelection + 1) % (NUM_BRIGHTNESS_OPTIONS + 1); } -void GlobalBrightness::onLongClick() +void GlobalBrightness::onShortClickL() +{ + // include one extra option for the exit slot + if (!m_curSelection) { + m_curSelection = NUM_BRIGHTNESS_OPTIONS; + } else { + m_curSelection = m_curSelection - 1; + } +} + +void GlobalBrightness::onShortClickM() +{ + onLongClickM(); +} + +void GlobalBrightness::onLongClickM() { if (m_curSelection >= NUM_BRIGHTNESS_OPTIONS) { // no save exit diff --git a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h index 34f2b65cfc..875303fb9a 100644 --- a/VortexEngine/src/Menus/MenuList/GlobalBrightness.h +++ b/VortexEngine/src/Menus/MenuList/GlobalBrightness.h @@ -13,18 +13,24 @@ class GlobalBrightness : public Menu MenuAction run() override; // handlers for clicks - void onShortClick() override; - void onLongClick() override; + void onShortClickL() override; + void onShortClickR() override; + void onShortClickM() override; + void onLongClickM() override; private: void showBrightnessSelection(); // the list of brightness options - const uint8_t m_brightnessOptions[4] = { + const uint8_t m_brightnessOptions[8] = { BRIGHTNESS_OPTION_1, BRIGHTNESS_OPTION_2, BRIGHTNESS_OPTION_3, - BRIGHTNESS_OPTION_4 + BRIGHTNESS_OPTION_4, + BRIGHTNESS_OPTION_5, + BRIGHTNESS_OPTION_6, + BRIGHTNESS_OPTION_7, + BRIGHTNESS_OPTION_8, }; }; diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp index e3391c1df2..1906e4c573 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp @@ -4,8 +4,9 @@ #include "../../Serial/Serial.h" #include "../../Time/TimeControl.h" #include "../../Time/Timings.h" -#include "../../Wireless/IRReceiver.h" +#include "../../Wireless/VLReceiver.h" #include "../../Wireless/VLSender.h" +#include "../../Wireless/IRReceiver.h" #include "../../Wireless/IRSender.h" #include "../../Buttons/Button.h" #include "../../Modes/Modes.h" @@ -15,8 +16,10 @@ ModeSharing::ModeSharing(const RGBColor &col, bool advanced) : Menu(col, advanced), - m_sharingMode(ModeShareState::SHARE_RECEIVE), - m_timeOutStartTime(0) + m_sharingMode(ModeShareState::SHARE_RECEIVE_VL), + m_timeOutStartTime(0), + m_lastSendTime(0), + m_shouldEndSend(false) { } @@ -34,7 +37,7 @@ bool ModeSharing::init() // start on receive because it's the more responsive of the two // the odds of opening receive and then accidentally receiving // a mode that is being broadcast nearby is completely unlikely - beginReceivingIR(); + beginReceivingVL(); DEBUG_LOG("Entering Mode Sharing"); return true; } @@ -46,11 +49,11 @@ Menu::MenuAction ModeSharing::run() return result; } switch (m_sharingMode) { - case ModeShareState::SHARE_SEND_IR: - // render the 'send mode' lights - showSendModeIR(); - // continue sending any data as long as there is more to send - continueSendingIR(); + case ModeShareState::SHARE_RECEIVE_VL: + // render the 'receive mode' lights + showReceiveModeVL(); + // load any modes that are received + receiveModeVL(); break; case ModeShareState::SHARE_SEND_VL: // render the 'send mode' lights @@ -58,25 +61,42 @@ Menu::MenuAction ModeSharing::run() // continue sending any data as long as there is more to send continueSendingVL(); break; - case ModeShareState::SHARE_RECEIVE: + case ModeShareState::SHARE_RECEIVE_IR: // render the 'receive mode' lights - showReceiveMode(); + showReceiveModeIR(); // load any modes that are received receiveModeIR(); break; + case ModeShareState::SHARE_SEND_IR: + // render the 'send mode' lights + showSendModeIR(); + // continue sending any data as long as there is more to send + continueSendingIR(); + break; } return MENU_CONTINUE; } // handlers for clicks -void ModeSharing::onShortClick() +void ModeSharing::onShortClickM() { switch (m_sharingMode) { - case ModeShareState::SHARE_RECEIVE: + case ModeShareState::SHARE_RECEIVE_VL: // click while on receive -> end receive, start sending - IRReceiver::endReceiving(); - beginSendingIR(); - DEBUG_LOG("Switched to send mode"); + VLReceiver::endReceiving(); + beginSendingVL(); + DEBUG_LOG("Switched to send VL"); + break; + case ModeShareState::SHARE_RECEIVE_IR: + if (!IRSender::isSending()) { + beginSendingIR(); + DEBUG_LOG("Switched to send IR"); + } else { + m_shouldEndSend = true; + } + break; + case ModeShareState::SHARE_SEND_IR: + m_shouldEndSend = true; break; default: break; @@ -84,27 +104,38 @@ void ModeSharing::onShortClick() Leds::clearAll(); } -void ModeSharing::onLongClick() +// handlers for clicks +void ModeSharing::onShortClickL() { - Modes::updateCurMode(&m_previewMode); - leaveMenu(true); + switchVLIR(); } -void ModeSharing::beginSendingVL() +void ModeSharing::onShortClickR() { - // if the sender is sending then cannot start again - if (VLSender::isSending()) { - ERROR_LOG("Cannot begin sending, sender is busy"); - return; - } - m_sharingMode = ModeShareState::SHARE_SEND_VL; - // initialize it with the current mode data - VLSender::loadMode(Modes::curMode()); - // send the first chunk of data, leave if we're done - if (!VLSender::send()) { - // when send has completed, stores time that last action was completed to calculate interval between sends + switchVLIR(); +} + +void ModeSharing::switchVLIR() +{ + switch (m_sharingMode) { + case ModeShareState::SHARE_RECEIVE_VL: + VLReceiver::endReceiving(); beginReceivingIR(); + break; + case ModeShareState::SHARE_RECEIVE_IR: + IRReceiver::endReceiving(); + beginReceivingVL(); + break; + default: + break; } + Leds::clearAll(); +} + +void ModeSharing::onLongClickM() +{ + Modes::updateCurMode(&m_previewMode); + leaveMenu(true); } void ModeSharing::beginSendingIR() @@ -115,47 +146,47 @@ 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(); - } -} - -void ModeSharing::continueSendingVL() -{ - // if the sender isn't sending then nothing to do - if (!VLSender::isSending()) { - return; - } - if (!VLSender::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(); } + DEBUG_LOG("Switched to sending IR"); } 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(); } } void ModeSharing::beginReceivingIR() { - m_sharingMode = ModeShareState::SHARE_RECEIVE; + m_sharingMode = ModeShareState::SHARE_RECEIVE_IR; IRReceiver::beginReceiving(); + DEBUG_LOG("Switched to receiving IR"); } void ModeSharing::receiveModeIR() -{ + { // if reveiving new data set our last data time if (IRReceiver::onNewData()) { m_timeOutStartTime = Time::getCurtime(); @@ -184,6 +215,73 @@ void ModeSharing::receiveModeIR() } } +void ModeSharing::beginSendingVL() +{ + // if the sender is sending then cannot start again + if (VLSender::isSending()) { + ERROR_LOG("Cannot begin sending, sender is busy"); + return; + } + m_sharingMode = ModeShareState::SHARE_SEND_VL; + // initialize it with the current mode data + VLSender::loadMode(Modes::curMode()); + // send the first chunk of data, leave if we're done + if (!VLSender::send()) { + // when send has completed, stores time that last action was completed to calculate interval between sends + beginReceivingVL(); + } + DEBUG_LOG("Switched to sending VL"); +} + +void ModeSharing::continueSendingVL() +{ + // if the sender isn't sending then nothing to do + if (!VLSender::isSending()) { + return; + } + if (!VLSender::send()) { + // when send has completed, stores time that last action was completed to calculate interval between sends + beginReceivingVL(); + } +} + +void ModeSharing::beginReceivingVL() +{ + m_sharingMode = ModeShareState::SHARE_RECEIVE_VL; + VLReceiver::beginReceiving(); + DEBUG_LOG("Switched to receiving VL"); +} + +void ModeSharing::receiveModeVL() +{ + // if reveiving new data set our last data time + if (VLReceiver::onNewData()) { + m_timeOutStartTime = Time::getCurtime(); + // if our last data was more than time out duration reset the recveiver + } else if (m_timeOutStartTime > 0 && (m_timeOutStartTime + MAX_TIMEOUT_DURATION) < Time::getCurtime()) { + VLReceiver::resetVLState(); + m_timeOutStartTime = 0; + return; + } + // check if the VLReceiver has a full packet available + if (!VLReceiver::dataReady()) { + // nothing available yet + return; + } + DEBUG_LOG("Mode ready to receive! Receiving..."); + // receive the VL mode into the current mode + if (!VLReceiver::receiveMode(&m_previewMode)) { + ERROR_LOG("Failed to receive mode"); + return; + } + DEBUG_LOGF("Success receiving mode: %u", m_previewMode.getPatternID()); + if (!m_advanced) { + Modes::updateCurMode(&m_previewMode); + // leave menu and save settings, even if the mode was the same whatever + leaveMenu(true); + } +} + void ModeSharing::showSendModeVL() { // show a dim color when not sending @@ -193,19 +291,36 @@ void ModeSharing::showSendModeVL() void ModeSharing::showSendModeIR() { // show a dim color when not sending - Leds::clearAll(); + Leds::setAll(RGB_CYAN5); + Leds::setAllEvens(RGB_CYAN0); +} + +void ModeSharing::showReceiveModeVL() +{ + if (VLReceiver::isReceiving()) { + // using uint32_t to avoid overflow, the result should be within 10 to 255 + Leds::clearAll(); + Leds::setRange(LED_FIRST, (LedPos)(VLReceiver::percentReceived() / 10), RGBColor(0, 1, 0)); + } else { + if (m_advanced) { + m_previewMode.play(); + } else { + Leds::setAll(0x010101); + } + } } -void ModeSharing::showReceiveMode() +void ModeSharing::showReceiveModeIR() { - if (IRReceiver::isReceiving()) { + if (VLReceiver::isReceiving()) { // using uint32_t to avoid overflow, the result should be within 10 to 255 - Leds::setAll(RGBColor(0, IRReceiver::percentReceived(), 0)); + Leds::clearAll(); + Leds::setRange(LED_FIRST, (LedPos)(VLReceiver::percentReceived() / 10), RGBColor(0, 1, 0)); } else { if (m_advanced) { m_previewMode.play(); } else { - Leds::setAll(RGB_WHITE0); + Leds::setAll(RGB_CYAN0); } } } diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.h b/VortexEngine/src/Menus/MenuList/ModeSharing.h index dc5adf2357..ed15e7c8af 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.h +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.h @@ -13,31 +13,44 @@ class ModeSharing : public Menu MenuAction run() override; // handlers for clicks - void onShortClick() override; - void onLongClick() override; + void onShortClickM() override; + void onShortClickL() override; + void onShortClickR() override; + void onLongClickM() override; private: void beginSendingVL(); - void beginSendingIR(); void continueSendingVL(); + void beginReceivingVL(); + void receiveModeVL(); + + void beginSendingIR(); void continueSendingIR(); void beginReceivingIR(); void receiveModeIR(); void showSendModeVL(); void showSendModeIR(); - void showReceiveMode(); + void showReceiveModeVL(); + void showReceiveModeIR(); + + void switchVLIR(); enum class ModeShareState { - SHARE_SEND_IR, // send mode over ir - SHARE_SEND_VL, // send mode over vl - SHARE_RECEIVE, // receive mode + SHARE_RECEIVE_VL, + SHARE_SEND_VL, + SHARE_RECEIVE_IR, + SHARE_SEND_IR, }; ModeShareState m_sharingMode; // 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..81ae047bdd 100644 --- a/VortexEngine/src/Menus/MenuList/PatternSelect.cpp +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.cpp @@ -52,11 +52,21 @@ void PatternSelect::onLedSelected() m_srcLed = ledmapGetFirstLed(m_targetLeds); } -void PatternSelect::onShortClick() +void PatternSelect::onShortClickR() { nextPattern(); } +void PatternSelect::onShortClickL() +{ + previousPattern(); +} + +void PatternSelect::onShortClickM() +{ + onLongClickM(); +} + void PatternSelect::nextPatternID() { // increment to next pattern @@ -72,7 +82,7 @@ void PatternSelect::nextPatternID() beginList = PATTERN_MULTI_FIRST; } #endif - m_newPatternID = (PatternID)((m_newPatternID + 1) % endList); + m_newPatternID = (PatternID)((m_newPatternID + 1) % (endList + 1)); if (m_newPatternID > endList || m_newPatternID < beginList) { m_newPatternID = beginList; } @@ -101,7 +111,52 @@ void PatternSelect::nextPattern() DEBUG_LOGF("Iterated to pattern id %d", m_newPatternID); } -void PatternSelect::onLongClick() +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::onLongClickM() { bool needsSave = false; Mode *cur = Modes::curMode(); diff --git a/VortexEngine/src/Menus/MenuList/PatternSelect.h b/VortexEngine/src/Menus/MenuList/PatternSelect.h index 5355b65e61..7690dc6e32 100644 --- a/VortexEngine/src/Menus/MenuList/PatternSelect.h +++ b/VortexEngine/src/Menus/MenuList/PatternSelect.h @@ -19,12 +19,16 @@ class PatternSelect : public Menu void onLedSelected() override; // handlers for clicks - void onShortClick() override; - void onLongClick() override; + void onShortClickL() override; + void onShortClickR() override; + void onShortClickM() override; + void onLongClickM() override; private: void nextPatternID(); void nextPattern(); + void previousPatternID(); + void previousPattern(); // the patternid of the current demo PatternID m_newPatternID; diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.cpp b/VortexEngine/src/Menus/MenuList/Randomizer.cpp index 608cb3980e..0f78118e6c 100644 --- a/VortexEngine/src/Menus/MenuList/Randomizer.cpp +++ b/VortexEngine/src/Menus/MenuList/Randomizer.cpp @@ -104,7 +104,7 @@ Menu::MenuAction Randomizer::run() return MENU_CONTINUE; } -void Randomizer::onShortClick() +void Randomizer::onShortClickM() { if (m_needToSelect) { if (m_flags == RANDOMIZE_BOTH) { @@ -115,7 +115,7 @@ void Randomizer::onShortClick() return; } // if the user fast-clicks 3 times then toggle automode - if (m_autoCycle || g_pButton->onConsecutivePresses(AUTO_CYCLE_RANDOMIZER_CLICKS)) { + if (m_autoCycle || g_pButtonM->onConsecutivePresses(AUTO_CYCLE_RANDOMIZER_CLICKS)) { // toggle the auto cycle flag m_autoCycle = !m_autoCycle; // display a quick flash of either green or red to indicate whether auto mode is on or not @@ -126,7 +126,7 @@ void Randomizer::onShortClick() reRoll(); } -void Randomizer::onLongClick() +void Randomizer::onLongClickM() { // if done the randomization selection part if (m_needToSelect) { diff --git a/VortexEngine/src/Menus/MenuList/Randomizer.h b/VortexEngine/src/Menus/MenuList/Randomizer.h index 0575ab132f..b9612a2df4 100644 --- a/VortexEngine/src/Menus/MenuList/Randomizer.h +++ b/VortexEngine/src/Menus/MenuList/Randomizer.h @@ -18,8 +18,8 @@ class Randomizer : public Menu MenuAction run() override; // handlers for clicks - void onShortClick() override; - void onLongClick() override; + void onShortClickM() override; + void onLongClickM() override; // re-roll a new randomization with a given context on an led bool reRoll(); diff --git a/VortexEngine/src/Menus/Menus.cpp b/VortexEngine/src/Menus/Menus.cpp index abeaffa074..3b27f713d4 100644 --- a/VortexEngine/src/Menus/Menus.cpp +++ b/VortexEngine/src/Menus/Menus.cpp @@ -102,7 +102,7 @@ bool Menus::run() bool Menus::runMenuSelection() { - if (g_pButton->onShortClick()) { + if (g_pButtonR->onShortClick()) { // otherwise increment selection and wrap around at num menus m_selection = (m_selection + 1) % NUM_MENUS; #if ENABLE_EDITOR_CONNECTION == 1 @@ -120,6 +120,24 @@ bool Menus::runMenuSelection() Leds::clearAll(); return true; } + if (g_pButtonL->onShortClick()) { + // decrement selection and wrap around at num menus + m_selection = m_selection ? m_selection - 1 : NUM_MENUS - 1; +#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 = MENU_EDITOR_CONNECTION - 1; + } +#endif + DEBUG_LOGF("Cyling backwards to ring menu %u", m_selection); + // reset the open time so that it starts again + m_openTime = Time::getCurtime(); + // clear the leds + Leds::clearAll(); + return true; + } // clear the leds so it always fills instead of replacing Leds::clearAll(); // timings for blink later @@ -130,10 +148,10 @@ bool Menus::runMenuSelection() // if the button was long pressed then select this menu, but we // need to check the presstime to ensure we don't catch the initial // release after opening the ringmenu - if (g_pButton->pressTime() >= m_openTime) { + if (g_pButtonM->pressTime() >= m_openTime) { // whether to open advanced menus or not - bool openAdv = (g_pButton->holdDuration() > ADV_MENU_DURATION_TICKS) && advMenus; - if (g_pButton->onLongClick()) { + bool openAdv = (g_pButtonM->holdDuration() > ADV_MENU_DURATION_TICKS) && advMenus; + if (g_pButtonM->onRelease()) { // ringmenu is open so select the menu DEBUG_LOGF("Selected ringmenu %s", menuList[m_selection].menuName); // open the menu we have selected @@ -145,7 +163,7 @@ bool Menus::runMenuSelection() return true; } // if holding down to select the menu option - if (g_pButton->isPressed() && openAdv) { + if (g_pButtonM->isPressed() && openAdv) { // make it strobe aw yiss offtime = HYPERSTROBE_OFF_DURATION; ontime = HYPERSTROBE_ON_DURATION; @@ -162,7 +180,7 @@ bool Menus::runMenuSelection() } } // check if the advanced menus have been enabled - if (g_pButton->onConsecutivePresses(ADVANCED_MENU_CLICKS)) { + if (g_pButtonR->onConsecutivePresses(ADVANCED_MENU_CLICKS)) { // toggle the advanced menu Modes::setAdvancedMenus(!advMenus); // display a pink or red depending on whether the menu was enabled @@ -191,11 +209,23 @@ bool Menus::runCurMenu() return false; case Menu::MENU_CONTINUE: // if Menu continue run the click handlers for the menu - if (g_pButton->onShortClick()) { - m_pCurMenu->onShortClick(); + if (g_pButtonL->onShortClick()) { + m_pCurMenu->onShortClickL(); + } + if (g_pButtonL->onLongClick()) { + m_pCurMenu->onLongClickL(); + } + if (g_pButtonM->onShortClick()) { + m_pCurMenu->onShortClickM(); + } + if (g_pButtonM->onLongClick()) { + m_pCurMenu->onLongClickM(); + } + if (g_pButtonR->onShortClick()) { + m_pCurMenu->onShortClickR(); } - if (g_pButton->onLongClick()) { - m_pCurMenu->onLongClick(); + if (g_pButtonR->onLongClick()) { + m_pCurMenu->onLongClickR(); } break; case Menu::MENU_SKIP: @@ -255,16 +285,16 @@ void Menus::showSelection(RGBColor colval) { // blink the tip led white for 150ms when the short // click threshold has been surpassed - if (g_pButton->isPressed() && - g_pButton->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS && - g_pButton->holdDuration() < (SHORT_CLICK_THRESHOLD_TICKS + MS_TO_TICKS(250))) { + if (g_pButtonM->isPressed() && + g_pButtonM->holdDuration() > SHORT_CLICK_THRESHOLD_TICKS && + g_pButtonM->holdDuration() < (SHORT_CLICK_THRESHOLD_TICKS + MS_TO_TICKS(250))) { Leds::setAll(colval); } } bool Menus::checkOpen() { - return m_menuState != MENU_STATE_NOT_OPEN && g_pButton->releaseTime() > m_openTime; + return m_menuState != MENU_STATE_NOT_OPEN && g_pButtonM->releaseTime() > m_openTime; } bool Menus::checkInMenu() diff --git a/VortexEngine/src/Modes/DefaultModes.cpp b/VortexEngine/src/Modes/DefaultModes.cpp index a0f4be8b7c..ef535e5b0f 100644 --- a/VortexEngine/src/Modes/DefaultModes.cpp +++ b/VortexEngine/src/Modes/DefaultModes.cpp @@ -122,6 +122,27 @@ const default_mode_entry default_modes[MAX_MODES] = { RGB_GREEN, RGB_BLUE } + }, + { + PATTERN_STROBE, 2, { + RGB_RED, + RGB_GREEN, + } + }, + { + PATTERN_DRIP, 4, { + RGB_RED, + RGB_GREEN, + RGB_BLUE, + RGB_YELLOW + } + }, + { + PATTERN_DASHCYCLE, 3, { + RGB_RED, + RGB_GREEN, + RGB_BLUE + } } }; diff --git a/VortexEngine/src/Modes/Modes.cpp b/VortexEngine/src/Modes/Modes.cpp index a81a6d72db..f1c8c90245 100644 --- a/VortexEngine/src/Modes/Modes.cpp +++ b/VortexEngine/src/Modes/Modes.cpp @@ -11,6 +11,7 @@ #include "../Storage/Storage.h" #include "../Buttons/Buttons.h" #include "../Time/Timings.h" +#include "../Menus/Menus.h" #include "../Modes/Mode.h" #include "../Leds/Leds.h" #include "../Log/Log.h" @@ -82,9 +83,17 @@ void Modes::play() return; } // shortclick cycles to the next mode - if (g_pButton->onShortClick()) { + if (g_pButtonR->onShortClick()) { nextMode(); } + // shortclick cycles to the next mode + //if (g_pButtonM->onShortClick()) { + // Menus::openMenuSelection(); + //} + // shortclick cycles to the next mode + if (g_pButtonL->onShortClick()) { + previousMode(); + } // play the current mode m_pCurModeLink->play(); } diff --git a/VortexEngine/src/Patterns/Multi/BouncePattern.cpp b/VortexEngine/src/Patterns/Multi/BouncePattern.cpp index a4afaa1f71..dbf4590bcf 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 (((LED_COUNT * 2) - 2) ? ((LED_COUNT * 2) - 2) : 1) #define HALF_STEPS (TOTAL_STEPS / 2) BouncePattern::BouncePattern(const PatternArgs &args) : @@ -36,10 +36,10 @@ void BouncePattern::init() void BouncePattern::blinkOn() { Leds::setAll(m_colorset.cur()); - if (m_progress < PAIR_COUNT) { - Leds::setPair((Pair)m_progress, m_colorset.peekNext()); + if (m_progress < LED_COUNT) { + Leds::setIndex((LedPos)m_progress, m_colorset.peekNext()); } else { - Leds::setPair((Pair)(TOTAL_STEPS - m_progress), m_colorset.peekNext()); + Leds::setIndex((LedPos)(TOTAL_STEPS - m_progress), m_colorset.peekNext()); } } diff --git a/VortexEngine/src/Patterns/Multi/FillPattern.cpp b/VortexEngine/src/Patterns/Multi/FillPattern.cpp index 87e034e4c1..50a88eefa3 100644 --- a/VortexEngine/src/Patterns/Multi/FillPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/FillPattern.cpp @@ -28,13 +28,13 @@ 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()); + Leds::setRange(LED_FIRST, (LedPos)m_progress, m_colorset.peekNext()); + Leds::setRange((LedPos)m_progress, LED_LAST, m_colorset.cur()); } void FillPattern::poststep() { - m_progress = (m_progress + 1) % PAIR_COUNT; + m_progress = (m_progress + 1) % LED_COUNT; if (m_progress == 0) { m_colorset.getNext(); } diff --git a/VortexEngine/src/Patterns/Multi/HueShiftPattern.cpp b/VortexEngine/src/Patterns/Multi/HueShiftPattern.cpp index d86a35810f..800e1173bb 100644 --- a/VortexEngine/src/Patterns/Multi/HueShiftPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/HueShiftPattern.cpp @@ -75,9 +75,17 @@ void HueShiftPattern::play() m_cur.hue += sign; } HSVColor showColor = HSVColor(m_cur.hue, 255, 255); - // set the target led with the current HSV color - for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { - Leds::setIndex(pos, hsv_to_rgb_generic(showColor)); - showColor.hue = (showColor.hue + 5) % 256; + + // variable amount to shift, more LEDs should have smaller shifts + uint8_t shiftAmount = 108 / LED_COUNT; + // if you increment color with each led index there's a sharp contrast between the first and last led + // instead this creates a perfectly looped gradient between the first and last led which is better + for (LedPos pos = LED_FIRST; pos < (LED_COUNT / 2) + 1; ++pos) { + if (((LED_COUNT / 2) + pos) != LED_COUNT) { + // set the target led with the current HSV color + Leds::setIndex((LedPos)((LED_COUNT / 2) + pos), hsv_to_rgb_generic(showColor)); + } + Leds::setIndex((LedPos)((LED_COUNT / 2) - pos), hsv_to_rgb_generic(showColor)); + showColor.hue = (showColor.hue + shiftAmount) % 256; } } diff --git a/VortexEngine/src/Patterns/Multi/PulsishPattern.cpp b/VortexEngine/src/Patterns/Multi/PulsishPattern.cpp index 9c9e9b49b9..93c5fc6da6 100644 --- a/VortexEngine/src/Patterns/Multi/PulsishPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/PulsishPattern.cpp @@ -55,16 +55,16 @@ void PulsishPattern::play() { // when the step timer triggers if (m_stepTimer.alarm() == 0) { - m_progress = (m_progress + 1) % PAIR_COUNT; + m_progress = (m_progress + 1) % LED_COUNT; } switch (m_blinkTimer.alarm()) { case -1: // just return return; case 0: // turn on the leds - for (Pair pair = PAIR_FIRST; pair < PAIR_COUNT; ++pair) { - if (pair != m_progress) { - Leds::setPair(pair, m_colorset.cur()); + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + if (pos != m_progress) { + Leds::setIndex(pos, m_colorset.cur()); } } m_colorset.skip(); @@ -73,9 +73,9 @@ void PulsishPattern::play() } break; case 1: - for (Pair pair = PAIR_FIRST; pair < PAIR_COUNT; ++pair) { - if (pair != m_progress) { - Leds::clearPair(pair); + for (LedPos pos = LED_FIRST; pos < LED_COUNT; ++pos) { + if (pos != m_progress) { + Leds::clearIndex(pos); } } break; @@ -85,10 +85,10 @@ void PulsishPattern::play() case -1: // just return return; case 0: // turn on the leds - Leds::setPair((Pair)m_progress, m_colorset.get(0)); + Leds::setIndex((LedPos)m_progress, m_colorset.get(0)); break; case 1: - Leds::clearPair((Pair)m_progress); + Leds::clearIndex((LedPos)m_progress); break; } } diff --git a/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.cpp b/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.cpp index 2868c09e97..45fec63ead 100644 --- a/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/Sequencer/ChaserPattern.cpp @@ -1,7 +1,7 @@ #include "ChaserPattern.h" // This controls the ratio of chaser dots to LED_COUNT. Default 1 chaser per 7 LEDs. Range: 1-LED_COUNT. -#define CHASER_RATIO 7 +#define CHASER_RATIO 5 // This pattern aims to be a demonstration of the sequencer. diff --git a/VortexEngine/src/Patterns/Multi/SnowballPattern.cpp b/VortexEngine/src/Patterns/Multi/SnowballPattern.cpp index 32d7121c17..92667b4ca2 100644 --- a/VortexEngine/src/Patterns/Multi/SnowballPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/SnowballPattern.cpp @@ -2,7 +2,7 @@ #include "../../Leds/Leds.h" -#define WORM_SIZE 6 +#define WORM_SIZE LED_COUNT / 3 SnowballPattern::SnowballPattern(const PatternArgs &args) : BlinkStepPattern(args), diff --git a/VortexEngine/src/Patterns/Multi/SparkleTracePattern.cpp b/VortexEngine/src/Patterns/Multi/SparkleTracePattern.cpp index 2221850d58..e5efe62cf4 100644 --- a/VortexEngine/src/Patterns/Multi/SparkleTracePattern.cpp +++ b/VortexEngine/src/Patterns/Multi/SparkleTracePattern.cpp @@ -21,10 +21,17 @@ void SparkleTracePattern::blinkOn() Leds::setAll(m_colorset.get(0)); } +void SparkleTracePattern::blinkOff() +{ + //this empty overriden function must be here to prevent the base + //blinkOff function from causing the ribbon in the blinkOn function + //to strobe instead +} + void SparkleTracePattern::poststep() { - for (uint8_t dot = 0; dot < 4; ++dot) { - Leds::setPair((Pair)m_randCtx.next8(PAIR_FIRST, PAIR_LAST), m_colorset.cur()); + for (uint8_t dot = 0; dot < LED_COUNT / 6; ++dot) { + Leds::setIndex((LedPos)m_randCtx.next8(LED_FIRST, LED_LAST), m_colorset.cur()); } m_colorset.skip(); if (m_colorset.curIndex() == 0) { diff --git a/VortexEngine/src/Patterns/Multi/SparkleTracePattern.h b/VortexEngine/src/Patterns/Multi/SparkleTracePattern.h index ef03a71827..13f8804021 100644 --- a/VortexEngine/src/Patterns/Multi/SparkleTracePattern.h +++ b/VortexEngine/src/Patterns/Multi/SparkleTracePattern.h @@ -13,6 +13,7 @@ class SparkleTracePattern : public BlinkStepPattern protected: virtual void blinkOn() override; + virtual void blinkOff() override; virtual void poststep() override; Random m_randCtx; diff --git a/VortexEngine/src/Patterns/Multi/TheaterChasePattern.cpp b/VortexEngine/src/Patterns/Multi/TheaterChasePattern.cpp index fbd527c7db..08f9cd8437 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 / 4) 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_OPPOSITES_1; m_stepCounter = 0; } @@ -32,12 +32,20 @@ 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; + if (m_stepCounter == 0) { + m_ledPositions = MAP_OPPOSITES_1; + } + if (m_stepCounter == 1) { + m_ledPositions = MAP_OPPOSITES_2; + } + if (m_stepCounter == 2) { + m_ledPositions = MAP_OPPOSITES_3; + } + if (m_stepCounter == 3) { + m_ledPositions = MAP_OPPOSITES_4; + } + if (m_stepCounter == 4) { + m_ledPositions = MAP_OPPOSITES_5; } // 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..52be6c91ab 100644 --- a/VortexEngine/src/Patterns/Multi/VortexPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/VortexPattern.cpp @@ -33,8 +33,9 @@ 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::setIndex((LedPos)(m_progress), m_colorset.peekNext()); + int offset = (m_progress != 0) ? m_progress : 10; + Leds::setIndex((LedPos)(LED_COUNT - (offset)), m_colorset.peekNext()); } void VortexPattern::poststep() diff --git a/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp b/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp index 1a2b19b185..595f0587d3 100644 --- a/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp +++ b/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp @@ -5,19 +5,7 @@ #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 -}; +#define VORTEX_WIPE_STEPS (LED_COUNT/2) VortexWipePattern::VortexWipePattern(const PatternArgs &args) : BlinkStepPattern(args), @@ -43,17 +31,15 @@ void VortexWipePattern::init() void VortexWipePattern::blinkOn() { - for (int index = 0; index < m_progress; ++index) { - Leds::setIndex(ledStepPositions[index], m_colorset.peekNext()); - } - for (int index = m_progress; index < LED_COUNT; ++index) { - Leds::setIndex(ledStepPositions[index], m_colorset.cur()); - } + Leds::setRange(LED_FIRST, (LedPos)m_progress, m_colorset.peekNext()); + Leds::setRange((LedPos)(LED_COUNT/2), (LedPos)((LED_COUNT/2) + m_progress), m_colorset.peekNext()); + Leds::setRange((LedPos)m_progress, (LedPos)((LED_COUNT/2) - 1), m_colorset.cur()); + Leds::setRange((LedPos)((LED_COUNT / 2) + m_progress), (LedPos)(LED_COUNT - 1), m_colorset.cur()); } void VortexWipePattern::poststep() { - m_progress = (m_progress + 1) % LED_COUNT; + m_progress = (m_progress + 1) % VORTEX_WIPE_STEPS; 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/WarpPattern.cpp b/VortexEngine/src/Patterns/Multi/WarpPattern.cpp index 24770e4328..318250b313 100644 --- a/VortexEngine/src/Patterns/Multi/WarpPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/WarpPattern.cpp @@ -30,12 +30,12 @@ void WarpPattern::init() void WarpPattern::blinkOn() { Leds::setAll(m_colorset.cur()); - Leds::setPair((Pair)m_progress, m_colorset.peekNext()); + Leds::setIndex((LedPos)m_progress, m_colorset.peekNext()); } void WarpPattern::poststep() { - m_progress = (m_progress + 1) % PAIR_COUNT; + m_progress = (m_progress + 1) % LED_COUNT; if (m_progress == 0) { m_colorset.getNext(); } diff --git a/VortexEngine/src/Patterns/Multi/WarpWormPattern.cpp b/VortexEngine/src/Patterns/Multi/WarpWormPattern.cpp index b6311ad339..d50190ddd8 100644 --- a/VortexEngine/src/Patterns/Multi/WarpWormPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/WarpWormPattern.cpp @@ -29,7 +29,7 @@ void WarpWormPattern::init() void WarpWormPattern::blinkOn() { - int wormSize = 6; + int wormSize = LED_COUNT / 3; Leds::setAll(m_colorset.get(0)); for (int body = 0; body < wormSize; ++body) { if (body + m_progress < LED_COUNT) { diff --git a/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp b/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp index 02062917eb..fdcbaa8b27 100644 --- a/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp @@ -13,7 +13,17 @@ const LedPos ZigzagPattern::ledStepPositions[] = { LED_5, LED_7, LED_9, - + LED_11, + LED_13, + LED_15, + LED_17, + LED_19, + + LED_18, + LED_16, + LED_14, + LED_12, + LED_10, LED_8, LED_6, LED_4, diff --git a/VortexEngine/src/Patterns/Patterns.h b/VortexEngine/src/Patterns/Patterns.h index 65f3b90bb7..e6bbf409dc 100644 --- a/VortexEngine/src/Patterns/Patterns.h +++ b/VortexEngine/src/Patterns/Patterns.h @@ -97,6 +97,7 @@ enum PatternID : int8_t INTERNAL_PATTERNS_END, // <<< DON'T USE OR TOUCH THIS ONE PATTERN_MULTI_LAST = (INTERNAL_PATTERNS_END - 1), PATTERN_MULTI_COUNT = (PATTERN_MULTI_LAST - PATTERN_MULTI_FIRST) + 1, + PATTERN_LAST = PATTERN_MULTI_LAST, PATTERN_COUNT = (PATTERN_LAST - PATTERN_FIRST) + 1, // total number of patterns }; diff --git a/VortexEngine/src/Serial/Serial.cpp b/VortexEngine/src/Serial/Serial.cpp index 5079e80455..fdc261a406 100644 --- a/VortexEngine/src/Serial/Serial.cpp +++ b/VortexEngine/src/Serial/Serial.cpp @@ -3,6 +3,9 @@ #include "../Serial/ByteStream.h" #include "../Time/TimeControl.h" #include "../Time/Timings.h" +#include "../Modes/Modes.h" +#include "../Menus/MainMenu.h" +#include "../Menus/Menus.h" #include "../Log/Log.h" #include "../VortexEngine.h" @@ -12,14 +15,19 @@ #include #endif +#ifdef VORTEX_EMBEDDED +#include +#include "soc/usb_serial_jtag_reg.h" +#include "HWCDC.h" +#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(); return true; } @@ -29,9 +37,45 @@ void SerialComs::cleanup() bool SerialComs::isConnected() { +#ifdef VORTEX_EMBEDDED + if (!isConnectedReal()) { + m_serialConnected = false; + return false; + } +#endif return m_serialConnected; } +bool SerialComs::isConnectedReal() +{ + static bool lastState = true; + static unsigned long lastChangeTime = 0; + +#ifdef VORTEX_EMBEDDED + bool currentState = HWCDCSerial.isConnected(); +#else + bool currentState = true; +#endif + + unsigned long currentTime = Time::getCurtime(); + if (!currentState) { + // Check if the state has been false for at least 1 millisecond + if (lastChangeTime && (currentTime - lastChangeTime) < 300) { + return lastState; // State hasn't been false long enough + } + if (currentState != lastState) { + // Update the last state and change time + lastChangeTime = currentTime; + lastState = currentState; + return lastState; + } + } else { + lastState = currentState; + lastChangeTime = currentTime; + } + return currentState; +} + // check for any serial connection or messages bool SerialComs::checkSerial() { @@ -53,18 +97,19 @@ bool SerialComs::checkSerial() } Vortex::vcallbacks()->serialBegin(SERIAL_BAUD_RATE); #else + // Begin serial communications (turns out this is actually a NO-OP in trinket source) + Serial.begin(SERIAL_BAUD_RATE); // This will check if the serial communication is open - if (!Serial.available()) { + if (!Serial && !Serial.available()) { // serial is not connected return false; } - // Begin serial communications - Serial.begin(SERIAL_BAUD_RATE); #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, ...) @@ -134,6 +179,26 @@ void SerialComs::read(ByteStream &byteStream) #endif } +void SerialComs::readAmount(uint32_t amount, ByteStream &byteStream) +{ +#if VORTEX_SLIM == 0 + if (!isConnected()) { + return; + } + do { + uint8_t byte = 0; +#ifdef VORTEX_LIB + if (!Vortex::vcallbacks()->serialRead((char *)&byte, 1)) { + return; + } +#else + byte = Serial.read(); +#endif + byteStream.serialize8(byte); + } while (--amount > 0); +#endif +} + bool SerialComs::dataReady() { #if VORTEX_SLIM == 0 diff --git a/VortexEngine/src/Serial/Serial.h b/VortexEngine/src/Serial/Serial.h index 0121b5bbb0..1ae2fe9d06 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(); @@ -29,6 +32,9 @@ class SerialComs // read a message from serial static void read(ByteStream &byteStream); + // read a specific chunk size + static void readAmount(uint32_t amount, ByteStream &byteStream); + // data in the socket ready to read static bool dataReady(); @@ -36,6 +42,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..a41e5b491b 100644 --- a/VortexEngine/src/Storage/Storage.cpp +++ b/VortexEngine/src/Storage/Storage.cpp @@ -12,10 +12,13 @@ #include "../VortexLib/VortexLib.h" #endif -#ifndef VORTEX_EMBEDDED +#ifdef VORTEX_EMBEDDED +#include "../Leds/Leds.h" +#include +#else // VORTEX_EMBEDDED #ifdef _WIN32 #include -#else +#else // _WIN32 #include #endif #endif @@ -30,6 +33,7 @@ std::string Storage::m_storageFilename; #endif uint32_t Storage::m_lastSaveSize = 0; +uint8_t Storage::m_storagePage = 0; Storage::Storage() { @@ -49,6 +53,11 @@ void Storage::cleanup() { } +void Storage::setStoragePage(uint8_t page) +{ + m_storagePage = page; +} + // store a serial buffer to storage bool Storage::write(uint16_t slot, ByteStream &buffer) { @@ -69,7 +78,19 @@ bool Storage::write(uint16_t slot, ByteStream &buffer) // just in case buffer.recalcCRC(); #ifdef VORTEX_EMBEDDED - // implement device storage here + // ESP32 Arduino environment + nvs_handle_t nvs; + uint8_t name[3] = { (uint8_t)('a' + m_storagePage), (uint8_t)('a' + (uint8_t)slot), 0 }; + esp_err_t err = nvs_open((char *)name, NVS_READWRITE, &nvs); + if (err != ESP_OK) { + return false; + } + err = nvs_set_blob(nvs, (char *)name, buffer.rawData(), buffer.rawSize()); + if (err != ESP_OK) { + nvs_close(nvs); + return false; + } + nvs_close(nvs); #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) { @@ -77,7 +98,7 @@ bool Storage::write(uint16_t slot, ByteStream &buffer) return false; } DWORD written = 0; - DWORD offset = slot * MAX_MODE_SIZE; + DWORD offset = (slot * MAX_MODE_SIZE) + (m_storagePage * (MAX_MODE_SIZE * MAX_MODES)); SetFilePointer(hFile, offset, NULL, FILE_BEGIN); if (!WriteFile(hFile, buffer.rawData(), MAX_MODE_SIZE, &written, NULL)) { // error @@ -89,7 +110,7 @@ bool Storage::write(uint16_t slot, ByteStream &buffer) if (!f) { return false; } - long offset = slot * MAX_MODE_SIZE; + long offset = (slot * MAX_MODE_SIZE) + (m_storagePage * (MAX_MODE_SIZE * MAX_MODES)); fseek(f, offset, SEEK_SET); if (!fwrite(buffer.rawData(), sizeof(char), MAX_MODE_SIZE, f)) { return false; @@ -118,7 +139,24 @@ bool Storage::read(uint16_t slot, ByteStream &buffer) return false; } #ifdef VORTEX_EMBEDDED - // implement device storage here + // ESP32 Arduino environment + nvs_handle_t nvs; + uint8_t name[3] = { (uint8_t)('a' + m_storagePage), (uint8_t)('a' + (uint8_t)slot), 0 }; + esp_err_t err = nvs_open((char *)name, NVS_READWRITE, &nvs); + if (err != ESP_OK) { + nvs_close(nvs); + //Leds::holdAll(RGB_YELLOW); + return false; + } + size_t read_size = size; + // build a two letter name based on the slot and page + err = nvs_get_blob(nvs, (char *)name, buffer.rawData(), &read_size); + if (err != ESP_OK) { + nvs_close(nvs); + //Leds::holdAll(RGB_PURPLE); + return false; + } + nvs_close(nvs); #elif defined(_WIN32) HANDLE hFile = CreateFile(STORAGE_FILENAME, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { @@ -126,7 +164,7 @@ bool Storage::read(uint16_t slot, ByteStream &buffer) return false; } DWORD bytesRead = 0; - DWORD offset = slot * MAX_MODE_SIZE; + DWORD offset = (slot * MAX_MODE_SIZE) + (m_storagePage * (MAX_MODE_SIZE * MAX_MODES)); SetFilePointer(hFile, offset, NULL, FILE_BEGIN); if (!ReadFile(hFile, buffer.rawData(), MAX_MODE_SIZE, &bytesRead, NULL)) { // error @@ -138,7 +176,7 @@ bool Storage::read(uint16_t slot, ByteStream &buffer) if (!f) { return false; } - long offset = slot * MAX_MODE_SIZE; + long offset = (slot * MAX_MODE_SIZE) + (m_storagePage * (MAX_MODE_SIZE * MAX_MODES)); fseek(f, offset, SEEK_SET); if (!fread(buffer.rawData(), sizeof(char), MAX_MODE_SIZE, f)) { return false; diff --git a/VortexEngine/src/Storage/Storage.h b/VortexEngine/src/Storage/Storage.h index 19f0999f54..58b39072f0 100644 --- a/VortexEngine/src/Storage/Storage.h +++ b/VortexEngine/src/Storage/Storage.h @@ -18,6 +18,9 @@ class Storage static bool init(); static void cleanup(); + // set the global storage page, the chromadeck has 8 pages of 16 slots each + static void setStoragePage(uint8_t page); + // store a serial buffer to storage static bool write(uint16_t slot, ByteStream &buffer); // read a serial buffer from storage @@ -38,6 +41,9 @@ class Storage // the size of the last save static uint32_t m_lastSaveSize; + + // the curren storage page + static uint8_t m_storagePage; }; #endif diff --git a/VortexEngine/src/Time/TimeControl.cpp b/VortexEngine/src/Time/TimeControl.cpp index 8493deb667..ba13756287 100644 --- a/VortexEngine/src/Time/TimeControl.cpp +++ b/VortexEngine/src/Time/TimeControl.cpp @@ -2,6 +2,8 @@ #include +#include "../VortexConfig.h" + #include "../Memory/Memory.h" #include "../Log/Log.h" @@ -22,6 +24,10 @@ static LARGE_INTEGER tps; static LARGE_INTEGER start; #endif +#if VORTEX_EMBEDDED == 1 +#include +#endif + // static members #if VARIABLE_TICKRATE == 1 uint32_t Time::m_tickrate = DEFAULT_TICKRATE; @@ -190,7 +196,7 @@ uint32_t Time::microseconds() void Time::delayMicroseconds(uint32_t us) { -#ifdef _WIN32 +#if VORTEX_EMBEDDED == 1 || defined(_WIN32) uint32_t newtime = microseconds() + us; while (microseconds() < newtime) { // busy loop @@ -202,11 +208,8 @@ void Time::delayMicroseconds(uint32_t us) void Time::delayMilliseconds(uint32_t ms) { -#ifdef VORTEX_EMBEDDED - // not very accurate - for (uint16_t i = 0; i < ms; ++i) { - delayMicroseconds(1000); - } +#if VORTEX_EMBEDDED == 1 + delay(ms); #elif defined(_WIN32) Sleep(ms); #else diff --git a/VortexEngine/src/UPDI/updi.cpp b/VortexEngine/src/UPDI/updi.cpp new file mode 100644 index 0000000000..15c089b2c0 --- /dev/null +++ b/VortexEngine/src/UPDI/updi.cpp @@ -0,0 +1,988 @@ +#include "updi.h" + +#ifdef VORTEX_EMBEDDED + +#include "../VortexConfig.h" +#include "../Time/TimeControl.h" +#include "../Log/Log.h" +#include "../Leds/Leds.h" +#include "../Serial/ByteStream.h" +#include "../Patterns/Pattern.h" +#include "../Modes/Mode.h" + +#include "driver/uart.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "driver/gpio.h" +#include "soc/uart_periph.h" +#include "esp_rom_gpio.h" +#include "hal/uart_types.h" +#include "soc/gpio_struct.h" +#include "soc/soc.h" +#include "soc/io_mux_reg.h" +#include "esp_timer.h" +#include "driver/timer.h" +#include + +#define HIGH 0x1 +#define LOW 0x0 + +#define FLASH_PAGE_SIZE 128 +#define EEPROM_PAGE_SIZE 64 + +// Define GPIO pin for UPDI communication +#define UPDI_PIN GPIO_NUM_10 + +// Timing macros (adjust as needed for your specific setup) +#define BIT_TIME_US 30 +#define UPDI_BREAK_PAUSE_US 2000 +#define UPDI_BREAK_DELAY_US 14000 + +// Direct GPIO manipulation macros using ESP32 registers +#define GPIO_SET_HIGH(gpio_num) (GPIO.out_w1ts.val = (1U << gpio_num)) +#define GPIO_SET_LOW(gpio_num) (GPIO.out_w1tc.val = (1U << gpio_num)) +#define GPIO_READ(gpio_num) ((GPIO.in.val >> gpio_num) & 0x1) + +// Configure GPIO for output +#define GPIO_SET_OUTPUT(gpio_num) (GPIO.enable_w1ts.val = (1U << gpio_num)) + +// Configure GPIO for input +// the key here is setting the gpio as GPIO_FLOATING to prevent contention +#define GPIO_SET_INPUT(gpio_num) (GPIO.enable_w1tc.val = (1U << gpio_num)); gpio_set_pull_mode((gpio_num_t)gpio_num, GPIO_FLOATING); + +// the max size of a single mode +#define DUO_MODE_SIZE 76 +// the size of the actual storage header data +#define DUO_HEADER_SIZE 15 +// the full size of the storage header, data + metadata (first 27 bytes of eeprom) +#define DUO_HEADER_FULL_SIZE (DUO_HEADER_SIZE + 12) +// the base address of eeprom and where the save header starts +// followed by 3 modes each 76 makes 255 bytes of eeprom +#define DUO_EEPROM_BASE 0x1400 +// then the next 6 modes are stored in flash from 0xfe00 to 0x10000 +// which is 512 bytes of space that can be adjusted +#define DUO_FLASH_STORAGE_BASE 0xFe00 +// the actual firmware is 0x8000 to 0x10000 +#define DUO_FIRMWARE_BASE 0x8000 + +// the old duo header was embedded in the entire mode save +// so we have to load the whole modes +#define LEGACY_DUO_HEADER_SIZE_1 5 +#define LEGACY_DUO_HEADER_SIZE_2 255 + + +UPDI::StorageType UPDI::m_storageType = MODERN_STORAGE; +#endif + +bool UPDI::init() +{ +#ifdef VORTEX_EMBEDDED + m_storageType = MODERN_STORAGE; +#endif + return true; +} + +void UPDI::cleanup() +{ +} + +uint8_t UPDI::isConnected() +{ +#ifdef VORTEX_EMBEDDED + sendDoubleBreak(); + stcs(Control_A, 0x6); + return cpu_mode<0xEF>(); +#else + return true; +#endif +} + +bool UPDI::readHeader(ByteStream &header) +{ +#ifdef VORTEX_EMBEDDED + if (!header.init(DUO_HEADER_SIZE)) { + return false; + } + enterProgrammingMode(); + uint8_t *ptr = (uint8_t *)header.rawData(); + uint16_t addr = DUO_EEPROM_BASE; + stptr_p((const uint8_t *)&addr, 2); + for (uint16_t i = 0; i < 4; ++i) { + ptr[i] = ldinc_b(); + } + const uint32_t size = *(uint32_t *)ptr; + if (!size) { + return false; + } + // more than 30 is old old duo where header is combined with save + if (size > 30) { + return readHeaderLegacy2(header); + } + // less than 6 is old duo where header is separate but smaller (no build number) + if (size < 6) { + return readHeaderLegacy1(header); + } + // modern duo header is 27 total and separate from modes + stptr_p((const uint8_t *)&addr, 2); + for (uint16_t i = 0; i < header.rawSize(); ++i) { + ptr[i] = ldinc_b(); + } + header.sanity(); + if (!header.checkCRC()) { + header.clear(); + ERROR_LOG("ERROR Header CRC Invalid!"); + reset(); + return false; + } + // major.minor are the first two bytes of the buffer + uint8_t major = header.data()[0]; + uint8_t minor = header.data()[1]; + // build was only added to this storage space later on + uint8_t build = (header.size() > 5) ? header.data()[5] : 0; + // Modern Duo with segmented header and build number in header + m_storageType = MODERN_STORAGE; +#endif + return true; +} + +#ifdef VORTEX_EMBEDDED +// kinda old duos like 1.2.0 to 1.3.0 +bool UPDI::readHeaderLegacy1(ByteStream &header) +{ + // around 1.2.0 the duo storage was segmented to fix the storage issue + // so the header is a separate buffer that's still at the start but only + // takes up about 17 bytes (12 + 5) of the start of the eeprom because + // it is contained in it's own ByteStream. Later on the size was increased + // by 10 bytes and 1 more of those bytes were used to store the build number + if (!header.init(LEGACY_DUO_HEADER_SIZE_1)) { + return false; + } + enterProgrammingMode(); + uint8_t *ptr = (uint8_t *)header.rawData(); + uint16_t addr = DUO_EEPROM_BASE; + stptr_p((const uint8_t *)&addr, 2); + for (uint16_t i = 0; i < header.rawSize(); ++i) { + ptr[i] = ldinc_b(); + } + header.sanity(); + if (!header.checkCRC()) { + header.clear(); + ERROR_LOG("ERROR Header CRC Invalid!"); + reset(); + return false; + } + // major.minor are the first two bytes of the buffer + uint8_t major = header.data()[0]; + uint8_t minor = header.data()[1]; + // LEGACY DUO! Old Storage format with segmented header and no build number + m_storageType = LEGACY_STORAGE_1; + return true; +} + +// really old duos like 1.0.0 +bool UPDI::readHeaderLegacy2(ByteStream &header) +{ + // Different size for old old duos, the whole eeprom was the only storage + // and it was thought the userrow provided another 128 but in reality that + // didn't actually work and only 255 bytes were available causing a bad + // storage bug that erased modes. So this reads the entire storage even + // though we only need the header at the start + if (!header.init(LEGACY_DUO_HEADER_SIZE_2)) { + return false; + } + //enterProgrammingMode(); + uint8_t *ptr = (uint8_t *)header.rawData(); + uint16_t addr = DUO_EEPROM_BASE; + stptr_p((const uint8_t *)&addr, 2); + for (uint16_t i = 0; i < header.rawSize(); ++i) { + ptr[i] = ldinc_b(); + } + header.sanity(); + if (!header.checkCRC()) { + header.clear(); + ERROR_LOG("ERROR Header CRC Invalid!"); + reset(); + return false; + } + // major.minor are the first two bytes of the buffer + uint8_t major = header.data()[0]; + uint8_t minor = header.data()[1]; + // build was only added to this storage space later on + uint8_t build = 0; + // LEGACY DUO! Old Storage format with combined header and no build number + m_storageType = LEGACY_STORAGE_2; + return true; +} + +#endif + +bool UPDI::readMode(uint8_t idx, ByteStream &modeBuffer) +{ +#ifdef VORTEX_EMBEDDED + if (m_storageType == LEGACY_STORAGE_2) { + // nope I'm not going to write code to read the old duos they can + // just send the mode c2c to the deck if they want to save it then + // they can update their duo and push the mode back to it + return false; + } + // initialize mode buffer + if (!modeBuffer.init(DUO_MODE_SIZE)) { + return false; + } + enterProgrammingMode(); + // DUO_MODE_SIZE is the max duo mode size (the slot size) + uint8_t *ptr = (uint8_t *)modeBuffer.rawData(); + uint16_t numBytes = modeBuffer.rawSize(); + uint16_t base; + // there are 3 modes in the eeprom after the header + if (idx < 3) { + // DUO_EEPROM_BASE is eeprom base + // DUO_HEADER_FULL_SIZE is size of duo header + // DUO_MODE_SIZE is size of each duo mode + base = DUO_EEPROM_BASE + (DUO_HEADER_FULL_SIZE) + (idx * DUO_MODE_SIZE); + // legacy storage 1 the header is a bit smaller so the first mode is a bit + // sooner in the eeprom but that's the only difference + if (m_storageType == LEGACY_STORAGE_1) { + base -= 10; + } + } else { + // DUO_FLASH_STORAGE_BASE is the end of flash, 0x200 before + base = DUO_FLASH_STORAGE_BASE + ((idx - 3) * DUO_MODE_SIZE); + } + stptr_p((const uint8_t *)&base, 2); + //rep(numBytes - 1); + for (uint16_t i = 0; i < modeBuffer.rawSize(); ++i) { + ptr[i] = ldinc_b(); + } + modeBuffer.sanity(); + if (!modeBuffer.checkCRC()) { + modeBuffer.clear(); + ERROR_LOG("ERROR Header CRC Invalid!"); + reset(); + return false; + } +#endif + return true; +} + +bool UPDI::writeHeader(ByteStream &headerBuffer) +{ +#ifdef VORTEX_EMBEDDED + if (m_storageType != MODERN_STORAGE) { + // nope! + return false; + } + enterProgrammingMode(); + // DUO_EEPROM_BASE is eeprom base + // DUO_HEADER_FULL_SIZE is size of duo header + // DUO_MODE_SIZE is size of each duo mode + uint16_t base = DUO_EEPROM_BASE; + // the size of the mode being written out + uint16_t size = headerBuffer.rawSize(); + uint8_t *ptr = (uint8_t *)headerBuffer.rawData(); + + // read out the page so the |xxxxxxxxxxx part + ByteStream pageBuffer(EEPROM_PAGE_SIZE); + uint8_t *pagePtr = (uint8_t *)pageBuffer.data(); + uint16_t end = base + DUO_HEADER_FULL_SIZE; + stptr_p((const uint8_t *)&end, 2); + for (uint16_t i = DUO_HEADER_FULL_SIZE; i < EEPROM_PAGE_SIZE; ++i) { + pagePtr[i] = ldinc_b(); + } + nvmWait(); + + // overlay the actual data, so the SSSSS part of |xxxxxxSSSS + memcpy(pagePtr, ptr, size); + + for (uint16_t i = 0; i < EEPROM_PAGE_SIZE; ++i) { + sts_b(base + i, pagePtr[i]); + } + nvmCmd(NVM_ERWP); + nvmWait(); +#endif + return true; +} + +bool UPDI::writeMode(uint8_t idx, ByteStream &modeBuffer) +{ +#ifdef VORTEX_EMBEDDED + if (m_storageType != MODERN_STORAGE) { + // nope! + return false; + } + modeBuffer.sanity(); + if (!modeBuffer.checkCRC()) { + ERROR_LOG("ERROR Mode CRC Invalid!"); + reset(); + return false; + } + // there are 3 modes in the eeprom after the header + if (idx < 3) { + return writeModeEeprom(idx, modeBuffer); + } + return writeModeFlash(idx, modeBuffer); +#else + return true; +#endif +} + +#ifdef VORTEX_EMBEDDED +bool UPDI::writeModeEeprom(uint8_t idx, ByteStream &modeBuffer) +{ + enterProgrammingMode(); + // DUO_EEPROM_BASE is eeprom base + // DUO_HEADER_FULL_SIZE is size of duo header + // DUO_MODE_SIZE is size of each duo mode + uint16_t base = DUO_EEPROM_BASE + DUO_HEADER_FULL_SIZE + (idx * DUO_MODE_SIZE); + // the size of the mode being written out + uint16_t size = modeBuffer.rawSize(); + uint8_t *ptr = (uint8_t *)modeBuffer.rawData(); + // The storage slot may lay across a page boundary which means potentially writing + // two pages instead of just one. In order to update only part of a page, the page + // buffer must be filled with both the previous content along with the new data. + // For example, imagine 2 pages of data: |xxxxxxSSSS|SSSxxxxxxx| the x's are other + // data that must be preserved, and the S's denote the storage slot being written. + // This would take place over two iterations of the loop, each writing out one page + // by read-then-writing-back the x's and writing out the new S's. This is necessary + // because the page buffer must be filled to perform a page write, at least I think + while (size > 0) { + uint16_t pageStart = base & ~(EEPROM_PAGE_SIZE - 1); + uint16_t offset = base % EEPROM_PAGE_SIZE; + uint16_t space = EEPROM_PAGE_SIZE - offset; + uint16_t writeSize = (size < space) ? size : space; + + // read out the page so the |xxxxxxxxxxx part + ByteStream pageBuffer(EEPROM_PAGE_SIZE); + uint8_t *pagePtr = (uint8_t *)pageBuffer.data(); + stptr_p((const uint8_t *)&pageStart, 2); + for (uint16_t i = 0; i < EEPROM_PAGE_SIZE; ++i) { + pagePtr[i] = ldinc_b(); + } + nvmWait(); + + // overlay the actual data, so the SSSSS part of |xxxxxxSSSS + memcpy(pagePtr + offset, ptr, writeSize); + + // write back the page + //nvmCmd(NVM_PBC); + //stptr_p((const uint8_t *)&pageStart, 2); + //stcs(Control_A, 0x0E); + //rep(pageSize - 1); + //stinc_b_noget(buf[0]); + //for (uint8_t i = 1; i < pageSize; ++i) { + // sendByte(buf[i]); + //} + //sts_b(pageStart, pagePtr[0]); + for (uint16_t i = 0; i < EEPROM_PAGE_SIZE; ++i) { + //stinc_b(pagePtr[i]); + sts_b(pageStart + i, pagePtr[i]); + } + //stcs(Control_A, 0x06); + nvmCmd(NVM_ERWP); + nvmWait(); + + //for (uint8_t i = 0; i < EEPROM_PAGE_SIZE; ++i) { + // uint8_t value; + // if (i >= offset && i < offset + writeSize) { + // // if this is within the slot then write out the new data + // value = ptr[i - offset]; + // } else { + // // otherwise just write-back the same value to fill the pagebuffer + // uint16_t addr = pageStart + i; + // stptr_p((const uint8_t *)&addr, 2); + // value = ld_b(); + // } + // sts_b(pageStart + i, value); + //} + + //bool cont = false; + //for (uint8_t i = 0; i < EEPROM_PAGE_SIZE; ++i) { + // // if outside the write area + // if (i < offset || i >= (offset + writeSize)) { + // // if continuously reading just read and continue + // if (cont) { + // ptr[i] = ldinc_b(); + // continue; + // } + // // otherwise start reading here + // uint16_t addr = pageStart + i; + // stptr_p((const uint8_t *)&addr, 2); + // ptr[i] = ldinc_b(); + // cont = true; + // } else { + // // no longer continuously reading + // cont = false; + // } + //} + + + //// first read out the rest of the page + //stptr_p((const uint8_t *)&base, 2); + ////rep(numBytes - 1); + //for (uint16_t i = 0; i < modeBuffer.rawSize(); ++i) { + // ptr[i] = ldinc_b(); + //} + + //nvmCmd(NVM_ERWP); + //nvmWait(); + //nvmCmd(NVM_PBC); + //stptr_w(pageStart + offset); + //stcs(Control_A, 0x0E); + //rep(writeSize - 1); + //stinc_b_noget(ptr[0]); + //for (uint8_t i = 1; i < writeSize; ++i) { + // sendByte(ptr[i]); + //} + //stcs(Control_A, 0x06); + //nvmCmd(NVM_WP); + //nvmWait(); + + // continue to the next page + base += writeSize; + ptr += writeSize; + size -= writeSize; + } + return true; +} + +bool UPDI::writeModeFlash(uint8_t idx, ByteStream &modeBuffer) +{ + enterProgrammingMode(); + // there are 3 modes in the eeprom after the header + // DUO_FLASH_STORAGE_BASE is the end of flash, 0x200 before + uint16_t base = DUO_FLASH_STORAGE_BASE + ((idx - 3) * DUO_MODE_SIZE); + uint16_t size = modeBuffer.rawSize(); + uint8_t *ptr = (uint8_t *)modeBuffer.rawData(); + // The storage slot may lay across a page boundary which means potentially writing + // two pages instead of just one. In order to update only part of a page, the page + // buffer must be filled with both the previous content along with the new data. + // For example, imagine 2 pages of data: |xxxxxxSSSS|SSSxxxxxxx| the x's are other + // data that must be preserved, and the S's denote the storage slot being written. + // This would take place over two iterations of the loop, each writing out one page + // by read-then-writing-back the x's and writing out the new S's. This is necessary + // because the page buffer must be filled to perform a page write, at least I think + while (size > 0) { + uint16_t pageStart = base & ~(FLASH_PAGE_SIZE - 1); + uint16_t offset = base % FLASH_PAGE_SIZE; + uint16_t space = FLASH_PAGE_SIZE - offset; + uint16_t writeSize = (size < space) ? size : space; + + // read out the page + ByteStream pageBuffer(FLASH_PAGE_SIZE); + uint8_t *pagePtr = (uint8_t *)pageBuffer.data(); +//#define NUM_SLICES 8 +//#define PAGE_SLICE (FLASH_PAGE_SIZE / NUM_SLICES) +// for (uint16_t j = 0; j < NUM_SLICES; ++j) { +// uint16_t pos = pageStart = j; +// stptr_p((const uint8_t *)&pos, 2); +// for (uint16_t i = 0; i < PAGE_SLICE; ++i) { +// ptr[(j * PAGE_SLICE) + i] = ldinc_b(); +// } +// // wait idk +// nvmWait(); +// } + + stptr_p((const uint8_t *)&pageStart, 2); + for (uint16_t i = 0; i < FLASH_PAGE_SIZE; ++i) { + pagePtr[i] = ldinc_b(); + } + //if (!readFlashPage(pageStart, pageBuffer)) { + // return false; + //} + // copy in the thing + memcpy(pagePtr + offset, ptr, writeSize); + + // write back the page + //nvmCmd(NVM_PBC); + //stptr_p((const uint8_t *)&pageStart, 2); + //stcs(Control_A, 0x0E); + //rep(pageSize - 1); + //stinc_b_noget(buf[0]); + //for (uint8_t i = 1; i < pageSize; ++i) { + // sendByte(buf[i]); + //} + //sts_b(pageStart, pagePtr[0]); + for (uint16_t i = 0; i < FLASH_PAGE_SIZE; ++i) { + //stinc_b(pagePtr[i]); + sts_b(pageStart + i, pagePtr[i]); + } + //stcs(Control_A, 0x06); + nvmCmd(NVM_ERWP); + nvmWait(); + + //if (!writeFlashPage(pageStart, pageBuffer)) { + // return false; + //} + + //for (uint8_t i = 0; i < FLASH_PAGE_SIZE; ++i) { + // uint8_t value; + // if (i >= offset && i < offset + writeSize) { + // // if this is within the slot then write out the new data + // value = ptr[i - offset]; + // } else { + // // otherwise just write-back the same value to fill the pagebuffer + // uint16_t addr = pageStart + i; + // stptr_p((const uint8_t *)&addr, 2); + // value = ld_b(); + // } + // sts_b(pageStart + i, value); + //} + + //bool cont = false; + //for (uint8_t i = 0; i < FLASH_PAGE_SIZE; ++i) { + // // if outside the write area + // if (i < offset || i >= (offset + writeSize)) { + // // if continuously reading just read and continue + // if (cont) { + // ptr[i] = ldinc_b(); + // continue; + // } + // // otherwise start reading here + // uint16_t addr = pageStart + i; + // stptr_p((const uint8_t *)&addr, 2); + // ptr[i] = ldinc_b(); + // cont = true; + // } else { + // // no longer continuously reading + // cont = false; + // } + //} + + + + //nvmCmd(NVM_ERWP); + //nvmWait(); + //nvmCmd(NVM_PBC); + //stptr_w(pageStart + offset); + //stcs(Control_A, 0x0E); + //rep(writeSize - 1); + //stinc_b_noget(ptr[0]); + //for (uint8_t i = 1; i < writeSize; ++i) { + // sendByte(ptr[i]); + //} + //stcs(Control_A, 0x06); + //nvmCmd(NVM_WP); + //nvmWait(); + + // continue to the next page + base += writeSize; + ptr += writeSize; + size -= writeSize; + } + return true; +} + +bool UPDI::writePage(uint16_t addr, const uint8_t *buf, uint16_t pageSize) +{ + stptr_w(addr); + stcs(Control_A, 0x0E); + rep(FLASH_PAGE_SIZE - 1); + stinc_b_noget(buf[0]); + for (uint16_t i = 1; i < FLASH_PAGE_SIZE; ++i) { + sendByte(buf[i]); + } + stcs(Control_A, 0x06); + nvmCmd(NVM_ERWP); + nvmWait(); + + //nvmCmd(NVM_PBC); + //stptr_p((const uint8_t *)&addr, 2); + ////stcs(Control_A, 0x0E); + ////rep(pageSize - 1); + ////stinc_b_noget(buf[0]); + ////for (uint8_t i = 1; i < pageSize; ++i) { + //// sendByte(buf[i]); + ////} + //for (uint8_t i = 0; i < pageSize; ++i) { + // //stinc_b_noget(buf[i]); + // sts_b(addr + i, buf[i]); + //} + ////stcs(Control_A, 0x06); + //nvmCmd(NVM_ERWP); + //nvmWait(); + return true; +} + +bool UPDI::readPage(uint8_t addr, uint8_t *buf, uint16_t pageSize) +{ + stptr_p((const uint8_t *)&addr, 2); + for (uint16_t i = 0; i < pageSize; ++i) { + buf[i] = ldinc_b(); + } + //stcs(Control_A, 0x0E); + //rep(pageSize - 1); + //buf[0] = ldinc_b(); + //for (uint16_t i = 0; i < pageSize; ++i) { + // stptr_w(addr + i); + // buf[i] = ld_b(); + //} + //stcs(Control_A, 0x06); + return true; +} + +bool UPDI::writeFlashPage(uint16_t addr, const uint8_t *buf) +{ + return writePage(addr, buf, FLASH_PAGE_SIZE); +} + +bool UPDI::readFlashPage(uint8_t addr, uint8_t *buf) +{ + return readPage(addr, buf, FLASH_PAGE_SIZE); +} + +bool UPDI::writeEepromPage(uint16_t addr, const uint8_t *buf) +{ + return writePage(addr, buf, EEPROM_PAGE_SIZE); +} + +bool UPDI::readEepromPage(uint8_t addr, uint8_t *buf) +{ + return readPage(addr, buf, EEPROM_PAGE_SIZE); +} + +#endif + +bool UPDI::writeFirmware(uint32_t position, ByteStream &firmwareBuffer) +{ +#ifdef VORTEX_EMBEDDED + firmwareBuffer.sanity(); + if (!firmwareBuffer.checkCRC()) { + ERROR_LOG("ERROR Header CRC Invalid!"); + reset(); + return false; + } + enterProgrammingMode(); + // DUO_MODE_SIZE is the max duo mode size (the slot size) + uint8_t *ptr = (uint8_t *)firmwareBuffer.data(); + // there are 3 modes in the eeprom after the header + // DUO_FLASH_STORAGE_BASE is the end of flash, 0x200 before + uint16_t base = DUO_FIRMWARE_BASE + position; + uint16_t size = firmwareBuffer.size(); + while (size > 0) { + uint16_t writeSize = (size < FLASH_PAGE_SIZE) ? size : FLASH_PAGE_SIZE; + //for (uint8_t i = 0; i < FLASH_PAGE_SIZE; ++i) { + // uint8_t value = (i < writeSize) ? ptr[i] : 0; + // sts_b(base + i, value); + //} + stptr_w(base); + stcs(Control_A, 0x0E); + rep(FLASH_PAGE_SIZE - 1); + stinc_b_noget(ptr[0]); + for (uint16_t i = 1; i < FLASH_PAGE_SIZE; ++i) { + sendByte((i < writeSize) ? ptr[i] : 0); + //sts_b(base + i, (i < writeSize) ? ptr[i] : 0); + } + stcs(Control_A, 0x06); + nvmCmd(NVM_ERWP); + nvmWait(); + // continue to the next page + base += writeSize; + ptr += writeSize; + size -= writeSize; + } +#endif + return true; +} + +bool UPDI::eraseMemory() +{ + uint8_t status = 0; +#ifdef VORTEX_EMBEDDED + sendDoubleBreak(); + sendEraseKey(); + status = ldcs(ASI_Key_Status); + if (status != 0x8) { + ERROR_LOGF("Erasing mem, bad key status: 0x%02x...", status); + } + reset(); +#endif + return status == 0x8; +} + +bool UPDI::reset() +{ +#ifdef VORTEX_EMBEDDED + resetOn(); + return resetOff(); +#else + return true; +#endif +} + +#ifdef VORTEX_EMBEDDED + +void UPDI::enterProgrammingMode() +{ + uint8_t mode; + while (1) { + sendDoubleBreak(); + stcs(Control_A, 0x6); + mode = cpu_mode<0xEF>(); + if (mode != 0x82 && mode != 0x21 && mode != 0xA2 && mode != 0x08) { + //sendDoubleBreak(); + sendBreak(); + uint8_t status = ldcs(Status_B); + ERROR_LOGF("Bad CPU Mode 0x%02x... error: 0x%02x", mode, status); + //reset(); + continue; + } + if (mode != 0x08) { + sendProgKey(); + uint8_t status = ldcs(ASI_Key_Status); + if (status != 0x10) { + ERROR_LOGF("Bad prog key status: 0x%02x", status); + reset(); + continue; + } + reset(); + } + // break the while (1) + break; + } + mode = cpu_mode(); + while (mode != 0x8) { + mode = cpu_mode(); + } +} + +void UPDI::resetOn() +{ + stcs(ASI_Reset_Request, 0x59); +} + +bool UPDI::resetOff() +{ + stcs(ASI_Reset_Request, 0x0); + uint8_t timeout = 0; + while (cpu_mode<0x0E>() == 0 && timeout < 10) { + Time::delayMicroseconds(100); + timeout++; + } + return timeout < 2; +} + +void UPDI::sendByte(uint8_t byte) +{ + uint8_t parity = __builtin_parity(byte); + GPIO_SET_OUTPUT(UPDI_PIN); + // Send start bit (low) + GPIO_SET_LOW(UPDI_PIN); + Time::delayMicroseconds(BIT_TIME_US); + // Send data bits (LSB first) + for (uint8_t i = 0; i < 8; i++) { + if (byte & 0x01) { + GPIO_SET_HIGH(UPDI_PIN); + } else { + GPIO_SET_LOW(UPDI_PIN); + } + Time::delayMicroseconds(BIT_TIME_US); + byte >>= 1; + } + // Send parity bit (even parity) + if (parity) { + GPIO_SET_HIGH(UPDI_PIN); + } else { + GPIO_SET_LOW(UPDI_PIN); + } + Time::delayMicroseconds(BIT_TIME_US); + // Send stop bit (high) + GPIO_SET_HIGH(UPDI_PIN); + Time::delayMicroseconds(BIT_TIME_US * 2); + GPIO_SET_INPUT(UPDI_PIN); +} + +uint8_t UPDI::receiveByte() +{ + uint8_t byte = 0; + uint32_t counter = 0; +#define TIMEOUT_TT 40000 + while (GPIO_READ(UPDI_PIN) == 1 && counter++ < TIMEOUT_TT); + if (counter >= TIMEOUT_TT) { + ERROR_LOG("Timed out waiting for start bit"); + return 0; + } + Time::delayMicroseconds(BIT_TIME_US / 2); // Half bit time for sampling + // Read data bits (LSB first) + for (uint8_t i = 0; i < 8; i++) { + Time::delayMicroseconds(BIT_TIME_US); + byte >>= 1; + if (GPIO_READ(UPDI_PIN)) { + byte |= 0x80; + } + } + // Read and discard parity bit + Time::delayMicroseconds(BIT_TIME_US); + // Read stop bit + Time::delayMicroseconds(BIT_TIME_US); + if (GPIO_READ(UPDI_PIN) == 0) { + //ERROR_LOG("Stop bit error"); + } + Time::delayMicroseconds(BIT_TIME_US * 2); + return byte; +} + +void UPDI::sendBreak() +{ + GPIO_SET_OUTPUT(UPDI_PIN); + GPIO_SET_LOW(UPDI_PIN); + Time::delayMicroseconds(UPDI_BREAK_DELAY_US); + GPIO_SET_HIGH(UPDI_PIN); + Time::delayMicroseconds(UPDI_BREAK_DELAY_US); +} + +void UPDI::sendDoubleBreak() +{ + GPIO_SET_OUTPUT(UPDI_PIN); + + // First break signal + GPIO_SET_LOW(UPDI_PIN); + Time::delayMicroseconds(UPDI_BREAK_DELAY_US); + GPIO_SET_HIGH(UPDI_PIN); + Time::delayMicroseconds(UPDI_BREAK_PAUSE_US); + + // Second break signal + GPIO_SET_LOW(UPDI_PIN); + Time::delayMicroseconds(UPDI_BREAK_DELAY_US); + GPIO_SET_HIGH(UPDI_PIN); + Time::delayMicroseconds(UPDI_BREAK_DELAY_US); +} + +void UPDI::sendKey(const char *key) +{ + sendByte(0x55); + sendByte(0xE0); + for (int8_t i = 7; i >= 0; --i) { + sendByte(key[i]); + } +} + +uint8_t UPDI::ldcs(cs_reg reg) +{ + sendByte(0x55); // SYNCH + sendByte(0x80 + reg); + return receiveByte(); +} + +void UPDI::stcs(cs_reg r, uint8_t data) +{ + sendByte(0x55); + sendByte(0xC0 + r); + sendByte(data); +} + +void UPDI::stptr_p(const uint8_t *addr_p, uint8_t n) +{ + sendByte(0x55); + sendByte(0x68 + --n); + sendByte(*(addr_p++)); + if (n >= 1) { + sendByte(*(addr_p++)); + } + if (n >= 2) { + sendByte(*addr_p); + } + receiveByte(); +} + +void UPDI::stptr_l(uint32_t address) +{ + sendByte(0x55); + sendByte(0x6A); + sendByte(address & 0xFF); + sendByte((address >> 8) & 0xFF); + sendByte((address >> 16) & 0xFF); + receiveByte(); +} + +void UPDI::stptr_w(uint16_t address) +{ + sendByte(0x55); + sendByte(0x69); + sendByte(address & 0xFF); + sendByte(address >> 8); + receiveByte(); +} + +void UPDI::stptr_inc_16(uint8_t *data, uint16_t len) +{ + sendByte(0x55); + sendByte(0x65); + receiveByte(); + uint16_t n = 2; + while (n < len) + { + sendByte(data[n]); + sendByte(data[n + 1]); + receiveByte(); + n += 2; + } +} + +void UPDI::rep(uint8_t repeats) +{ + sendByte(0x55); + sendByte(0xA0); + sendByte(repeats); +} + +uint8_t UPDI::ldinc_b() +{ + sendByte(0x55); + sendByte(0x24); + return receiveByte(); +} + +void UPDI::sts_b(uint16_t address, uint8_t data) +{ + sendByte(0x55); + sendByte(0x44); + sendByte(address & 0xFF); + sendByte(address >> 8); + receiveByte(); + sendByte(data); + receiveByte(); +} + +uint8_t UPDI::lds_b(uint16_t address) +{ + sendByte(0x55); + sendByte(0x04); + sendByte(address & 0xFF); + sendByte(address >> 8); + return receiveByte(); +} + +uint8_t UPDI::ld_b() +{ + sendByte(0x55); + sendByte(0x20); + return receiveByte(); +} + +void UPDI::stinc_b(uint8_t data) +{ + sendByte(0x55); + sendByte(0x100); + sendByte(data); + receiveByte(); +} + +void UPDI::stinc_b_noget(uint8_t data) +{ + sendByte(0x55); + sendByte(0x64); + sendByte(data); +} + +void UPDI::stinc_w_noget(uint16_t data) +{ + sendByte(0x55); + sendByte(0x65); + sendByte(data & 0xFF); + sendByte((data >> 8) & 0xFF); +} + + +#endif + diff --git a/VortexEngine/src/UPDI/updi.h b/VortexEngine/src/UPDI/updi.h new file mode 100644 index 0000000000..b7bd25f023 --- /dev/null +++ b/VortexEngine/src/UPDI/updi.h @@ -0,0 +1,164 @@ +#ifndef UPDI_H +#define UPDI_H + +#include + +#include "../VortexConfig.h" + + +class ByteStream; + +class UPDI +{ +public: + static bool init(); + static void cleanup(); + + static uint8_t isConnected(); + + // read the duo save header over updi + static bool readHeader(ByteStream &headerBuffer); + static bool readMode(uint8_t idx, ByteStream &modeBuffer); + + static bool writeHeader(ByteStream &headerBuffer); + static bool writeMode(uint8_t idx, ByteStream &modeBuffer); + + static bool writeFirmware(uint32_t offset, ByteStream &firmwareBuffer); + + static bool eraseMemory(); + static bool reset(); + +private: + +#ifdef VORTEX_EMBEDDED + // really old duos like 1.0.0 + static bool readHeaderLegacy1(ByteStream &headerBuffer); + // kinda old duos like 1.2.0 to 1.3.0 + static bool readHeaderLegacy2(ByteStream &headerBuffer); + + static bool writeModeEeprom(uint8_t idx, ByteStream &modeBuffer); + static bool writeModeFlash(uint8_t idx, ByteStream &modeBuffer); + + static bool writePage(uint16_t addr, const uint8_t *buf, uint16_t pageSize = 128); + static bool readPage(uint8_t addr, uint8_t *buf, uint16_t pageSize = 128); + + static bool writeFlashPage(uint16_t addr, const uint8_t *buf); + static bool readFlashPage(uint8_t addr, uint8_t *buf); + + static bool writeEepromPage(uint16_t addr, const uint8_t *buf); + static bool readEepromPage(uint8_t addr, uint8_t *buf); + + // *** Base Addresses *** + enum base + { + NVM_base = 0x1000, /* Base address of the NVM controller */ + Sig_base = 0x1100, /* Base address of the signature */ + Fuse_base = 0x1280, /* Base address of the fuses */ + User_base = 0x1300, /* Base address of the User Row EEPROM */ + EEPROM_base = 0x1400 /* Base address of the main EEPROM */ + }; + + enum cmnd + { + NVM_CMD_NOP, // Does nothing + NVM_CMD_WP, // Write page buffer to memory + NVM_CMD_ER, // Erase page + NVM_CMD_ERWP, // Erase and write page + NVM_CMD_PBC, // Page buffer clear + NVM_CMD_CHER, // Chip erase: erase Flash and EEPROM + NVM_CMD_EEER, // EEPROM Erase + NVM_CMD_WFU // Write fuse + }; + + enum cs_reg : uint8_t + { + Status_A, Status_B, Control_A, Control_B, + Reg_4, Reg_5, Reg_6, ASI_Key_Status, + ASI_Reset_Request, ASI_Control_A, ASI_System_Control_A, ASI_System_Status, + ASI_CRC_Status, Reg_13, Reg_14, Reg_15 + }; + + // *** NVM Registers (offsets from NVN_base are enum default values) *** + enum nvm_reg + { + NVM_CTRLA, + NVM_CTRLB, + NVM_STATUS, + NVM_INTCTRL, + NVM_INTFLAGS, + NVM_Reg_5, + NVM_DATA_lo, + NVM_DATA_hi, + NVM_ADDR_lo, + NVM_ADDR_hi + }; + + // *** NVM Commands (write to CTRLA to execute) *** + enum nvm_cmd + { + NVM_NOP, /* Does nothing */ + NVM_WP, /* Write page buffer to memory */ + NVM_ER, /* Erase page */ + NVM_ERWP, /* Erase and write page */ + NVM_PBC, /* Page buffer clear */ + NVM_CHER, /* Chip erase: erase Flash and EEPROM */ + NVM_EEER, /* EEPROM Erase */ + NVM_WFU /* Write fuse */ + }; + + static void enterProgrammingMode(); + + static void resetOn(); + static bool resetOff(); + + static void sendByte(uint8_t byte); + static uint8_t receiveByte(); + static void sendBreak(); + static void sendDoubleBreak(); + static void sendKey(const char *key); + + static uint8_t ldcs(cs_reg r); + static void stcs(cs_reg r, uint8_t data); + static void stptr_w(uint16_t addr); + static void stptr_p(const uint8_t *addr_p, uint8_t n); + static void stptr_l(uint32_t address); + static void stptr_inc_16(uint8_t *data, uint16_t len); + static void stinc_b(uint8_t data); + static void stinc_b_noget(uint8_t data); + static void stinc_w_noget(uint16_t data); + static void rep(uint8_t repeats); + static uint8_t ldinc_b(); + static uint8_t lds_b(uint16_t address); + static uint8_t ld_b(); + static void sts_b(uint16_t address, uint8_t data); + + static void nvmCmd(nvm_cmd cmd) { UPDI::sts_b(NVM_base + NVM_CTRLA, cmd); } + static void nvmWait() { while (UPDI::lds_b(NVM_base + NVM_STATUS) & 0x03); } + + template + static uint8_t cpu_mode() + { + uint8_t mode = ldcs(ASI_System_Status); + return mode & mask; + } + + static void sendEraseKey() { sendKey("NVMErase"); } + static void sendProgKey() { sendKey("NVMProg "); } + static void sendUserrowKey() { sendKey("NVMUs&te"); } + + enum StorageType + { + // modern duos like 1.4.0+ + MODERN_STORAGE, + // duos like 1.2.0. to 1.3..0 + LEGACY_STORAGE_1, + // old duos like 1.0.0 to 1.2.0 + LEGACY_STORAGE_2, + }; + + // whether connected to a legacy duo + static StorageType m_storageType; +#endif +}; + +#endif // UPDI_H diff --git a/VortexEngine/src/VortexConfig.h b/VortexEngine/src/VortexConfig.h index dc47583a5c..55404c349a 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 "Chromadeck" // 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 15 // 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 16 // Default Tickrate in Ticks Per Second (TPS) // @@ -288,10 +288,14 @@ // // 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_4 255 +#define BRIGHTNESS_OPTION_1 5 +#define BRIGHTNESS_OPTION_2 30 +#define BRIGHTNESS_OPTION_3 60 +#define BRIGHTNESS_OPTION_4 90 +#define BRIGHTNESS_OPTION_5 120 +#define BRIGHTNESS_OPTION_6 160 +#define BRIGHTNESS_OPTION_7 200 +#define BRIGHTNESS_OPTION_8 255 // Saturation Options // @@ -314,7 +318,7 @@ // Serial Baud Rate // // The serial connection baud rate for the editor and anything else serial -#define SERIAL_BAUD_RATE 9600 +#define SERIAL_BAUD_RATE 115200 // =================================================================== // Boolean Configurations (0 or 1) @@ -529,6 +533,12 @@ // the done message from the device when it done the command #define EDITOR_VERB_PUSH_EACH_MODE_DONE "H" +// flash firmware +#define EDITOR_VERB_FLASH_FIRMWARE "I" +// ack for each step of flashing +#define EDITOR_VERB_FLASH_FIRMWARE_ACK "J" +// done flashing firmware +#define EDITOR_VERB_FLASH_FIRMWARE_DONE "K" // =================================================================== // Manually Configured Sizes diff --git a/VortexEngine/src/VortexEngine.cpp b/VortexEngine/src/VortexEngine.cpp index 087931c7d9..8924ddc375 100644 --- a/VortexEngine/src/VortexEngine.cpp +++ b/VortexEngine/src/VortexEngine.cpp @@ -12,9 +12,11 @@ #include "Time/Timings.h" #include "Serial/Serial.h" #include "Modes/Modes.h" +#include "Menus/MainMenu.h" #include "Menus/Menus.h" #include "Modes/Mode.h" #include "Leds/Leds.h" +#include "UPDI/updi.h" #include "Log/Log.h" #ifdef VORTEX_LIB @@ -30,12 +32,12 @@ bool VortexEngine::m_autoCycle = false; bool VortexEngine::init() { // all of the global controllers - if (!SerialComs::init()) { - DEBUG_LOG("Serial failed to initialize"); + if (!Time::init()) { + //DEBUG_LOG("Time failed to initialize"); return false; } - if (!Time::init()) { - DEBUG_LOG("Time failed to initialize"); + if (!SerialComs::init()) { + DEBUG_LOG("Serial failed to initialize"); return false; } if (!Storage::init()) { @@ -82,6 +84,14 @@ bool VortexEngine::init() DEBUG_LOG("Settings failed to initialize"); return false; } + if (!MainMenu::init()) { + DEBUG_LOG("Main menu failed to initialize"); + return false; + } + if (!UPDI::init()) { + DEBUG_LOG("UPDI failed to initialize"); + return false; + } #if COMPRESSION_TEST == 1 compressionTest(); @@ -133,12 +143,12 @@ void VortexEngine::tick() // update the buttons to check for wake Buttons::update(); // several fast clicks will unlock the device - if (Modes::locked() && g_pButton->onConsecutivePresses(DEVICE_LOCK_CLICKS - 1)) { + if (Modes::locked() && g_pButtonM->onConsecutivePresses(DEVICE_LOCK_CLICKS - 1)) { // turn off the lock flag and save it to disk Modes::setLocked(false); } // check for any kind of press to wakeup - if (g_pButton->check() || g_pButton->onRelease() || !Vortex::sleepEnabled()) { + if (g_pButtonM->check() || g_pButtonM->onRelease() || !Vortex::sleepEnabled()) { wakeup(); } return; @@ -172,6 +182,25 @@ void VortexEngine::runMainLogic() // the current tick uint32_t now = Time::getCurtime(); + // check for serial first before main menus run, but as a result if we open + // editor we have to call modes load inside here + if (SerialComs::checkSerial()) { + if (Menus::curMenuID() != MENU_EDITOR_CONNECTION) { + // have to do this because we check for serial before main menu + if (MainMenu::isOpen()) { + MainMenu::select(); + Modes::load(); + } + // directly open the editor connection menu because we are connected to USB serial + Menus::openMenu(MENU_EDITOR_CONNECTION); + } + } + + // if the main menu is open just run it and return + if (MainMenu::run()) { + return; + } + // load modes if necessary if (!Modes::load()) { // don't do anything if modes couldn't load @@ -184,14 +213,14 @@ void VortexEngine::runMainLogic() } // check if we should enter the menu - if (g_pButton->isPressed() && g_pButton->holdDuration() > MENU_TRIGGER_THRESHOLD_TICKS) { + if (g_pButtonM->isPressed() && g_pButtonM->holdDuration() > MENU_TRIGGER_THRESHOLD_TICKS) { DEBUG_LOG("Entering Menu Selection..."); Menus::openMenuSelection(); return; } // toggle auto cycle mode with many clicks at main modes - if ((g_pButton->onRelease() && m_autoCycle) || g_pButton->onConsecutivePresses(AUTO_CYCLE_MODES_CLICKS)) { + if ((g_pButtonM->onRelease() && m_autoCycle) || g_pButtonM->onConsecutivePresses(AUTO_CYCLE_MODES_CLICKS)) { m_autoCycle = !m_autoCycle; Leds::holdAll(m_autoCycle ? RGB_GREEN : RGB_RED); } diff --git a/VortexEngine/src/Wireless/IRConfig.h b/VortexEngine/src/Wireless/IRConfig.h index 2a8e68ddf2..e763b6b17d 100644 --- a/VortexEngine/src/Wireless/IRConfig.h +++ b/VortexEngine/src/Wireless/IRConfig.h @@ -39,7 +39,7 @@ #define IR_DIVIDER_SPACE_MIN IR_HEADER_MARK_MIN #define IR_DIVIDER_SPACE_MAX IR_HEADER_MARK_MAX -#define IR_SEND_PWM_PIN 0 -#define IR_RECEIVER_PIN 2 +#define IR_SEND_PWM_PIN 3 +#define IR_RECEIVER_PIN 4 #endif diff --git a/VortexEngine/src/Wireless/IRReceiver.cpp b/VortexEngine/src/Wireless/IRReceiver.cpp index 47ec50ef4d..0190d52e8a 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; } @@ -138,7 +151,7 @@ void IRReceiver::recvPCIHandler() // check previous time for validity if (!m_prevTime || m_prevTime > now) { m_prevTime = now; - DEBUG_LOG("Bad first time diff, resetting..."); + //DEBUG_LOG("Bad first time diff, resetting..."); resetIRState(); return; } @@ -155,7 +168,7 @@ void IRReceiver::handleIRTiming(uint32_t diff) { // if the diff is too long or too short then it's not useful if ((diff > IR_HEADER_MARK_MAX && m_recvState < READING_DATA_MARK) || diff < IR_TIMING_MIN) { - DEBUG_LOGF("bad delay: %u, resetting...", diff); + //DEBUG_LOGF("bad delay: %u, resetting...", diff); resetIRState(); return; } @@ -164,7 +177,7 @@ void IRReceiver::handleIRTiming(uint32_t diff) if (diff >= IR_HEADER_MARK_MIN && diff <= IR_HEADER_MARK_MAX) { m_recvState = WAITING_HEADER_SPACE; } else { - DEBUG_LOGF("Bad header mark %u, resetting...", diff); + //DEBUG_LOGF("Bad header mark %u, resetting...", diff); resetIRState(); } break; @@ -172,7 +185,7 @@ void IRReceiver::handleIRTiming(uint32_t diff) if (diff >= IR_HEADER_SPACE_MIN && diff <= IR_HEADER_SPACE_MAX) { m_recvState = READING_DATA_MARK; } else { - DEBUG_LOGF("Bad header space %u, resetting...", diff); + //DEBUG_LOGF("Bad header space %u, resetting...", diff); resetIRState(); } break; @@ -186,7 +199,7 @@ void IRReceiver::handleIRTiming(uint32_t diff) m_recvState = READING_DATA_MARK; break; default: // ?? - DEBUG_LOGF("Bad receive state: %u", m_recvState); + //DEBUG_LOGF("Bad receive state: %u", m_recvState); break; } } @@ -197,7 +210,7 @@ void IRReceiver::resetIRState() m_recvState = WAITING_HEADER_MARK; // zero out the receive buffer and reset bit receiver position m_irData.reset(); - DEBUG_LOG("IR State Reset"); + //DEBUG_LOG("IR State Reset"); } #endif diff --git a/VortexEngine/src/Wireless/IRSender.cpp b/VortexEngine/src/Wireless/IRSender.cpp index 1d127a1978..b211680d4a 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 @@ -32,6 +36,9 @@ uint32_t IRSender::m_writeCounter = 0; bool IRSender::init() { +#ifdef VORTEX_EMBEDDED + initPWM(); +#endif return true; } @@ -144,6 +151,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,7 +162,34 @@ 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 } +#ifdef VORTEX_EMBEDDED +const uint32_t pwmFrequency = 39062; // Actual frequency with divider = 8 +const uint8_t pwmResolution = 8; +const uint32_t pwmDutyCycle = 85; + +void IRSender::initPWM() +{ + // Configure the PWM on the specified pin with initial duty cycle of 0 + ledcAttach(IR_SEND_PWM_PIN, pwmFrequency, pwmResolution); +} + +void IRSender::startPWM() +{ + // Start PWM with the specified duty cycle + ledcWrite(IR_SEND_PWM_PIN, pwmDutyCycle); +} + +void IRSender::stopPWM() +{ + // Stop PWM by setting the duty cycle to 0 + ledcWrite(IR_SEND_PWM_PIN, 0); +} +#endif + #endif diff --git a/VortexEngine/src/Wireless/IRSender.h b/VortexEngine/src/Wireless/IRSender.h index 8f65fa7048..2b2048ffad 100644 --- a/VortexEngine/src/Wireless/IRSender.h +++ b/VortexEngine/src/Wireless/IRSender.h @@ -27,6 +27,12 @@ class IRSender static uint32_t percentDone() { return (uint32_t)(((float)m_writeCounter / (float)m_size) * 100.0); } private: +#ifdef VORTEX_EMBEDDED + static void initPWM(); + static void startPWM(); + static void stopPWM(); +#endif + // sender functions static void beginSend(); // send a full 8 bits in a tight loop diff --git a/VortexEngine/src/Wireless/VLConfig.h b/VortexEngine/src/Wireless/VLConfig.h index 2ee8490d67..a2a310c6f3 100644 --- a/VortexEngine/src/Wireless/VLConfig.h +++ b/VortexEngine/src/Wireless/VLConfig.h @@ -42,6 +42,6 @@ #define VL_DIVIDER_SPACE_MAX VL_HEADER_MARK_MAX #define VL_SEND_PWM_PIN 0 -#define VL_RECEIVER_PIN 0 +#define VL_RECEIVER_PIN 1 #endif diff --git a/VortexEngine/src/Wireless/VLReceiver.cpp b/VortexEngine/src/Wireless/VLReceiver.cpp index 8d2f12a9a1..aa6c9cab2d 100644 --- a/VortexEngine/src/Wireless/VLReceiver.cpp +++ b/VortexEngine/src/Wireless/VLReceiver.cpp @@ -16,8 +16,57 @@ uint32_t VLReceiver::m_prevTime = 0; uint8_t VLReceiver::m_pinState = 0; uint32_t VLReceiver::m_previousBytes = 0; +#ifdef VORTEX_EMBEDDED +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/adc.h" +#include "esp_adc_cal.h" +#include "esp_log.h" +#include "esp_timer.h" + +#include "../Serial/Serial.h" + +// ADC and timer configuration +#define ADC_CHANNEL ADC1_CHANNEL_1 // Update this based on the actual ADC channel used +#define ADC_ATTEN ADC_ATTEN_DB_0 +#define ADC_WIDTH ADC_WIDTH_BIT_12 +#define TIMER_INTERVAL_MICRO_SEC 1000 // Check every 10ms, adjust as needed for your application + +// Timer handle as a global variable for control in beginReceiving and endReceiving +esp_timer_handle_t periodic_timer = nullptr; +esp_adc_cal_characteristics_t adc_chars; + +#define MIN_THRESHOLD 200 +#define BASE_OFFSET 100 +#define THRESHOLD_BEGIN (MIN_THRESHOLD + BASE_OFFSET) +// the threshold needs to start high then it will be automatically pulled down +uint32_t threshold = THRESHOLD_BEGIN; +void VLReceiver::adcCheckTimerCallback(void *arg) +{ + static bool wasAboveThreshold = false; + uint32_t raw = adc1_get_raw(ADC_CHANNEL); + uint32_t val = esp_adc_cal_raw_to_voltage(raw, &adc_chars); + + if (val > MIN_THRESHOLD && val < (threshold + BASE_OFFSET)) { + threshold = val + BASE_OFFSET; + } + bool isAboveThreshold = (val > threshold); + if (wasAboveThreshold != isAboveThreshold) { + wasAboveThreshold = isAboveThreshold; + VLReceiver::recvPCIHandler(); + } +} +#endif + bool VLReceiver::init() { +#ifdef VORTEX_EMBEDDED + // Initialize ADC for GPIO1 (or appropriate pin connected to your light sensor) + adc1_config_width(ADC_WIDTH); + adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN); + memset(&adc_chars, 0, sizeof(adc_chars)); + esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN, ADC_WIDTH, 0, &adc_chars); +#endif return m_vlData.init(VL_RECV_BUF_SIZE); } @@ -83,12 +132,36 @@ bool VLReceiver::receiveMode(Mode *pMode) bool VLReceiver::beginReceiving() { +#ifdef VORTEX_EMBEDDED + if (periodic_timer) { + DEBUG_LOG("VL Reception already running."); + return false; // Timer is already running + } + // Initialize timer for periodic ADC checks + const esp_timer_create_args_t periodic_timer_args = { + .callback = &VLReceiver::adcCheckTimerCallback, + .name = "adc_check_timer", + }; + ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); + ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, TIMER_INTERVAL_MICRO_SEC)); +#endif resetVLState(); return true; } bool VLReceiver::endReceiving() { +#ifdef VORTEX_EMBEDDED + if (periodic_timer == nullptr) { + DEBUG_LOG("VL Reception was not running."); + return false; // Timer was not running + } + // Stop and delete the timer + ESP_ERROR_CHECK(esp_timer_stop(periodic_timer)); + ESP_ERROR_CHECK(esp_timer_delete(periodic_timer)); + periodic_timer = nullptr; + DEBUG_LOG("VL Reception stopped."); +#endif resetVLState(); return true; } @@ -138,10 +211,11 @@ void VLReceiver::recvPCIHandler() // check previous time for validity if (!m_prevTime || m_prevTime > now) { m_prevTime = now; - DEBUG_LOG("Bad first time diff, resetting..."); + //DEBUG_LOG("Bad first time diff, resetting..."); resetVLState(); return; } + //DEBUG_LOGF("Received: %u", m_pinState); // calc time difference between previous change and now uint32_t diff = (uint32_t)(now - m_prevTime); // and update the previous changetime for next loop @@ -155,7 +229,7 @@ void VLReceiver::handleVLTiming(uint32_t diff) { // if the diff is too long or too short then it's not useful if ((diff > VL_HEADER_MARK_MAX && m_recvState < READING_DATA_MARK) || diff < VL_TIMING_MIN) { - DEBUG_LOGF("bad delay: %u, resetting...", diff); + //DEBUG_LOGF("bad delay: %u, resetting...", diff); resetVLState(); return; } @@ -164,7 +238,7 @@ void VLReceiver::handleVLTiming(uint32_t diff) if (diff >= VL_HEADER_SPACE_MIN && diff <= VL_HEADER_MARK_MAX) { m_recvState = WAITING_HEADER_SPACE; } else { - DEBUG_LOGF("Bad header mark %u, resetting...", diff); + //DEBUG_LOGF("Bad header mark %u, resetting...", diff); resetVLState(); } break; @@ -172,7 +246,7 @@ void VLReceiver::handleVLTiming(uint32_t diff) if (diff >= VL_HEADER_SPACE_MIN && diff <= VL_HEADER_MARK_MAX) { m_recvState = READING_DATA_MARK; } else { - DEBUG_LOGF("Bad header space %u, resetting...", diff); + //DEBUG_LOGF("Bad header space %u, resetting...", diff); resetVLState(); } break; @@ -186,7 +260,7 @@ void VLReceiver::handleVLTiming(uint32_t diff) m_recvState = READING_DATA_MARK; break; default: // ?? - DEBUG_LOGF("Bad receive state: %u", m_recvState); + //DEBUG_LOGF("Bad receive state: %u", m_recvState); break; } } @@ -197,7 +271,7 @@ void VLReceiver::resetVLState() m_recvState = WAITING_HEADER_MARK; // zero out the receive buffer and reset bit receiver position m_vlData.reset(); - DEBUG_LOG("VL State Reset"); + //DEBUG_LOG("VL State Reset"); } #endif diff --git a/VortexEngine/src/Wireless/VLReceiver.h b/VortexEngine/src/Wireless/VLReceiver.h index be4b0a68b5..1d7d35628b 100644 --- a/VortexEngine/src/Wireless/VLReceiver.h +++ b/VortexEngine/src/Wireless/VLReceiver.h @@ -9,6 +9,10 @@ #if VL_ENABLE_RECEIVER == 1 +#ifdef VORTEX_EMBEDDED +#include +#endif + class ByteStream; class Mode; @@ -73,6 +77,10 @@ class VLReceiver // used to compare if received data has changed since last checking static uint32_t m_previousBytes; +#ifdef VORTEX_EMBEDDED + static void adcCheckTimerCallback(void *arg); +#endif + #ifdef VORTEX_LIB friend class Vortex; #endif diff --git a/VortexEngine/src/Wireless/VLSender.cpp b/VortexEngine/src/Wireless/VLSender.cpp index 8b39b9e563..951f8b6df2 100644 --- a/VortexEngine/src/Wireless/VLSender.cpp +++ b/VortexEngine/src/Wireless/VLSender.cpp @@ -174,8 +174,7 @@ void VLSender::startPWM() uint8_t oldBrightness = Leds::getBrightness(); // ensure max brightness Leds::setBrightness(255); - Leds::clearAll(); - Leds::setIndex(LED_0, RGB_WHITE); + Leds::setAll(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..5d77bf9f79 100644 Binary files a/VortexEngine/tests/tests_general.tar.gz and b/VortexEngine/tests/tests_general.tar.gz differ