diff --git a/.github/workflows/spark_build.yml b/.github/workflows/spark_build.yml index f33b3715fb..f4c9380425 100644 --- a/.github/workflows/spark_build.yml +++ b/.github/workflows/spark_build.yml @@ -89,19 +89,18 @@ jobs: export VORTEX_BUILD_NUMBER=${{ needs.setup.outputs.vortex_build_number }} export VORTEX_VERSION_NUMBER=${{ needs.setup.outputs.vortex_version_number }} make build - - name: Archive production artifacts - uses: actions/upload-artifact@v4 - with: - name: embedded firmware - path: | + - name: Zip firmware files + run: | + zip SparkFirmware.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 - build/VortexEngine.ino.elf - build/VortexEngine.ino.map - - name: Archive production artifacts for deployment + - name: Archive firmware zip uses: actions/upload-artifact@v4 with: - name: firmware-artifact - path: build/VortexEngine.ino.bin + name: spark-firmware-zip + path: SparkFirmware.zip wasm: needs: [setup, test, embedded] @@ -130,7 +129,8 @@ jobs: working-directory: VortexEngine/VortexLib docs: - needs: [setup, test, embedded, wasm] + #todo: fix the depends to be setup, test, embedded, wasm + needs: [setup, test] runs-on: ubuntu-latest if: github.ref == 'refs/heads/spark' steps: @@ -162,15 +162,15 @@ jobs: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: - name: firmware-artifact + name: spark-firmware-zip path: build - name: Rename and Deploy Firmware run: | DEVICE_TYPE="spark" - VERSIONED_FILENAME="VortexEngine-${DEVICE_TYPE}-${{ needs.setup.outputs.vortex_version_number }}.bin" - mv build/VortexEngine.ino.bin build/$VERSIONED_FILENAME + VERSIONED_FILENAME="VortexEngine-${DEVICE_TYPE}-${{ needs.setup.outputs.vortex_version_number }}.zip" + mv build/SparkFirmware.zip build/$VERSIONED_FILENAME echo "Version is ${{ needs.setup.outputs.vortex_version_number }}" - echo "Filename is is $VERSIONED_FILENAME" + echo "Filename is $VERSIONED_FILENAME" curl -X POST \ -F "file=@build/$VERSIONED_FILENAME" \ -F "device=$DEVICE_TYPE" \ diff --git a/VortexEngine/VortexEngine.ino b/VortexEngine/VortexEngine.ino index 014b37a8af..a0985e6bd1 100644 --- a/VortexEngine/VortexEngine.ino +++ b/VortexEngine/VortexEngine.ino @@ -1,5 +1,4 @@ #include - #include "src/VortexEngine.h" void setup() diff --git a/VortexEngine/src/Buttons/Buttons.cpp b/VortexEngine/src/Buttons/Buttons.cpp index 073b55684d..b146ac3118 100644 --- a/VortexEngine/src/Buttons/Buttons.cpp +++ b/VortexEngine/src/Buttons/Buttons.cpp @@ -12,7 +12,7 @@ #define BUTTON_PIN 47 #else // orbit is on 7 -#define BUTTON_PIN 7 +#define BUTTON_PIN 5 #endif // Since there is only one button I am just going to expose a global pointer to diff --git a/VortexEngine/src/Leds/LedTypes.h b/VortexEngine/src/Leds/LedTypes.h index 64317d3843..6d2b03d229 100644 --- a/VortexEngine/src/Leds/LedTypes.h +++ b/VortexEngine/src/Leds/LedTypes.h @@ -164,6 +164,11 @@ 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)) +// bitmaps specific to Sparks +#define MAP_OPPOSITES_1 (MAP_LED(LED_0) | MAP_LED(LED_3)) +#define MAP_OPPOSITES_2 (MAP_LED(LED_1) | MAP_LED(LED_4)) +#define MAP_OPPOSITES_3 (MAP_LED(LED_2) | MAP_LED(LED_5)) + // 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 a06ff99751..09a665aa06 100644 --- a/VortexEngine/src/Leds/Leds.cpp +++ b/VortexEngine/src/Leds/Leds.cpp @@ -15,12 +15,7 @@ #ifdef VORTEX_EMBEDDED #pragma GCC diagnostic ignored "-Wclass-memaccess" #include -#define LED_PIN 0 -#if SPARK_HANDLE == 1 -#define MOSFET_PIN 48 -#else -#define MOSFET_PIN 18 -#endif +#define LED_PIN 0 #endif // global brightness @@ -31,10 +26,8 @@ RGBColor Leds::m_ledColors[LED_COUNT] = { RGB_OFF }; bool Leds::init() { #ifdef VORTEX_EMBEDDED - FastLED.addLeds((CRGB *)m_ledColors, LED_COUNT); + FastLED.addLeds((CRGB *)m_ledColors, LED_COUNT); FastLED.setMaxRefreshRate(0); - pinMode(MOSFET_PIN, OUTPUT); - digitalWrite(MOSFET_PIN, HIGH); #endif #ifdef VORTEX_LIB Vortex::vcallbacks()->ledsInit(m_ledColors, LED_COUNT); @@ -91,8 +84,10 @@ void Leds::setRangeEvens(Pair first, Pair last, RGBColor col) void Leds::setAllEvens(RGBColor col) { - for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { - setIndex(pairEven(pos), col); + for (LedPos pos = LED_FIRST; pos <= LED_LAST; pos++) { + if (isEven(pos)) { + setIndex(pos, col); + } } } @@ -105,8 +100,10 @@ void Leds::setRangeOdds(Pair first, Pair last, RGBColor col) void Leds::setAllOdds(RGBColor col) { - for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { - setIndex(pairOdd(pos), col); + for (LedPos pos = LED_FIRST; pos <= LED_LAST; pos++) { + if (isOdd(pos)) { + setIndex(pos, col); + } } } @@ -119,8 +116,10 @@ void Leds::clearRangeEvens(Pair first, Pair last) void Leds::clearAllEvens() { - for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { - clearIndex(pairEven(pos)); + for (LedPos pos = LED_FIRST; pos <= LED_LAST; pos++) { + if (isEven(pos)) { + clearIndex(pos); + } } } @@ -133,8 +132,10 @@ void Leds::clearRangeOdds(Pair first, Pair last) void Leds::clearAllOdds() { - for (Pair pos = PAIR_FIRST; pos <= PAIR_LAST; pos++) { - clearIndex(pairOdd(pos)); + for (LedPos pos = LED_FIRST; pos <= LED_LAST; pos++) { + if (isOdd(pos)) { + clearIndex(pos); + } } } diff --git a/VortexEngine/src/Menus/Menu.cpp b/VortexEngine/src/Menus/Menu.cpp index 9df6728d76..6e76e7a93c 100644 --- a/VortexEngine/src/Menus/Menu.cpp +++ b/VortexEngine/src/Menus/Menu.cpp @@ -131,18 +131,27 @@ void Menu::nextBulbSelection() // do not allow multi led to select anything else //break; } - m_targetLeds = MAP_LED(LED_FIRST); + m_targetLeds = MAP_LED(LED_MULTI); break; - case MAP_LED(LED_LAST): + case MAP_LED(LED_MULTI): 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); + m_targetLeds = MAP_OPPOSITES_1; break; - case MAP_LED(LED_MULTI): + case MAP_OPPOSITES_1: + m_targetLeds = MAP_OPPOSITES_2; + break; + case MAP_OPPOSITES_2: + m_targetLeds = MAP_OPPOSITES_3; + break; + case MAP_OPPOSITES_3: + m_targetLeds = MAP_LED(LED_FIRST); + break; + case MAP_LED(LED_LAST): m_targetLeds = MAP_LED_ALL; break; default: // LED_FIRST through LED_LAST diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp index c8dade7503..72d8f6646d 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" @@ -14,12 +16,19 @@ #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 +45,7 @@ bool EditorConnection::init() // skip led selection m_ledSelected = true; clearDemo(); + DEBUG_LOG("Entering Editor Connection"); return true; } @@ -65,14 +75,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 +86,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 +109,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 +151,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 +175,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 +199,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 +228,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 +290,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(); @@ -280,7 +331,6 @@ Menu::MenuAction EditorConnection::run() m_state = STATE_IDLE; break; } - return MENU_CONTINUE; } void EditorConnection::sendCurModeVL() @@ -293,7 +343,15 @@ void EditorConnection::sendCurModeVL() m_state = STATE_TRANSMIT_MODE_VL; } -// handlers for clicks +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; +} + void EditorConnection::onShortClick() { // if the device has received any commands do not reset! @@ -313,12 +371,34 @@ void EditorConnection::onLongClick() 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(); + } +} + void EditorConnection::showEditor() { switch (m_state) { @@ -327,7 +407,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 +454,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 +474,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 +590,94 @@ 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); + } 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..443ff4050c 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.h +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.h @@ -17,6 +17,7 @@ class EditorConnection : public Menu // broadcast the current preview mode over VL void sendCurModeVL(); + void listenModeVL(); // handlers for clicks void onShortClick() override; @@ -26,18 +27,25 @@ class EditorConnection : public Menu 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 +79,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 +96,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/ModeSharing.cpp b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp index e3391c1df2..1adff8c1cd 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) { } @@ -31,10 +34,14 @@ bool ModeSharing::init() } // skip led selection m_ledSelected = true; - // 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(); + if (m_advanced) { + // 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 + beginReceivingVL(); + } else { + beginReceivingIR(); + } DEBUG_LOG("Entering Mode Sharing"); return true; } @@ -46,11 +53,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,12 +65,18 @@ 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; } @@ -72,11 +85,22 @@ Menu::MenuAction ModeSharing::run() void ModeSharing::onShortClick() { 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; @@ -86,25 +110,24 @@ void ModeSharing::onShortClick() void ModeSharing::onLongClick() { - Modes::updateCurMode(&m_previewMode); - leaveMenu(true); + leaveMenu(); } -void ModeSharing::beginSendingVL() +void ModeSharing::switchVLIR() { - // 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 + 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::beginSendingIR() @@ -115,47 +138,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(); @@ -177,11 +200,74 @@ void ModeSharing::receiveModeIR() 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); + Modes::updateCurMode(&m_previewMode); + // leave menu and save settings, even if the mode was the same whatever + leaveMenu(true); +} + +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()); + Modes::updateCurMode(&m_previewMode); + // leave menu and save settings, even if the mode was the same whatever + leaveMenu(true); } void ModeSharing::showSendModeVL() @@ -193,19 +279,28 @@ 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::showReceiveMode() +void ModeSharing::showReceiveModeVL() { - 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() / 16), RGBColor(0, 1, 0)); } else { - if (m_advanced) { - m_previewMode.play(); - } else { - Leds::setAll(RGB_WHITE0); - } + Leds::setAll(0x010101); + } +} + +void ModeSharing::showReceiveModeIR() +{ + if (VLReceiver::isReceiving()) { + // using uint32_t to avoid overflow, the result should be within 10 to 255 + Leds::clearAll(); + Leds::setRange(LED_FIRST, (LedPos)(IRReceiver::percentReceived() / 16), RGBColor(0, 255, 0)); + } else { + Leds::setAll(RGB_WHITE0); } } diff --git a/VortexEngine/src/Menus/MenuList/ModeSharing.h b/VortexEngine/src/Menus/MenuList/ModeSharing.h index dc5adf2357..aae4198d81 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.h +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.h @@ -18,26 +18,37 @@ class ModeSharing : public Menu 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/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..0a39133551 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/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..f2a778724b 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..2398365301 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 / 2) 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,14 @@ 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; } // 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..deedf0304b 100644 --- a/VortexEngine/src/Patterns/Multi/VortexPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/VortexPattern.cpp @@ -33,14 +33,16 @@ 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()); + if (MIDDLE_POINT + m_progress != LED_COUNT) { + Leds::setIndex((LedPos)(MIDDLE_POINT + m_progress), m_colorset.cur()); + } + Leds::setIndex((LedPos)(MIDDLE_POINT - m_progress), m_colorset.cur()); } void VortexPattern::poststep() { // step till the middle point - m_progress = (m_progress + 1) % MIDDLE_POINT; + m_progress = (m_progress + 1) % (MIDDLE_POINT + 1); // each cycle progress to the next color if (m_progress == 0) { m_colorset.getNext(); diff --git a/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp b/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp index 1a2b19b185..23d92e7e07 100644 --- a/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp +++ b/VortexEngine/src/Patterns/Multi/VortexWipePattern.cpp @@ -5,19 +5,8 @@ #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 -}; +// add 1 to prevent the middle point from being led 0 +#define MIDDLE_POINT ((LED_COUNT + 1) / 2) VortexWipePattern::VortexWipePattern(const PatternArgs &args) : BlinkStepPattern(args), @@ -43,17 +32,20 @@ void VortexWipePattern::init() void VortexWipePattern::blinkOn() { - for (int index = 0; index < m_progress; ++index) { - Leds::setIndex(ledStepPositions[index], m_colorset.peekNext()); + Leds::setAll(m_colorset.cur()); + if (!m_progress) { + // none } - for (int index = m_progress; index < LED_COUNT; ++index) { - Leds::setIndex(ledStepPositions[index], m_colorset.cur()); + if (m_progress) { + Leds::setRange((LedPos)(MIDDLE_POINT - (m_progress - 1)), (LedPos)(MIDDLE_POINT + (m_progress - 1)), m_colorset.peekNext()); } } void VortexWipePattern::poststep() { - m_progress = (m_progress + 1) % LED_COUNT; + // step till the middle point + m_progress = (m_progress + 1) % (MIDDLE_POINT + 1); + // each cycle progress to the next color if (m_progress == 0) { m_colorset.getNext(); } 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..a5cb0d80b3 100644 --- a/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp +++ b/VortexEngine/src/Patterns/Multi/ZigzagPattern.cpp @@ -8,17 +8,19 @@ // The lights runs across evens, then back across odds. // Index this array with m_step in order to get correct LedPos const LedPos ZigzagPattern::ledStepPositions[] = { - LED_1, + LED_0, LED_3, - LED_5, - LED_7, - LED_9, - - LED_8, - LED_6, + LED_1, LED_4, LED_2, + + LED_5, + LED_3, LED_0, + LED_4, + LED_1, + LED_5, + LED_2 }; // There just happens to be LED_COUNT steps in the pattern diff --git a/VortexEngine/src/Serial/Serial.cpp b/VortexEngine/src/Serial/Serial.cpp index 2ed5cf257a..efd96fc9a6 100644 --- a/VortexEngine/src/Serial/Serial.cpp +++ b/VortexEngine/src/Serial/Serial.cpp @@ -57,20 +57,21 @@ bool SerialComs::isConnectedReal() #endif unsigned long currentTime = Time::getCurtime(); - - if (currentState != lastState) { - if (!currentState) { - // Check if the state has been false for at least 1 millisecond - if ((currentTime - lastChangeTime) < 1) { - return lastState; // State hasn't been false long enough - } + 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 } - - // Update the last state and change time - lastChangeTime = currentTime; + if (currentState != lastState) { + // Update the last state and change time + lastChangeTime = currentTime; + lastState = currentState; + return lastState; + } + } else { lastState = currentState; + lastChangeTime = currentTime; } - return currentState; } @@ -177,6 +178,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 7ce393361f..1ae2fe9d06 100644 --- a/VortexEngine/src/Serial/Serial.h +++ b/VortexEngine/src/Serial/Serial.h @@ -32,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(); diff --git a/VortexEngine/src/VortexEngine.cpp b/VortexEngine/src/VortexEngine.cpp index 087931c7d9..1d366932a8 100644 --- a/VortexEngine/src/VortexEngine.cpp +++ b/VortexEngine/src/VortexEngine.cpp @@ -30,14 +30,14 @@ bool VortexEngine::m_autoCycle = false; bool VortexEngine::init() { // all of the global controllers - if (!SerialComs::init()) { - DEBUG_LOG("Serial failed to initialize"); - return false; - } if (!Time::init()) { DEBUG_LOG("Time failed to initialize"); return false; } + if (!SerialComs::init()) { + DEBUG_LOG("Serial failed to initialize"); + return false; + } if (!Storage::init()) { DEBUG_LOG("Storage failed to initialize"); return false; @@ -178,6 +178,13 @@ void VortexEngine::runMainLogic() return; } + // check for serial first before anything runs, but as a result if we open + // editor we have to call modes load inside here + if (!Menus::checkInMenu() && SerialComs::checkSerial()) { + // directly open the editor connection menu because we are connected to USB serial + Menus::openMenu(MENU_EDITOR_CONNECTION); + } + // if the menus are open and running then just return if (Menus::run()) { return; 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 00438aea9a..a2a310c6f3 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 0 +#define VL_ENABLE_RECEIVER 1 // the size of IR blocks in bits #define VL_DEFAULT_BLOCK_SIZE 256 @@ -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/tests/tests_general.tar.gz b/VortexEngine/tests/tests_general.tar.gz index 8faf2c0dbc..f981bb72fd 100644 Binary files a/VortexEngine/tests/tests_general.tar.gz and b/VortexEngine/tests/tests_general.tar.gz differ