From 700b103180aea802df74810b3fdb7fdec2c04388 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 18 Feb 2026 21:55:57 -0800 Subject: [PATCH 01/13] various backported changes from various devices --- VortexEngine/src/Modes/DefaultModes.cpp | 4 ++++ VortexEngine/src/Modes/Mode.cpp | 19 +++++++++++++++++++ VortexEngine/src/Modes/Mode.h | 6 ++++++ VortexEngine/src/Serial/BitStream.h | 1 + VortexEngine/src/Time/Timings.h | 4 ++++ VortexEngine/src/VortexConfig.h | 21 +++++++++++++++++++++ VortexEngine/src/VortexEngine.cpp | 8 ++++---- 7 files changed, 59 insertions(+), 4 deletions(-) diff --git a/VortexEngine/src/Modes/DefaultModes.cpp b/VortexEngine/src/Modes/DefaultModes.cpp index a0f4be8b7c..ef69dbaf73 100644 --- a/VortexEngine/src/Modes/DefaultModes.cpp +++ b/VortexEngine/src/Modes/DefaultModes.cpp @@ -127,3 +127,7 @@ const default_mode_entry default_modes[MAX_MODES] = { // exposed size of the default modes array const uint8_t num_default_modes = (sizeof(default_modes) / sizeof(default_modes[0])); + +// compile time assert for max modes count check +static_assert(MAX_MODES == (sizeof(default_modes) / sizeof(default_modes[0])), + "Incorrect number of default modes! Ensure default_modes array has " EXPAND_AND_QUOTE(MAX_MODES) " enties"); diff --git a/VortexEngine/src/Modes/Mode.cpp b/VortexEngine/src/Modes/Mode.cpp index 3d129d18b8..8770ccec11 100644 --- a/VortexEngine/src/Modes/Mode.cpp +++ b/VortexEngine/src/Modes/Mode.cpp @@ -593,6 +593,25 @@ bool Mode::setPatternMap(LedMap map, PatternID pat, const PatternArgs *args, con return true; } +void Mode::copyPatternFrom(const Mode *other, LedPos to, LedPos from) +{ + if (to >= LED_COUNT || from >= LED_COUNT) { + return; + } + delete m_singlePats[to]; + m_singlePats[to] = PatternBuilder::dupe(other->m_singlePats[from]); +} + +void Mode::swapPatterns(LedPos a, LedPos b) +{ + if (a >= LED_COUNT || b >= LED_COUNT) { + return; + } + Pattern *temp = m_singlePats[a]; + m_singlePats[a] = m_singlePats[b]; + m_singlePats[b] = temp; +} + // set colorset at a specific position bool Mode::setColorset(const Colorset &set, LedPos pos) { diff --git a/VortexEngine/src/Modes/Mode.h b/VortexEngine/src/Modes/Mode.h index e364483863..18669b6343 100644 --- a/VortexEngine/src/Modes/Mode.h +++ b/VortexEngine/src/Modes/Mode.h @@ -95,6 +95,12 @@ class Mode bool setPattern(PatternID pat, LedPos pos = LED_ANY, const PatternArgs *args = nullptr, const Colorset *set = nullptr); bool setPatternMap(LedMap pos, PatternID pat, const PatternArgs *args = nullptr, const Colorset *set = nullptr); + // copy a pattern from another mode ledpos into this mode + void copyPatternFrom(const Mode *other, LedPos to, LedPos from); + + // swap two patterns + void swapPatterns(LedPos a, LedPos b); + // set colorset at a specific position bool setColorset(const Colorset &set, LedPos pos = LED_ANY); // set colorset at each position in a map diff --git a/VortexEngine/src/Serial/BitStream.h b/VortexEngine/src/Serial/BitStream.h index 4d17de016f..79a3c1c233 100644 --- a/VortexEngine/src/Serial/BitStream.h +++ b/VortexEngine/src/Serial/BitStream.h @@ -34,6 +34,7 @@ class BitStream bool allocated() const { return m_allocated; } uint16_t size() const { return m_buf_size; } const uint8_t *data() const { return m_buf; } + uint8_t peekData(uint32_t index) const { return m_buf[index]; } const uint32_t *dwData() const { return (uint32_t *)m_buf; } uint16_t dwordpos() const { return m_bit_pos / 32; } uint16_t bytepos() const { return m_bit_pos / 8; } diff --git a/VortexEngine/src/Time/Timings.h b/VortexEngine/src/Time/Timings.h index 647ef2ca78..ae27636d50 100644 --- a/VortexEngine/src/Time/Timings.h +++ b/VortexEngine/src/Time/Timings.h @@ -13,6 +13,10 @@ #define SHORT_CLICK_THRESHOLD_TICKS MS_TO_TICKS(CLICK_THRESHOLD) #define CONSECUTIVE_WINDOW_TICKS MS_TO_TICKS(CONSECUTIVE_WINDOW) #define AUTO_RANDOM_DELAY_TICKS MS_TO_TICKS(AUTO_RANDOM_DELAY) +#define SLEEP_ENTER_THRESHOLD_TICKS MS_TO_TICKS(SLEEP_TRIGGER_TIME) +#define SLEEP_WINDOW_THRESHOLD_TICKS MS_TO_TICKS(SLEEP_WINDOW_TIME) +#define FORCE_SLEEP_THRESHOLD_TICKS MS_TO_TICKS(FORCE_SLEEP_TIME) +#define ONE_CLICK_THRESHOLD_TICKS MS_TO_TICKS(ONE_CLICK_MODE_TRHESHOLD) #define DELETE_THRESHOLD_TICKS MS_TO_TICKS(COL_DELETE_THRESHOLD) #define DELETE_CYCLE_TICKS MS_TO_TICKS(COL_DELETE_CYCLE) #define FACTORY_RESET_THRESHOLD_TICKS MS_TO_TICKS(RESET_HOLD_TIME) diff --git a/VortexEngine/src/VortexConfig.h b/VortexEngine/src/VortexConfig.h index e0af0570e4..730a50e9d5 100644 --- a/VortexEngine/src/VortexConfig.h +++ b/VortexEngine/src/VortexConfig.h @@ -68,6 +68,27 @@ // it will be registered as a 'long click' #define CLICK_THRESHOLD 250 +// Sleep Enter Threshold (in milliseconds) +// +// How long the button has to be held at the main mode area to enter +// sleep mode +#define SLEEP_TRIGGER_TIME 500 + +// Sleep Window Time (in milliseconds) +// +// How long the user has to release the button to enter sleep +#define SLEEP_WINDOW_TIME 750 + +// Force Sleep Time (in milliseconds) +// +// How long the user has to hold the button anywhere to force sleep +#define FORCE_SLEEP_TIME 6000 + +// One-click mode threshold +// +// How long the user has to hold the button to trigger one-click mode +#define ONE_CLICK_MODE_TRHESHOLD 500 + // Device Lock Clicks // // How many rapid clicks the user must perform to lock/unlock the device. diff --git a/VortexEngine/src/VortexEngine.cpp b/VortexEngine/src/VortexEngine.cpp index 3addef3007..783e552d47 100644 --- a/VortexEngine/src/VortexEngine.cpp +++ b/VortexEngine/src/VortexEngine.cpp @@ -30,12 +30,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()) { + // time must init first but can't log failures till serialcomms inits 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()) { From 099d9a46274e3d3f2f73e034172645d04570c8bc Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 18 Feb 2026 21:59:45 -0800 Subject: [PATCH 02/13] fixed default modes --- VortexEngine/src/Modes/DefaultModes.cpp | 5 +++-- VortexEngine/src/Modes/DefaultModes.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/VortexEngine/src/Modes/DefaultModes.cpp b/VortexEngine/src/Modes/DefaultModes.cpp index ef69dbaf73..e06708f769 100644 --- a/VortexEngine/src/Modes/DefaultModes.cpp +++ b/VortexEngine/src/Modes/DefaultModes.cpp @@ -4,7 +4,7 @@ // Here is the array of 'default modes' that are assigned to // the gloveset upon factory reset -const default_mode_entry default_modes[MAX_MODES] = { +const default_mode_entry default_modes[] = { { PATTERN_DOPS, 5, { RGB_RED, @@ -130,4 +130,5 @@ const uint8_t num_default_modes = (sizeof(default_modes) / sizeof(default_modes[ // compile time assert for max modes count check static_assert(MAX_MODES == (sizeof(default_modes) / sizeof(default_modes[0])), - "Incorrect number of default modes! Ensure default_modes array has " EXPAND_AND_QUOTE(MAX_MODES) " enties"); + "Incorrect number of default modes! Ensure default_modes array has " + EXPAND_AND_QUOTE(MAX_MODES) " enties"); diff --git a/VortexEngine/src/Modes/DefaultModes.h b/VortexEngine/src/Modes/DefaultModes.h index 888e4fb7bf..c2338ee01f 100644 --- a/VortexEngine/src/Modes/DefaultModes.h +++ b/VortexEngine/src/Modes/DefaultModes.h @@ -13,7 +13,7 @@ struct default_mode_entry }; // exposed global array of default modes -extern const default_mode_entry default_modes[MAX_MODES]; +extern const default_mode_entry default_modes[]; // exposed size of the default modes array extern const uint8_t num_default_modes; From b629a9271c0603ef6f8ad79934600ee76790055d Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 18 Feb 2026 22:03:06 -0800 Subject: [PATCH 03/13] pinned emsdk version --- .github/workflows/core_build.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/core_build.yml b/.github/workflows/core_build.yml index b0dc41d3b3..ee113c8eb7 100644 --- a/.github/workflows/core_build.yml +++ b/.github/workflows/core_build.yml @@ -7,6 +7,9 @@ on: branches: [ "master" ] workflow_dispatch: # manual trigger +env: + EMSDK_VERSION: "3.1.74" + jobs: setup: runs-on: ubuntu-latest @@ -79,11 +82,12 @@ jobs: 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 + git fetch --tags + git checkout "${EMSDK_VERSION}" + ./emsdk install "${EMSDK_VERSION}" + ./emsdk activate "${EMSDK_VERSION}" working-directory: VortexEngine/VortexLib - name: Build Webassembly run: | From c4236995bd4844f6e13cb74999a4aacf490f49a9 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 19 Feb 2026 00:49:55 -0800 Subject: [PATCH 04/13] minor fixes and updates --- .../src/Menus/MenuList/EditorConnection.cpp | 184 +++++++++--- .../src/Menus/MenuList/EditorConnection.h | 16 +- .../src/Menus/MenuList/ModeSharing.cpp | 25 +- VortexEngine/src/Wireless/IRSender.cpp | 6 +- VortexEngine/src/Wireless/IRSender.h | 4 +- VortexEngine/src/Wireless/VLConfig.h | 48 +++- VortexEngine/src/Wireless/VLReceiver.cpp | 272 ++++++++++++++---- VortexEngine/src/Wireless/VLReceiver.h | 40 ++- VortexEngine/src/Wireless/VLSender.cpp | 170 +++++------ VortexEngine/src/Wireless/VLSender.h | 35 +-- 10 files changed, 538 insertions(+), 262 deletions(-) diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp index c0b02b8bcf..24801bc3e5 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp @@ -5,7 +5,9 @@ #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" @@ -17,6 +19,7 @@ EditorConnection::EditorConnection(const RGBColor &col, bool advanced) : Menu(col, advanced), m_state(STATE_DISCONNECTED), + m_timeOutStartTime(0), m_allowReset(true), m_previousModeIndex(0), m_numModesToReceive(0), @@ -78,7 +81,7 @@ void EditorConnection::onLongClick() void EditorConnection::leaveMenu(bool doSave) { - SerialComs::write(EDITOR_VERB_GOODBYE); + writeData(EDITOR_VERB_GOODBYE); Menu::leaveMenu(true); } @@ -109,6 +112,7 @@ const EditorConnection::CommandState EditorConnection::commands[] = { { EDITOR_VERB_PULL_EACH_MODE, STATE_PULL_EACH_MODE }, { EDITOR_VERB_PUSH_EACH_MODE, STATE_PUSH_EACH_MODE }, { EDITOR_VERB_TRANSMIT_VL, STATE_TRANSMIT_MODE_VL }, + { EDITOR_VERB_LISTEN_VL, STATE_LISTEN_MODE_VL }, { EDITOR_VERB_SET_GLOBAL_BRIGHTNESS, STATE_SET_GLOBAL_BRIGHTNESS }, { EDITOR_VERB_GET_GLOBAL_BRIGHTNESS, STATE_GET_GLOBAL_BRIGHTNESS }, }; @@ -146,11 +150,8 @@ void EditorConnection::handleState() case STATE_DISCONNECTED: default: // not connected yet so check for connections - if (!SerialComs::isConnected()) { - if (!SerialComs::checkSerial()) { - // no connection found just continue waiting - break; - } + if (!detectConnection()) { + break; } // a connection was found, say hello m_state = STATE_GREETING; @@ -160,7 +161,7 @@ void EditorConnection::handleState() // Send Greeting case STATE_GREETING: // send the hello greeting with our version number and build time - SerialComs::write(EDITOR_VERB_GREETING); + writeData(EDITOR_VERB_GREETING); m_state = STATE_IDLE; break; @@ -172,7 +173,7 @@ void EditorConnection::handleState() // parse the receive buffer for any commands from the editor handleCommand(); // watch for disconnects - if (!SerialComs::isConnected()) { + if (!isConnected()) { Leds::holdAll(RGB_RED); leaveMenu(true); } @@ -195,7 +196,7 @@ void EditorConnection::handleState() break; case STATE_PULL_MODES_DONE: // send our acknowledgement that the modes were sent - SerialComs::write(EDITOR_VERB_PULL_MODES_ACK); + writeData(EDITOR_VERB_PULL_MODES_ACK); // go idle m_state = STATE_IDLE; break; @@ -204,7 +205,7 @@ void EditorConnection::handleState() // Receive Modes from PC case STATE_PUSH_MODES: // now say we are ready - SerialComs::write(EDITOR_VERB_READY); + writeData(EDITOR_VERB_READY); // move to receiving m_state = STATE_PUSH_MODES_RECEIVE; break; @@ -218,7 +219,7 @@ void EditorConnection::handleState() m_state = STATE_PUSH_MODES_DONE; break; case STATE_PUSH_MODES_DONE: - SerialComs::write(EDITOR_VERB_PUSH_MODES_ACK); + writeData(EDITOR_VERB_PUSH_MODES_ACK); m_state = STATE_IDLE; break; @@ -226,7 +227,7 @@ void EditorConnection::handleState() // Demo Mode from PC case STATE_DEMO_MODE: // now say we are ready - SerialComs::write(EDITOR_VERB_READY); + writeData(EDITOR_VERB_READY); // move to receiving m_state = STATE_DEMO_MODE_RECEIVE; break; @@ -241,7 +242,7 @@ void EditorConnection::handleState() break; case STATE_DEMO_MODE_DONE: // say we are done - SerialComs::write(EDITOR_VERB_DEMO_MODE_ACK); + writeData(EDITOR_VERB_DEMO_MODE_ACK); m_state = STATE_IDLE; break; @@ -249,7 +250,7 @@ void EditorConnection::handleState() // Reset Demo to Nothing case STATE_CLEAR_DEMO: clearDemo(); - SerialComs::write(EDITOR_VERB_CLEAR_DEMO_ACK); + writeData(EDITOR_VERB_CLEAR_DEMO_ACK); m_state = STATE_IDLE; break; @@ -258,25 +259,35 @@ void EditorConnection::handleState() case STATE_TRANSMIT_MODE_VL: #if VL_ENABLE_SENDER == 1 // immediately load the mode and send it now - VLSender::loadMode(&m_previewMode); - VLSender::send(); + VLSender::send(&m_previewMode); #endif - m_state = STATE_TRANSMIT_MODE_VL_TRANSMIT; + m_state = STATE_TRANSMIT_MODE_VL_DONE; break; - case STATE_TRANSMIT_MODE_VL_TRANSMIT: -#if VL_ENABLE_SENDER == 1 - // if still sending and the send command indicated more data - if (VLSender::isSending() && VLSender::send()) { - // then continue sending + case STATE_TRANSMIT_MODE_VL_DONE: + // done transmitting + writeData(EDITOR_VERB_TRANSMIT_VL_ACK); + m_state = STATE_IDLE; + break; + + // ------------------------------- + // Receive Mode from Duo + case STATE_LISTEN_MODE_VL: +#if VL_ENABLE_RECEIVER == 1 + // immediately load the mode and send it now + VLReceiver::beginReceiving(); +#endif + m_state = STATE_LISTEN_MODE_VL_LISTEN; + break; + case STATE_LISTEN_MODE_VL_LISTEN: + // immediately load the mode and send it now + showReceiveModeVL(); + if (receiveModeVL() == RV_WAIT) { break; } -#endif - // othewrise, done, switch to the transmit done state - m_state = STATE_TRANSMIT_MODE_VL_DONE; + m_state = STATE_LISTEN_MODE_VL_DONE; break; - case STATE_TRANSMIT_MODE_VL_DONE: + case STATE_LISTEN_MODE_VL_DONE: // done transmitting - SerialComs::write(EDITOR_VERB_TRANSMIT_VL_ACK); m_state = STATE_IDLE; break; @@ -295,7 +306,10 @@ void EditorConnection::handleState() if (Modes::numModes() == 0) { m_state = STATE_PULL_EACH_MODE_DONE; } else { + // backup the old mode index so we can return to it m_previousModeIndex = Modes::curModeIndex(); + // switch to mode 0 to ensure we start at the beginning + Modes::setCurMode(0); m_state = STATE_PULL_EACH_MODE_SEND; } break; @@ -323,7 +337,7 @@ void EditorConnection::handleState() break; case STATE_PULL_EACH_MODE_DONE: // send our acknowledgement that the modes were sent - SerialComs::write(EDITOR_VERB_PULL_EACH_MODE_DONE); + writeData(EDITOR_VERB_PULL_EACH_MODE_DONE); // switch back to the previous mode Modes::setCurMode(m_previousModeIndex); // go idle @@ -335,7 +349,7 @@ void EditorConnection::handleState() case STATE_PUSH_EACH_MODE: // editor requested to push modes, find out how many // ack the command and wait for the amount of modes - SerialComs::write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + writeData(EDITOR_VERB_PUSH_EACH_MODE_ACK); m_state = STATE_PUSH_EACH_MODE_COUNT; break; case STATE_PUSH_EACH_MODE_COUNT: @@ -346,7 +360,7 @@ void EditorConnection::handleState() // clear modes and start receiving Modes::clearModes(); // write out an ack - SerialComs::write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + writeData(EDITOR_VERB_PUSH_EACH_MODE_ACK); // ready to receive a mode m_state = STATE_PUSH_EACH_MODE_RECEIVE; break; @@ -356,7 +370,7 @@ void EditorConnection::handleState() // just wait break; } - SerialComs::write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + writeData(EDITOR_VERB_PUSH_EACH_MODE_ACK); if (m_numModesToReceive > 0) { m_numModesToReceive--; } @@ -376,7 +390,7 @@ void EditorConnection::handleState() // ------------------------------- // Set Global Brightness case STATE_SET_GLOBAL_BRIGHTNESS: - SerialComs::write(EDITOR_VERB_READY); + writeData(EDITOR_VERB_READY); m_state = STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE; break; case STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE: @@ -417,22 +431,25 @@ void EditorConnection::showEditor() void EditorConnection::receiveData() { - // read more data into the receive buffer - SerialComs::read(m_receiveBuffer); + if (m_receiveBuffer.size() >= 512) { + return; + } + // Otherwise, read more from Serial + readData(m_receiveBuffer); } void EditorConnection::sendModes() { ByteStream modesBuffer; Modes::saveToBuffer(modesBuffer); - SerialComs::write(modesBuffer); + writeData(modesBuffer); } void EditorConnection::sendModeCount() { ByteStream buffer; buffer.serialize8(Modes::numModes()); - SerialComs::write(buffer); + writeData(buffer); } void EditorConnection::sendCurMode() @@ -447,7 +464,7 @@ void EditorConnection::sendCurMode() // ?? return; } - SerialComs::write(modeBuffer); + writeData(modeBuffer); } void EditorConnection::sendCurModeVL() @@ -457,13 +474,20 @@ void EditorConnection::sendCurModeVL() #endif } +void EditorConnection::listenModeVL() +{ +#if VL_ENABLE_RECEIVER == 1 + m_state = STATE_LISTEN_MODE_VL; +#endif +} + ReturnCode EditorConnection::sendBrightness() { ByteStream brightnessBuf; if (!brightnessBuf.serialize8(Leds::getBrightness())) { return RV_FAIL; } - SerialComs::write(brightnessBuf); + writeData(brightnessBuf); return RV_OK; } @@ -570,7 +594,6 @@ ReturnCode EditorConnection::receiveDemoMode() ReturnCode EditorConnection::receiveMessage(const char *message) { size_t len = strlen(message); - uint8_t byte = 0; // wait for the editor to ack the idle if (m_receiveBuffer.size() < len) { return RV_WAIT; @@ -578,10 +601,8 @@ ReturnCode EditorConnection::receiveMessage(const char *message) if (memcmp(m_receiveBuffer.data(), message, len) != 0) { return RV_FAIL; } - for (size_t i = 0; i < len; ++i) { - if (!m_receiveBuffer.consume8(&byte)) { - return RV_FAIL; - } + if (!m_receiveBuffer.consume((uint32_t)len)) { + return RV_FAIL; } // we have now received at least one command, do not allow resetting m_allowReset = false; @@ -612,3 +633,80 @@ ReturnCode EditorConnection::receiveBrightness() } return RV_OK; } + +ReturnCode EditorConnection::receiveModeVL() +{ +#if VL_ENABLE_RECEIVER == 1 + // 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 RV_WAIT; + } + // check if the VLReceiver has a full packet available + if (!VLReceiver::dataReady()) { + // nothing available yet + return RV_WAIT; + } + 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 RV_FAIL; + } + DEBUG_LOGF("Success receiving mode: %u", m_previewMode.getPatternID()); + if (!Modes::updateCurMode(&m_previewMode)) { + return RV_FAIL; + } +#endif + ByteStream modeBuffer; + if (!m_previewMode.saveToBuffer(modeBuffer)) { + return RV_FAIL; + } + writeData(modeBuffer); + return RV_OK; +} + +void EditorConnection::showReceiveModeVL() +{ +#if VL_ENABLE_RECEIVER == 1 + 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)(((uint32_t)VLReceiver::percentReceived() * LED_COUNT) / 100), RGB_GREEN6); + } else { + Leds::setAll(RGB_WHITE0); + } +#endif +} + +bool EditorConnection::detectConnection() +{ + if (SerialComs::isConnected() || SerialComs::checkSerial()) { + return true; + } + return false; +} + +bool EditorConnection::isConnected() +{ + return SerialComs::isConnected(); +} + +void EditorConnection::readData(ByteStream &buffer) +{ + readData(buffer); +} + +void EditorConnection::writeData(ByteStream &buffer) +{ + writeData(buffer); +} + +void EditorConnection::writeData(const char *message) +{ + writeData(message); +} diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.h b/VortexEngine/src/Menus/MenuList/EditorConnection.h index b4f4f04103..df4b539a82 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.h +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.h @@ -40,6 +40,7 @@ class EditorConnection : public Menu void sendModeCount(); void sendCurMode(); void sendCurModeVL(); + void listenModeVL(); ReturnCode sendBrightness(); ReturnCode receiveBuffer(ByteStream &buffer); ReturnCode receiveModes(); @@ -48,6 +49,13 @@ class EditorConnection : public Menu ReturnCode receiveDemoMode(); ReturnCode receiveMessage(const char *message); ReturnCode receiveBrightness(); + ReturnCode receiveModeVL(); + void showReceiveModeVL(); + bool detectConnection(); + void readData(ByteStream &buffer); + void writeData(ByteStream &buffer); + void writeData(const char *message); + bool isConnected(); enum EditorConnectionState { // the editor is not connected @@ -79,9 +87,13 @@ class EditorConnection : public Menu // transmit the mode over visible light STATE_TRANSMIT_MODE_VL, - STATE_TRANSMIT_MODE_VL_TRANSMIT, STATE_TRANSMIT_MODE_VL_DONE, + // receive a mode over VL + STATE_LISTEN_MODE_VL, + STATE_LISTEN_MODE_VL_LISTEN, + STATE_LISTEN_MODE_VL_DONE, + // editor pulls the modes from device (safer version) STATE_PULL_EACH_MODE, STATE_PULL_EACH_MODE_COUNT, @@ -115,6 +127,8 @@ class EditorConnection : public Menu EditorConnectionState m_state; // the data that is received ByteStream m_receiveBuffer; + // receiver timeout + uint32_t m_timeOutStartTime; // 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 diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp index e3391c1df2..833990dbb6 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp @@ -92,19 +92,8 @@ void ModeSharing::onLongClick() 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 - beginReceivingIR(); - } + VLSender::send(Modes::curMode()); + beginReceivingIR(); } void ModeSharing::beginSendingIR() @@ -127,13 +116,9 @@ void ModeSharing::beginSendingIR() 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(); - } + VLSender::send(Modes::curMode()); + // when send has completed, stores time that last action was completed to calculate interval between sends + beginReceivingIR(); } void ModeSharing::continueSendingIR() diff --git a/VortexEngine/src/Wireless/IRSender.cpp b/VortexEngine/src/Wireless/IRSender.cpp index 1d127a1978..757b8250a9 100644 --- a/VortexEngine/src/Wireless/IRSender.cpp +++ b/VortexEngine/src/Wireless/IRSender.cpp @@ -25,8 +25,6 @@ uint32_t IRSender::m_size = 0; uint8_t IRSender::m_numBlocks = 0; // the amount in the final block uint8_t IRSender::m_remainder = 0; -// configuration options for the sender -uint32_t IRSender::m_blockSize = 0; // write total uint32_t IRSender::m_writeCounter = 0; @@ -109,8 +107,8 @@ bool IRSender::send() void IRSender::beginSend() { m_isSending = true; - DEBUG_LOGF("[%zu] Beginning send size %u (blocks: %u remainder: %u blocksize: %u)", - Time::microseconds(), m_size, m_numBlocks, m_remainder, m_blockSize); + DEBUG_LOGF("[%zu] Beginning send size %u (blocks: %u remainder: %u)", + Time::microseconds(), m_size, m_numBlocks, m_remainder); // wakeup the other receiver with a very quick mark/space sendMark(50); sendSpace(100); diff --git a/VortexEngine/src/Wireless/IRSender.h b/VortexEngine/src/Wireless/IRSender.h index 8f65fa7048..f04f1f3941 100644 --- a/VortexEngine/src/Wireless/IRSender.h +++ b/VortexEngine/src/Wireless/IRSender.h @@ -23,6 +23,7 @@ class IRSender static bool send(); static bool isSending() { return m_isSending; } + static void stopSending() { m_isSending = false; } static uint32_t percentDone() { return (uint32_t)(((float)m_writeCounter / (float)m_size) * 100.0); } @@ -49,9 +50,6 @@ class IRSender // the amount in the final block static uint8_t m_remainder; - // configuration options for the sender - static uint32_t m_blockSize; - // write total static uint32_t m_writeCounter; }; diff --git a/VortexEngine/src/Wireless/VLConfig.h b/VortexEngine/src/Wireless/VLConfig.h index 2ee8490d67..cfc1a323f8 100644 --- a/VortexEngine/src/Wireless/VLConfig.h +++ b/VortexEngine/src/Wireless/VLConfig.h @@ -8,7 +8,7 @@ // Whether to enable the Visible Light system as a whole // #define VL_ENABLE_SENDER 1 -#define VL_ENABLE_RECEIVER 1 +#define VL_ENABLE_RECEIVER 0 // the size of IR blocks in bits #define VL_DEFAULT_BLOCK_SIZE 256 @@ -25,21 +25,45 @@ #define VL_THRES_UP (1 + VL_THRESHOLD) #define VL_THRES_DOWN (1 - VL_THRESHOLD) -#define VL_TIMING (uint32_t)3230 -#define VL_TIMING_MIN ((uint32_t)(VL_TIMING * VL_THRES_DOWN)) +// These are the modern constants for new sender/receiver in modesharing +// new one is faster, more reliable, and better at error detection +#define VL_TIMING (uint16_t)(2230) +// NOTE! Only the chromadeck uses this 'fast' timing to send, because it's cpu +// is running so much faster it can handle it in the sender code. The Duo +// receiver has no issue with the faster speed but the Duo sender cannot do it +#define VL_TIMING_FAST (uint16_t)(1230) -#define VL_HEADER_MARK (uint32_t)(VL_TIMING * 16) -#define VL_HEADER_SPACE (uint32_t)(VL_TIMING * 8) +// despite the faster VL_TIMING the header mark and space must remain the same +#define VL_HEADER_MARK (uint16_t)(VL_TIMING * 16) +#define VL_HEADER_SPACE (uint16_t)(VL_TIMING * 8) -#define VL_HEADER_MARK_MIN ((uint32_t)(VL_HEADER_MARK * VL_THRES_DOWN)) -#define VL_HEADER_SPACE_MIN ((uint32_t)(VL_HEADER_SPACE * VL_THRES_DOWN)) +#define VL_HEADER_MARK_MIN ((uint16_t)(VL_HEADER_MARK * VL_THRES_DOWN)) +#define VL_HEADER_SPACE_MIN ((uint16_t)(VL_HEADER_SPACE * VL_THRES_DOWN)) -#define VL_HEADER_MARK_MAX ((uint32_t)(VL_HEADER_MARK * VL_THRES_UP)) -#define VL_HEADER_SPACE_MAX ((uint32_t)(VL_HEADER_SPACE * VL_THRES_UP)) +#define VL_HEADER_MARK_MAX ((uint16_t)(VL_HEADER_MARK * VL_THRES_UP)) +#define VL_HEADER_SPACE_MAX ((uint16_t)(VL_HEADER_SPACE * VL_THRES_UP)) + +#define VL_TIMING_BIT_ONE (uint16_t)(VL_TIMING_FAST * 3) +#define VL_TIMING_BIT_ZERO (uint16_t)(VL_TIMING_FAST) +#define VL_TIMING_BIT(bit) (bit ? VL_TIMING_BIT_ONE : VL_TIMING_BIT_ZERO) + +// legacy constants for old sender/receiver in modesharing, this one is +// less reliable and much slower +#define VL_TIMING_LEGACY (uint16_t)(3230) + +#define VL_HEADER_MARK_LEGACY (uint16_t)(VL_TIMING_LEGACY * 16) +#define VL_HEADER_SPACE_LEGACY (uint16_t)(VL_TIMING_LEGACY * 8) + +#define VL_HEADER_MARK_MIN_LEGACY ((uint16_t)(VL_HEADER_MARK_LEGACY * VL_THRES_DOWN)) +#define VL_HEADER_SPACE_MIN_LEGACY ((uint16_t)(VL_HEADER_SPACE_LEGACY * VL_THRES_DOWN)) + +#define VL_HEADER_MARK_MAX_LEGACY ((uint16_t)(VL_HEADER_MARK_LEGACY * VL_THRES_UP)) +#define VL_HEADER_SPACE_MAX_LEGACY ((uint16_t)(VL_HEADER_SPACE_LEGACY * VL_THRES_UP)) + +#define VL_TIMING_BIT_ONE_LEGACY (uint16_t)(VL_TIMING_LEGACY * 3) +#define VL_TIMING_BIT_ZERO_LEGACY (uint16_t)(VL_TIMING_LEGACY) +#define VL_TIMING_BIT_LEGACY(bit) (bit ? VL_TIMING_BIT_ONE_LEGACY : VL_TIMING_BIT_ZERO_LEGACY) -#define VL_DIVIDER_SPACE VL_HEADER_MARK -#define VL_DIVIDER_SPACE_MIN VL_HEADER_MARK_MIN -#define VL_DIVIDER_SPACE_MAX VL_HEADER_MARK_MAX #define VL_SEND_PWM_PIN 0 #define VL_RECEIVER_PIN 0 diff --git a/VortexEngine/src/Wireless/VLReceiver.cpp b/VortexEngine/src/Wireless/VLReceiver.cpp index 8d2f12a9a1..cd7ed5e7ee 100644 --- a/VortexEngine/src/Wireless/VLReceiver.cpp +++ b/VortexEngine/src/Wireless/VLReceiver.cpp @@ -1,5 +1,5 @@ #include "VLReceiver.h" -#include "IRConfig.h" +#include "VLConfig.h" #if VL_ENABLE_RECEIVER == 1 @@ -14,10 +14,68 @@ BitStream VLReceiver::m_vlData; VLReceiver::RecvState VLReceiver::m_recvState = WAITING_HEADER_MARK; uint32_t VLReceiver::m_prevTime = 0; uint8_t VLReceiver::m_pinState = 0; -uint32_t VLReceiver::m_previousBytes = 0; +uint16_t VLReceiver::m_previousBytes = 0; +// the determined time based on sync +uint16_t VLReceiver::m_vlMarkThreshold = 0; +uint16_t VLReceiver::m_vlSpaceThreshold = 0; +// counter is reused for two purposes +uint8_t VLReceiver::m_counter = 0; +// parity is for each byte of data received +uint8_t VLReceiver::m_parityBit = 0; +// legacy is for old receiving method (sucks) +bool VLReceiver::m_legacy = false; + +#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); } @@ -31,17 +89,9 @@ bool VLReceiver::dataReady() if (!isReceiving()) { return false; } - uint8_t blocks = m_vlData.data()[0]; - uint8_t remainder = m_vlData.data()[1]; - uint32_t total = ((blocks - 1) * 32) + remainder; - if (!total || total > VL_MAX_DATA_TRANSFER) { - DEBUG_LOGF("Bad VL Data size: %u", total); - return false; - } - // if there are size + 2 bytes in the VLData receiver - // then a full message is ready, the + 2 is from the - // two bytes for blocks + remainder that are sent first - return (m_vlData.bytepos() >= (uint32_t)(total + 2)); + uint8_t size = m_vlData.peekData(1); + // check if there are size + 1 bytes in the VLData receiver + return (m_vlData.bytepos() >= ((uint32_t)size + 2)); } // whether actively receiving @@ -60,11 +110,9 @@ uint8_t VLReceiver::percentReceived() if (!isReceiving()) { return 0; } - uint8_t blocks = m_vlData.data()[0]; - uint8_t remainder = m_vlData.data()[1]; - uint16_t total = ((blocks - 1) * 32) + remainder; + uint8_t size = m_vlData.peekData(1); // round by adding half of the total to the numerator - return (uint8_t)((uint16_t)((m_vlData.bytepos() * 100 + (total / 2)) / total)); + return (uint8_t)((uint16_t)((m_vlData.bytepos() * 100 + (size / 2)) / size)); } bool VLReceiver::receiveMode(Mode *pMode) @@ -83,12 +131,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; } @@ -108,16 +180,7 @@ bool VLReceiver::read(ByteStream &data) DEBUG_LOG("Nothing to read, or read too much"); return false; } - // read the size out (blocks + remainder) - uint8_t blocks = m_vlData.data()[0]; - uint8_t remainder = m_vlData.data()[1]; - // calculate size from blocks + remainder - uint32_t size = ((blocks - 1) * 32) + remainder; - if (!size || size > VL_MAX_DATA_TRANSFER) { - DEBUG_LOGF("Bad VL Data size: %u", size); - return false; - } - // the actual data starts 2 bytes later because of the size byte + uint8_t size = m_vlData.peekData(1); const uint8_t *actualData = m_vlData.data() + 2; if (!data.rawInit(actualData, size)) { DEBUG_LOG("Failed to init buffer for VL read"); @@ -128,61 +191,148 @@ bool VLReceiver::read(ByteStream &data) return true; } -// The recv PCI handler is called every time the pin state changes +// The recv PCI handler is called every time the analog value passes the +// 'threshold' which is dynamically established to be about half way between +// 'light' and 'dark' in the current environment. So in other words, it is +// called for every 'on' or 'off' blink void VLReceiver::recvPCIHandler() { // toggle the tracked pin state no matter what m_pinState = (uint8_t)!m_pinState; // grab current time uint32_t now = Time::microseconds(); - // check previous time for validity - if (!m_prevTime || m_prevTime > now) { - m_prevTime = now; - DEBUG_LOG("Bad first time diff, resetting..."); - resetVLState(); - return; - } // calc time difference between previous change and now uint32_t diff = (uint32_t)(now - m_prevTime); - // and update the previous changetime for next loop m_prevTime = now; - // handle the bliank duration and process it - handleVLTiming(diff); + // filter out values much too large from being truncated + if (diff > UINT16_MAX) { + return; + } + // handle the blink duration and process it into data + // legacy would normally be handled here but it is no longer supported + //if (m_legacy) { + // handleVLTimingLegacy((uint16_t)diff); + // return; + //} + handleVLTiming((uint16_t)diff); } // state machine that can be fed VL timings to parse them and interpret the intervals -void VLReceiver::handleVLTiming(uint32_t diff) +void VLReceiver::handleVLTimingLegacy(uint16_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); - resetVLState(); - return; - } switch (m_recvState) { - case WAITING_HEADER_MARK: // initial state - if (diff >= VL_HEADER_SPACE_MIN && diff <= VL_HEADER_MARK_MAX) { + case WAITING_HEADER_MARK: + // just look for a big blink timing + if (diff >= VL_HEADER_SPACE_MIN_LEGACY && diff <= VL_HEADER_MARK_MAX_LEGACY) { + // go straight to the header space m_recvState = WAITING_HEADER_SPACE; - } else { - DEBUG_LOGF("Bad header mark %u, resetting...", diff); - resetVLState(); } break; case WAITING_HEADER_SPACE: - if (diff >= VL_HEADER_SPACE_MIN && diff <= VL_HEADER_MARK_MAX) { + // the header space is technically shorter but this check uses the same range + // because being restrictive here isn't really beneficial + if (diff >= VL_HEADER_SPACE_MIN_LEGACY && diff <= VL_HEADER_MARK_MAX_LEGACY) { + // iterate to first data mark m_recvState = READING_DATA_MARK; - } else { - DEBUG_LOGF("Bad header space %u, resetting...", diff); - resetVLState(); + // estimate the data threshold based on the length of the space + m_vlMarkThreshold = (diff / 4); } break; case READING_DATA_MARK: - // classify mark/space based on the timing and write into buffer - m_vlData.write1Bit((diff > (VL_TIMING * 2)) ? 1 : 0); + // classify as 1 or 0 based on the mark threshold and write into buffer + m_vlData.write1Bit(diff > m_vlMarkThreshold); m_recvState = READING_DATA_SPACE; break; case READING_DATA_SPACE: - // the space could be just a regular space, or a gap in between blocks + // in the legacy transmission the spaces didn't carry data + m_recvState = READING_DATA_MARK; + break; + default: // ?? + DEBUG_LOGF("Bad receive state: %u", m_recvState); + break; + } +} + +// state machine that can be fed VL timings to parse them and interpret the intervals +void VLReceiver::handleVLTiming(uint16_t diff) +{ + uint8_t bit; + switch (m_recvState) { + case WAITING_HEADER_MARK: + case WAITING_HEADER_SPACE: + // both cases are basically the same, just look for a big timing + if (diff >= VL_HEADER_SPACE_MIN && diff <= VL_HEADER_MARK_MAX) { + // iterate through first two states + m_recvState = (RecvState)(m_recvState + 1); + } + break; + case READING_BAUD_MARK: + // accumulate the diff in the mark threshold for averaging later + m_vlMarkThreshold += diff; + m_recvState = READING_BAUD_SPACE; + break; + case READING_BAUD_SPACE: + // couonter is used to count bauds till 4 + m_counter++; + // accumulate the diff in the space threshold for averaging later + m_vlSpaceThreshold += diff; + // if not at 4 bauds yet keep reading baud marks + if (m_counter < 4) { + m_recvState = READING_BAUD_MARK; + break; + } + // otherwise read all 4 bauds now proceed with processing them + // average out the mark and space from the bauds + m_vlMarkThreshold /= 4; + m_vlSpaceThreshold /= 4; + // reset counter and parity bit + m_counter = 0; + m_parityBit = 0; + // advanced state to first data mark + m_recvState = READING_DATA_MARK; + break; + case READING_DATA_MARK: + // counter is now counting the marks for parity tracking + m_counter++; + // extract the bit of data based on the calculated mark threshold + bit = (diff > m_vlMarkThreshold) ? 1 : 0; + // accumulate the parity and write out the bit into the vldata + m_parityBit = (m_parityBit ^ bit) & 1; + m_vlData.write1Bit(bit); + m_recvState = READING_DATA_SPACE; + break; + case READING_DATA_SPACE: + // the spaces also transmit data in their lengths + bit = (diff > m_vlSpaceThreshold) ? 1 : 0; + // also accumulate this data into the parity + m_parityBit = (m_parityBit ^ bit) & 1; + // and then write out the bit into the vldata + m_vlData.write1Bit(bit); + // when counter is a multiple of 4 (since it only counts marks) + if ((m_counter % 4) == 0) { + // then go to the parity processing for the previous 8 bits of data + m_recvState = READING_DATA_PARITY_MARK; + } else { + // otherwise continue reading out data bits + m_recvState = READING_DATA_MARK; + } + break; + case READING_DATA_PARITY_MARK: + // the parity is also a bit of data using a mark + bit = (diff > m_vlMarkThreshold) ? 1 : 0; + // check the parity bit against the running accumulated parity + if ((m_parityBit & 1) != bit) { + // immediately reset the receiver if the parity doesn't match + // could show a flash here but that would probably just make + // receiving worse by introducing a delay + resetVLState(); + break; + } + // the parity space is just an empty space after the parity bit + m_recvState = READING_DATA_PARITY_SPACE; + break; + case READING_DATA_PARITY_SPACE: + // after the parity it's back to regular mark m_recvState = READING_DATA_MARK; break; default: // ?? @@ -193,10 +343,18 @@ void VLReceiver::handleVLTiming(uint32_t diff) void VLReceiver::resetVLState() { + m_counter = 0; + m_vlMarkThreshold = 0; + m_vlSpaceThreshold = 0; m_previousBytes = 0; + m_parityBit = 0; m_recvState = WAITING_HEADER_MARK; // zero out the receive buffer and reset bit receiver position m_vlData.reset(); +#ifdef VORTEX_EMBEDDED + // reset the threshold to a high value so that it can be pulled down again + threshold = THRESHOLD_BEGIN; +#endif DEBUG_LOG("VL State Reset"); } diff --git a/VortexEngine/src/Wireless/VLReceiver.h b/VortexEngine/src/Wireless/VLReceiver.h index be4b0a68b5..fb4ac174e6 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; @@ -40,14 +44,19 @@ class VLReceiver // reset VL receiver buffer static void resetVLState(); - static void recvPCIHandler(); + // turn on/off the legacy receiver + static void setLegacyReceiver(bool legacy) { m_legacy = legacy; } + // The handler called when the VL receiver flips between on/off + // this has to be public because it's called from a global ISR + // but technically it's an internal function for VLReceiver + static void recvPCIHandler(); private: - // reading functions // PCI handler for when VL receiver pin changes states static bool read(ByteStream &data); - static void handleVLTiming(uint32_t diff); + static void handleVLTiming(uint16_t diff); + static void handleVLTimingLegacy(uint16_t diff); // =================== // private data: @@ -60,8 +69,12 @@ class VLReceiver { WAITING_HEADER_MARK, WAITING_HEADER_SPACE, + READING_BAUD_MARK, + READING_BAUD_SPACE, READING_DATA_MARK, - READING_DATA_SPACE + READING_DATA_SPACE, + READING_DATA_PARITY_MARK, + READING_DATA_PARITY_SPACE }; // state information used by the PCIHandler @@ -71,7 +84,24 @@ class VLReceiver static uint8_t m_pinState; // used to compare if received data has changed since last checking - static uint32_t m_previousBytes; + static uint16_t m_previousBytes; + + // the determined time based on sync + static uint16_t m_vlMarkThreshold; + // the determined time based on sync + static uint16_t m_vlSpaceThreshold; + + // count of the sync bits (similar length starter bits) + static uint8_t m_counter; + + static uint8_t m_parityBit; + + // legacy mode + static bool m_legacy; + +#ifdef VORTEX_EMBEDDED + static void adcCheckTimerCallback(void *arg); +#endif #ifdef VORTEX_LIB friend class Vortex; diff --git a/VortexEngine/src/Wireless/VLSender.cpp b/VortexEngine/src/Wireless/VLSender.cpp index 8b39b9e563..7d91ff6c6f 100644 --- a/VortexEngine/src/Wireless/VLSender.cpp +++ b/VortexEngine/src/Wireless/VLSender.cpp @@ -1,5 +1,4 @@ #include "VLSender.h" -#include "IRConfig.h" #if VL_ENABLE_SENDER == 1 @@ -16,20 +15,9 @@ ByteStream VLSender::m_serialBuf; // a bit walker for the serial data BitStream VLSender::m_bitStream; -// whether actively sending -bool VLSender::m_isSending = false; -// the time of the last sent chunk -uint32_t VLSender::m_lastSendTime = 0; // some runtime meta info -uint32_t VLSender::m_size = 0; -// the number of blocks that will be sent -uint8_t VLSender::m_numBlocks = 0; -// the amount in the final block -uint8_t VLSender::m_remainder = 0; -// configuration options for the sender -uint32_t VLSender::m_blockSize = 0; -// write total -uint32_t VLSender::m_writeCounter = 0; +uint8_t VLSender::m_size = 0; +uint8_t VLSender::m_parity = 0; bool VLSender::init() { @@ -60,110 +48,110 @@ bool VLSender::loadMode(const Mode *targetMode) } // point the bitstream at the serial buffer m_bitStream.init((uint8_t *)m_serialBuf.rawData(), m_serialBuf.rawSize()); - // the size of the packet - m_size = m_serialBuf.rawSize(); - // the number of blocks that will be sent (possibly just 1 with less than 32 bytes) - m_numBlocks = (m_size + (VL_DEFAULT_BLOCK_SIZE - 1)) / VL_DEFAULT_BLOCK_SIZE; - // the amount in the final block (possibly the only block) - m_remainder = m_size % (VL_DEFAULT_BLOCK_SIZE + 0); - DEBUG_LOGF("Num blocks: %u", m_numBlocks); - DEBUG_LOGF("Remainder: %u", m_remainder); + // the size of the buf should never really exceed 256 + m_size = (uint8_t)m_serialBuf.rawSize(); + // reset parity + m_parity = 0; DEBUG_LOGF("Size: %u", m_size); - // reset write counter - m_writeCounter = 0; return true; } -bool VLSender::send() +void VLSender::send(const Mode *targetMode) { - // must have at least one block to send - if (m_numBlocks < 1) { - m_isSending = false; - return m_isSending; - } - if (!m_isSending) { - beginSend(); - } - if (m_lastSendTime > 0 && m_lastSendTime + VL_DEFAULT_BLOCK_SPACING > Time::getCurtime()) { - // don't send yet - return m_isSending; + loadMode(targetMode); + // now send the header + sendMarkSpace(VL_HEADER_MARK, VL_HEADER_SPACE); + // send some sync bytes to let the receiver determine baudrate, this will + // send: (zero mark - zero space - one mark - one space) x 2 + // then the receiver will add up all the marks and all the spaces and divide + // each by 4 to get the middle point between the one/zero for both marks and spaces + for (uint8_t b = 0; b < 2; b++) { + sendMarkSpace(VL_TIMING_BIT_ZERO, VL_TIMING_BIT_ZERO); + sendMarkSpace(VL_TIMING_BIT_ONE, VL_TIMING_BIT_ONE); } - // blocksize is default unless it's the last block, then it's the remainder - uint32_t blocksize = (m_numBlocks == 1) ? m_remainder : VL_DEFAULT_BLOCK_SIZE; - DEBUG_LOGF("VLSender Sending block #%u", m_numBlocks); - // pointer to the data at the correct location - const uint8_t *buf_ptr = m_bitStream.data() + m_writeCounter; + // it ends up being way cheaper just to send this legacy byte than to + // implement logic in the receiver to account for this byte being missing in + // the new protocol, so we send it, originally it was the block count but + // that always ended up being 1 anyway for the Duo because blocksize was 255 + // and the duo mode size max was no more than ~76 bytes? (I forget right now) + sendByte(1); + // send the size of the data first (this was the blocksize) + sendByte(m_size); // iterate each byte in the block and write it - for (uint32_t i = 0; i < blocksize; ++i) { + for (uint8_t i = 0; i < m_size; ++i) { // write the byte - sendByte(*buf_ptr); - // increment to next byte in buffer - buf_ptr++; - // record the written byte - m_writeCounter++; + sendByte(m_bitStream.peekData(i)); } - // wrote one block - m_numBlocks--; - // still sending if we have more blocks - m_isSending = (m_numBlocks > 0); - // the curtime - m_lastSendTime = Time::getCurtime(); - // return whether still sending - return m_isSending; + // send one last blink because the previous data could end on a space + // so the receiver needs a last blink to determine it's length + sendMarkSpace(VL_TIMING_BIT_ZERO, VL_TIMING_BIT_ZERO); } -void VLSender::beginSend() +void VLSender::sendLegacy(const Mode *targetMode) { - m_isSending = true; - DEBUG_LOGF("[%zu] Beginning send size %u (blocks: %u remainder: %u blocksize: %u)", - Time::microseconds(), m_size, m_numBlocks, m_remainder, m_blockSize); - // wakeup the other receiver with a very quick mark/space - sendMark(50); - sendSpace(100); + loadMode(targetMode); + // wakeup receiver + sendMarkSpace(50, 50); // now send the header - sendMark(VL_HEADER_MARK); - sendSpace(VL_HEADER_SPACE); - // reset writeCounter - m_writeCounter = 0; - // write the number of blocks being sent, most likely just 1 - sendByte(m_numBlocks); - // now write the number of bytes in the last block - sendByte(m_remainder); + sendMarkSpace(VL_HEADER_MARK_LEGACY, VL_HEADER_SPACE_LEGACY); + // the number of blocks is always 1, previously block size would be for big + // transfers like IR but in reality that never happens on a duo so it is 1 + sendByteLegacy(1); + // the next byte is the size or 'remainder' in the last block, so 1 block + // means the size in this byte is the size of that block. But again on duo + // this protocol ended up being impractical and only 1 block is ever needed. + // The purpose of separating blocks was to put a space between blocks for + // improved sender/receiver synchronization instead of one long stream + sendByteLegacy(m_size); + // iterate each byte in the block and write it + for (uint8_t i = 0; i < m_size; ++i) { + // write the byte + sendByteLegacy(m_bitStream.peekData(i)); + } } void VLSender::sendByte(uint8_t data) { - // Sends from left to right, MSB first - for (int b = 0; b < 8; b++) { - // grab the bit of data at the indexspace - uint32_t bit = (data >> (7 - b)) & 1; - // send 3x timing size for 1s and 1x timing for 0 - sendMark(VL_TIMING + (VL_TIMING * (2 * bit))); - // send 1x timing size for space - sendSpace(VL_TIMING); + // Loop through bits 7-0 two at a time + for (uint8_t i = 0; i < 8; i += 2) { + // Extract the next two bits as a 2-bit value + uint8_t pair = (data >> (6 - i)) & 0x3; + // First bit (mark) is the higher bit + uint8_t mark = (pair >> 1) & 1; + // Second bit (space) is the lower bit + uint8_t space = pair & 1; + // Send both bits as a mark-space pair + sendMarkSpace(VL_TIMING_BIT(mark), VL_TIMING_BIT(space)); + // Update parity by XOR'ing both bits + m_parity ^= mark ^ space; } - DEBUG_LOGF("Sent byte[%u]: 0x%x", m_writeCounter, data); - } + // Send final parity bit as mark, followed by a 0 space + sendMarkSpace(VL_TIMING_BIT(m_parity & 1), VL_TIMING_BIT(0)); +} -void VLSender::sendMark(uint16_t time) +void VLSender::sendByteLegacy(uint8_t data) { -#ifdef VORTEX_LIB - // send mark timing over socket - Vortex::vcallbacks()->infraredWrite(true, time); -#else - startPWM(); - Time::delayMicroseconds(time); -#endif + // Sends from left to right, MSB first + for (uint8_t b = 0; b < 8; b++) { + // grab the bit of data at the index + uint8_t bit = (data >> (7 - b)) & 1; + // send 3x timing size for 1s and 1x timing for 0 + sendMarkSpace(VL_TIMING_BIT_LEGACY(bit), VL_TIMING); + } } -void VLSender::sendSpace(uint16_t time) +void VLSender::sendMarkSpace(uint16_t markTime, uint16_t spaceTime) { #ifdef VORTEX_LIB + // send mark timing over socket + Vortex::vcallbacks()->infraredWrite(true, markTime); // send space timing over socket - Vortex::vcallbacks()->infraredWrite(false, time); + Vortex::vcallbacks()->infraredWrite(false, spaceTime); #else + startPWM(); + Time::delayMicrosecondsCancellable(markTime); stopPWM(); - Time::delayMicroseconds(time); + Time::delayMicrosecondsCancellable(spaceTime); #endif } diff --git a/VortexEngine/src/Wireless/VLSender.h b/VortexEngine/src/Wireless/VLSender.h index ad25ea203d..a368201d99 100644 --- a/VortexEngine/src/Wireless/VLSender.h +++ b/VortexEngine/src/Wireless/VLSender.h @@ -18,24 +18,18 @@ class VLSender static bool init(); static void cleanup(); - // initialize the VL sender with a mode to send - static bool loadMode(const Mode *targetMode); - static bool send(); - - static bool isSending() { return m_isSending; } - - static uint32_t percentDone() { return (uint32_t)(((float)m_writeCounter / (float)m_size) * 100.0); } + // send a mode + static void send(const Mode *targetMode); + static void sendLegacy(const Mode *targetMode); private: - // sender functions - static void beginSend(); + static bool loadMode(const Mode *targetMode); // send a full 8 bits in a tight loop static void sendByte(uint8_t data); + // send full 8 bits legacy protocol + static void sendByteLegacy(uint8_t data); // send a mark/space by turning PWM on/off - static void sendMark(uint16_t time); - static void sendSpace(uint16_t time); - // Pulse-Width Modulator (VL Transmitter) - static void initPWM(); + static void sendMarkSpace(uint16_t markTime, uint16_t spaceTime); // turn the VL transmitter on/off in realtime static void startPWM(); static void stopPWM(); @@ -44,21 +38,10 @@ class VLSender static ByteStream m_serialBuf; // a bit walker for the serial data static BitStream m_bitStream; - static bool m_isSending; - static uint32_t m_lastSendTime; // some runtime meta info - static uint32_t m_size; - // the number of blocks that will be sent - static uint8_t m_numBlocks; - // the amount in the final block - static uint8_t m_remainder; - - // configuration options for the sender - static uint32_t m_blockSize; - - // write total - static uint32_t m_writeCounter; + static uint8_t m_size; + static uint8_t m_parity; }; #endif From ecbc4e4d9d6e03d3bbfa68e151cf05fa16756711 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 19 Feb 2026 01:00:14 -0800 Subject: [PATCH 05/13] more fixes --- VortexEngine/src/VortexConfig.h | 14 +++++ VortexEngine/src/Wireless/IRConfig.h | 7 +-- VortexEngine/src/Wireless/VLConfig.h | 7 --- VortexEngine/src/Wireless/VLReceiver.cpp | 73 ------------------------ VortexEngine/src/Wireless/VLReceiver.h | 8 --- VortexEngine/src/Wireless/VLSender.cpp | 4 +- 6 files changed, 17 insertions(+), 96 deletions(-) diff --git a/VortexEngine/src/VortexConfig.h b/VortexEngine/src/VortexConfig.h index 730a50e9d5..a102d51f63 100644 --- a/VortexEngine/src/VortexConfig.h +++ b/VortexEngine/src/VortexConfig.h @@ -340,6 +340,20 @@ // =================================================================== // Boolean Configurations (0 or 1) +// Infrared Enable +// +// Whether to enable the Infrared system as a whole. This is for mode +// transferring with IR peripherals +#define IR_ENABLE_SENDER 1 +#define IR_ENABLE_RECEIVER 1 + +// Visible Light Enable +// +// Whether to enable the Visible Light system as a whole. This is for mode +// transferring with the Leds and a light sensor +#define VL_ENABLE_SENDER 1 +#define VL_ENABLE_RECEIVER 1 + // Debug Allocations // // Tracks all memory allocations and logs them, useful for finding leaks diff --git a/VortexEngine/src/Wireless/IRConfig.h b/VortexEngine/src/Wireless/IRConfig.h index 2a8e68ddf2..2b2a023b32 100644 --- a/VortexEngine/src/Wireless/IRConfig.h +++ b/VortexEngine/src/Wireless/IRConfig.h @@ -1,12 +1,7 @@ #ifndef IR_CONFIG_H #define IR_CONFIG_H -// Infrared Enable -// -// Whether to enable the Infrared system as a whole -// -#define IR_ENABLE_SENDER 1 -#define IR_ENABLE_RECEIVER 1 +#include "../VortexConfig.h" // the size of IR blocks in bits #define IR_DEFAULT_BLOCK_SIZE 32 diff --git a/VortexEngine/src/Wireless/VLConfig.h b/VortexEngine/src/Wireless/VLConfig.h index cfc1a323f8..2f4548668b 100644 --- a/VortexEngine/src/Wireless/VLConfig.h +++ b/VortexEngine/src/Wireless/VLConfig.h @@ -3,13 +3,6 @@ #include "../VortexConfig.h" -// Visible Light Enable -// -// Whether to enable the Visible Light system as a whole -// -#define VL_ENABLE_SENDER 1 -#define VL_ENABLE_RECEIVER 0 - // the size of IR blocks in bits #define VL_DEFAULT_BLOCK_SIZE 256 #define VL_DEFAULT_BLOCK_SPACING MS_TO_TICKS(5) diff --git a/VortexEngine/src/Wireless/VLReceiver.cpp b/VortexEngine/src/Wireless/VLReceiver.cpp index cd7ed5e7ee..bafcc0ed02 100644 --- a/VortexEngine/src/Wireless/VLReceiver.cpp +++ b/VortexEngine/src/Wireless/VLReceiver.cpp @@ -25,57 +25,8 @@ uint8_t VLReceiver::m_parityBit = 0; // legacy is for old receiving method (sucks) bool VLReceiver::m_legacy = false; -#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); } @@ -131,36 +82,12 @@ 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; } diff --git a/VortexEngine/src/Wireless/VLReceiver.h b/VortexEngine/src/Wireless/VLReceiver.h index fb4ac174e6..96482cea1f 100644 --- a/VortexEngine/src/Wireless/VLReceiver.h +++ b/VortexEngine/src/Wireless/VLReceiver.h @@ -9,10 +9,6 @@ #if VL_ENABLE_RECEIVER == 1 -#ifdef VORTEX_EMBEDDED -#include -#endif - class ByteStream; class Mode; @@ -99,10 +95,6 @@ class VLReceiver // legacy mode static bool m_legacy; -#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 7d91ff6c6f..d320ec9b00 100644 --- a/VortexEngine/src/Wireless/VLSender.cpp +++ b/VortexEngine/src/Wireless/VLSender.cpp @@ -149,9 +149,9 @@ void VLSender::sendMarkSpace(uint16_t markTime, uint16_t spaceTime) Vortex::vcallbacks()->infraredWrite(false, spaceTime); #else startPWM(); - Time::delayMicrosecondsCancellable(markTime); + Time::delayMicroseconds(markTime); stopPWM(); - Time::delayMicrosecondsCancellable(spaceTime); + Time::delayMicroseconds(spaceTime); #endif } From 1d21473b8a9d0e017bd7086c734b106a23bb9ddd Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 19 Feb 2026 01:53:52 -0800 Subject: [PATCH 06/13] small fix --- VortexEngine/src/Menus/MenuList/EditorConnection.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp index 24801bc3e5..8810871837 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp @@ -698,15 +698,15 @@ bool EditorConnection::isConnected() void EditorConnection::readData(ByteStream &buffer) { - readData(buffer); + SerialComs::read(buffer); } void EditorConnection::writeData(ByteStream &buffer) { - writeData(buffer); + SerialComs::write(buffer); } void EditorConnection::writeData(const char *message) { - writeData(message); + SerialComs::write(message); } From 1f61a4631c701c33b573ef7779d9260279865400 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 19 Feb 2026 21:21:50 -0800 Subject: [PATCH 07/13] remove core build file --- .github/workflows/core_build.yml | 125 ------------------------------- 1 file changed, 125 deletions(-) delete mode 100644 .github/workflows/core_build.yml diff --git a/.github/workflows/core_build.yml b/.github/workflows/core_build.yml deleted file mode 100644 index ee113c8eb7..0000000000 --- a/.github/workflows/core_build.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: Core Build - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - workflow_dispatch: # manual trigger - -env: - EMSDK_VERSION: "3.1.74" - -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: | - git clone https://github.com/emscripten-core/emsdk.git - cd emsdk - git fetch --tags - git checkout "${EMSDK_VERSION}" - ./emsdk install "${EMSDK_VERSION}" - ./emsdk activate "${EMSDK_VERSION}" - 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@v4 - with: - name: doxygen-docs-core - path: docs/core From 0deeb9a3884fe228d318116ef479fa4aa0f6e605 Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 22 Feb 2026 04:40:28 -0800 Subject: [PATCH 08/13] added memory operators toggle --- VortexEngine/src/Memory/Memory.cpp | 2 +- VortexEngine/src/Memory/Memory.h | 4 +++- VortexEngine/src/VortexConfig.h | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/VortexEngine/src/Memory/Memory.cpp b/VortexEngine/src/Memory/Memory.cpp index 9172488167..c44abd97c9 100644 --- a/VortexEngine/src/Memory/Memory.cpp +++ b/VortexEngine/src/Memory/Memory.cpp @@ -117,7 +117,7 @@ uint32_t cur_memory_usage_total() #endif // DEBUG_ALLOCATIONS == 1 -#ifndef VORTEX_LIB +#if CPP_MEMORY_OPERATORS == 1 // for C++11 need the following: void *operator new (size_t size) { return vmalloc(size); } diff --git a/VortexEngine/src/Memory/Memory.h b/VortexEngine/src/Memory/Memory.h index b851d781ec..275a19aaf4 100644 --- a/VortexEngine/src/Memory/Memory.h +++ b/VortexEngine/src/Memory/Memory.h @@ -36,7 +36,8 @@ uint32_t cur_memory_usage_total(); #endif -#ifndef VORTEX_LIB +#if CPP_MEMORY_OPERATORS == 1 + void *operator new (size_t size); void *operator new[](size_t size); void operator delete (void *ptr); @@ -51,6 +52,7 @@ void operator delete[](void *ptr, size_t size) noexcept; //void operator delete[](void *ptr, std::align_val_t al) noexcept; //void operator delete (void *ptr, size_t size, std::align_val_t al) noexcept; //void operator delete[](void *ptr, size_t size, std::align_val_t al) noexcept; + #endif #endif diff --git a/VortexEngine/src/VortexConfig.h b/VortexEngine/src/VortexConfig.h index a102d51f63..7b40762700 100644 --- a/VortexEngine/src/VortexConfig.h +++ b/VortexEngine/src/VortexConfig.h @@ -365,6 +365,14 @@ // it will allocate space permanently for the new brush and never free it #define DEBUG_ALLOCATIONS 0 +// CPP Memory Operators +// +// Uses custom/drop-in replacements for new/delete to support C++ memory +// operators in embedded environments where they aren't normally available. +// Turn this off if the framework/hardware being targetted offers new/delete +// and enable it if new/delete are unavailable. +#define CPP_MEMORY_OPERATORS 1 + // Variable Tickrate // // This controls whether the setTickrate function is available and @@ -632,6 +640,10 @@ #define RGB_MENU_BRIGHTNESS_SELECT RGB_YELLOW4 #define RGB_MENU_FACTORY_RESET RGB_RED4 +// vortex lib doesn't need custom memory operators for cpp +#undef CPP_MEMORY_OPERATORS +#define CPP_MEMORY_OPERATORS 0 + #endif // ifdef VORTEX_LIB // This will be defined if the project is being built inside the test framework From 73c3bd1112ace3e1e85fb3df50232deb3ad7248c Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 22 Feb 2026 04:44:28 -0800 Subject: [PATCH 09/13] disable cpp memory operators --- VortexEngine/src/VortexConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VortexEngine/src/VortexConfig.h b/VortexEngine/src/VortexConfig.h index e69df5367a..7f48228113 100644 --- a/VortexEngine/src/VortexConfig.h +++ b/VortexEngine/src/VortexConfig.h @@ -371,7 +371,7 @@ // operators in embedded environments where they aren't normally available. // Turn this off if the framework/hardware being targetted offers new/delete // and enable it if new/delete are unavailable. -#define CPP_MEMORY_OPERATORS 1 +#define CPP_MEMORY_OPERATORS 0 // Variable Tickrate // From d19d08bfe4e12b3b5d40621ad4d8a48bf65c5740 Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 22 Feb 2026 04:56:27 -0800 Subject: [PATCH 10/13] small whitespace --- VortexEngine/src/Menus/Menu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VortexEngine/src/Menus/Menu.h b/VortexEngine/src/Menus/Menu.h index b60958720b..8e17f78e85 100644 --- a/VortexEngine/src/Menus/Menu.h +++ b/VortexEngine/src/Menus/Menu.h @@ -16,7 +16,7 @@ class Menu virtual bool init(); // the action for the menu to execute - enum MenuAction :uint8_t { + enum MenuAction : uint8_t { // quit the menus MENU_QUIT, // continue running the menu From 8fc411739e14f1b611e1db93f56d49541806c162 Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 22 Feb 2026 05:03:14 -0800 Subject: [PATCH 11/13] more small fixes --- VortexEngine/src/Modes/Modes.h | 2 +- VortexEngine/src/Serial/BitStream.cpp | 5 +++-- VortexEngine/src/Serial/BitStream.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/VortexEngine/src/Modes/Modes.h b/VortexEngine/src/Modes/Modes.h index 8de629da45..b35fd85eeb 100644 --- a/VortexEngine/src/Modes/Modes.h +++ b/VortexEngine/src/Modes/Modes.h @@ -119,7 +119,7 @@ class Modes // set or get flags static bool setFlag(uint8_t flag, bool enable, bool save = true); - static bool getFlag(uint8_t flag) { return ((m_globalFlags & flag) != 0); } + static bool getFlag(uint8_t flag) { return ((m_globalFlags & flag) == flag); } // reset flags to factory default (must save after) static void resetFlags() { m_globalFlags = 0; } diff --git a/VortexEngine/src/Serial/BitStream.cpp b/VortexEngine/src/Serial/BitStream.cpp index 9245f381a2..27aadeaac0 100644 --- a/VortexEngine/src/Serial/BitStream.cpp +++ b/VortexEngine/src/Serial/BitStream.cpp @@ -86,7 +86,7 @@ uint8_t BitStream::read1Bit() return rv; } -void BitStream::write1Bit(uint8_t bit) +void BitStream::write1Bit(bool bit) { if (m_buf_eof) { return; @@ -95,7 +95,8 @@ void BitStream::write1Bit(uint8_t bit) m_buf_eof = true; return; } - m_buf[m_bit_pos / 8] |= (bit & 1) << (7 - (m_bit_pos % 8)); + uint8_t bitVal = (uint8_t)bit & 1; + m_buf[m_bit_pos / 8] |= bitVal << (7 - (m_bit_pos % 8)); m_bit_pos++; if (m_bit_pos >= (m_buf_size * 8)) { m_buf_eof = true; diff --git a/VortexEngine/src/Serial/BitStream.h b/VortexEngine/src/Serial/BitStream.h index 79a3c1c233..09a3b06a8c 100644 --- a/VortexEngine/src/Serial/BitStream.h +++ b/VortexEngine/src/Serial/BitStream.h @@ -24,7 +24,7 @@ class BitStream // read/write a single bit in LSB uint8_t read1Bit(); - void write1Bit(uint8_t bit); + void write1Bit(bool bit); // read/write multiple bits from left to right at LSB uint8_t readBits(uint32_t numBits); void writeBits(uint32_t numBits, uint32_t val); From 2a91c1f4f5b9fdfd1144517f85e1854ed989b89a Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 22 Feb 2026 05:06:30 -0800 Subject: [PATCH 12/13] small fix --- VortexEngine/src/Wireless/IRReceiver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VortexEngine/src/Wireless/IRReceiver.cpp b/VortexEngine/src/Wireless/IRReceiver.cpp index 47ec50ef4d..1f467a8995 100644 --- a/VortexEngine/src/Wireless/IRReceiver.cpp +++ b/VortexEngine/src/Wireless/IRReceiver.cpp @@ -178,7 +178,7 @@ void IRReceiver::handleIRTiming(uint32_t diff) break; case READING_DATA_MARK: // classify mark/space based on the timing and write into buffer - m_irData.write1Bit((diff > (IR_TIMING * 2)) ? 1 : 0); + m_irData.write1Bit(diff > (IR_TIMING * 2)); m_recvState = READING_DATA_SPACE; break; case READING_DATA_SPACE: From ab8605031c8b2c1f114f25d2d6152a74685ab3a8 Mon Sep 17 00:00:00 2001 From: Dan Date: Sun, 22 Feb 2026 05:41:47 -0800 Subject: [PATCH 13/13] small fix --- VortexEngine/src/VortexConfig.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/VortexEngine/src/VortexConfig.h b/VortexEngine/src/VortexConfig.h index 7b40762700..64810b82fd 100644 --- a/VortexEngine/src/VortexConfig.h +++ b/VortexEngine/src/VortexConfig.h @@ -584,6 +584,9 @@ // get the global brightness of the device #define EDITOR_VERB_GET_GLOBAL_BRIGHTNESS "M" +// set the global brightness of the chromalinked duo +#define EDITOR_VERB_SET_CHROMA_BRIGHTNESS "N" + // =================================================================== // Manually Configured Sizes //