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/Menus/Menu.h b/VortexEngine/src/Menus/Menu.h index de85897cf2..0b72ff246f 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 diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp index a3545489ee..251c84d834 100644 --- a/VortexEngine/src/Menus/MenuList/EditorConnection.cpp +++ b/VortexEngine/src/Menus/MenuList/EditorConnection.cpp @@ -7,7 +7,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" @@ -19,6 +21,7 @@ EditorConnection::EditorConnection(VortexEngine &engine, const RGBColor &col, bool advanced) : Menu(engine, col, advanced), m_state(STATE_DISCONNECTED), + m_timeOutStartTime(0), m_allowReset(true), m_previousModeIndex(0), m_numModesToReceive(0), @@ -80,7 +83,7 @@ void EditorConnection::onLongClick() void EditorConnection::leaveMenu(bool doSave) { - m_engine.serial().write(EDITOR_VERB_GOODBYE); + writeData(EDITOR_VERB_GOODBYE); Menu::leaveMenu(true); } @@ -111,6 +114,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 }, }; @@ -148,11 +152,8 @@ void EditorConnection::handleState() case STATE_DISCONNECTED: default: // not connected yet so check for connections - if (!m_engine.serial().isConnected()) { - if (!m_engine.serial().checkSerial()) { - // no connection found just continue waiting - break; - } + if (!detectConnection()) { + break; } // a connection was found, say hello m_state = STATE_GREETING; @@ -162,7 +163,7 @@ void EditorConnection::handleState() // Send Greeting case STATE_GREETING: // send the hello greeting with our version number and build time - m_engine.serial().write(EDITOR_VERB_GREETING); + writeData(EDITOR_VERB_GREETING); m_state = STATE_IDLE; break; @@ -174,7 +175,7 @@ void EditorConnection::handleState() // parse the receive buffer for any commands from the editor handleCommand(); // watch for disconnects - if (!m_engine.serial().isConnected()) { + if (!isConnected()) { m_engine.leds().holdAll(RGB_RED); leaveMenu(true); } @@ -197,7 +198,7 @@ void EditorConnection::handleState() break; case STATE_PULL_MODES_DONE: // send our acknowledgement that the modes were sent - m_engine.serial().write(EDITOR_VERB_PULL_MODES_ACK); + writeData(EDITOR_VERB_PULL_MODES_ACK); // go idle m_state = STATE_IDLE; break; @@ -206,7 +207,7 @@ void EditorConnection::handleState() // Receive Modes from PC case STATE_PUSH_MODES: // now say we are ready - m_engine.serial().write(EDITOR_VERB_READY); + writeData(EDITOR_VERB_READY); // move to receiving m_state = STATE_PUSH_MODES_RECEIVE; break; @@ -220,7 +221,7 @@ void EditorConnection::handleState() m_state = STATE_PUSH_MODES_DONE; break; case STATE_PUSH_MODES_DONE: - m_engine.serial().write(EDITOR_VERB_PUSH_MODES_ACK); + writeData(EDITOR_VERB_PUSH_MODES_ACK); m_state = STATE_IDLE; break; @@ -228,7 +229,7 @@ void EditorConnection::handleState() // Demo Mode from PC case STATE_DEMO_MODE: // now say we are ready - m_engine.serial().write(EDITOR_VERB_READY); + writeData(EDITOR_VERB_READY); // move to receiving m_state = STATE_DEMO_MODE_RECEIVE; break; @@ -243,7 +244,7 @@ void EditorConnection::handleState() break; case STATE_DEMO_MODE_DONE: // say we are done - m_engine.serial().write(EDITOR_VERB_DEMO_MODE_ACK); + writeData(EDITOR_VERB_DEMO_MODE_ACK); m_state = STATE_IDLE; break; @@ -251,7 +252,7 @@ void EditorConnection::handleState() // Reset Demo to Nothing case STATE_CLEAR_DEMO: clearDemo(); - m_engine.serial().write(EDITOR_VERB_CLEAR_DEMO_ACK); + writeData(EDITOR_VERB_CLEAR_DEMO_ACK); m_state = STATE_IDLE; break; @@ -260,25 +261,35 @@ void EditorConnection::handleState() case STATE_TRANSMIT_MODE_VL: #if VL_ENABLE_SENDER == 1 // immediately load the mode and send it now - m_engine.vlSender().loadMode(&m_previewMode); - m_engine.vlSender().send(); + m_engine.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 (m_engine.vlSender().isSending() && m_engine.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 + m_engine.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 - m_engine.serial().write(EDITOR_VERB_TRANSMIT_VL_ACK); m_state = STATE_IDLE; break; @@ -297,7 +308,10 @@ void EditorConnection::handleState() if (m_engine.modes().numModes() == 0) { m_state = STATE_PULL_EACH_MODE_DONE; } else { + // backup the old mode index so we can return to it m_previousModeIndex = m_engine.modes().curModeIndex(); + // switch to mode 0 to ensure we start at the beginning + m_engine.modes().setCurMode(0); m_state = STATE_PULL_EACH_MODE_SEND; } break; @@ -325,7 +339,7 @@ void EditorConnection::handleState() break; case STATE_PULL_EACH_MODE_DONE: // send our acknowledgement that the modes were sent - m_engine.serial().write(EDITOR_VERB_PULL_EACH_MODE_DONE); + writeData(EDITOR_VERB_PULL_EACH_MODE_DONE); // switch back to the previous mode m_engine.modes().setCurMode(m_previousModeIndex); // go idle @@ -337,7 +351,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 - m_engine.serial().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: @@ -348,7 +362,7 @@ void EditorConnection::handleState() // clear modes and start receiving m_engine.modes().clearModes(); // write out an ack - m_engine.serial().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; @@ -358,7 +372,7 @@ void EditorConnection::handleState() // just wait break; } - m_engine.serial().write(EDITOR_VERB_PUSH_EACH_MODE_ACK); + writeData(EDITOR_VERB_PUSH_EACH_MODE_ACK); if (m_numModesToReceive > 0) { m_numModesToReceive--; } @@ -378,7 +392,7 @@ void EditorConnection::handleState() // ------------------------------- // Set Global Brightness case STATE_SET_GLOBAL_BRIGHTNESS: - m_engine.serial().write(EDITOR_VERB_READY); + writeData(EDITOR_VERB_READY); m_state = STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE; break; case STATE_SET_GLOBAL_BRIGHTNESS_RECEIVE: @@ -419,22 +433,25 @@ void EditorConnection::showEditor() void EditorConnection::receiveData() { - // read more data into the receive buffer - m_engine.serial().read(m_receiveBuffer); + if (m_receiveBuffer.size() >= 512) { + return; + } + // Otherwise, read more from Serial + readData(m_receiveBuffer); } void EditorConnection::sendModes() { ByteStream modesBuffer; m_engine.modes().saveToBuffer(modesBuffer); - m_engine.serial().write(modesBuffer); + writeData(modesBuffer); } void EditorConnection::sendModeCount() { ByteStream buffer; buffer.serialize8(m_engine.modes().numModes()); - m_engine.serial().write(buffer); + writeData(buffer); } void EditorConnection::sendCurMode() @@ -449,7 +466,7 @@ void EditorConnection::sendCurMode() // ?? return; } - m_engine.serial().write(modeBuffer); + writeData(modeBuffer); } void EditorConnection::sendCurModeVL() @@ -459,13 +476,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(m_engine.leds().getBrightness())) { return RV_FAIL; } - m_engine.serial().write(brightnessBuf); + writeData(brightnessBuf); return RV_OK; } @@ -572,7 +596,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; @@ -580,10 +603,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; @@ -614,3 +635,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 (m_engine.vlReceiver().onNewData()) { + m_timeOutStartTime = m_engine.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) < m_engine.time().getCurtime()) { + m_engine.vlReceiver().resetVLState(); + m_timeOutStartTime = 0; + return RV_WAIT; + } + // check if the VLReceiver has a full packet available + if (!m_engine.vlReceiver().dataReady()) { + // nothing available yet + return RV_WAIT; + } + DEBUG_LOG("Mode ready to receive! Receiving..."); + // receive the VL mode into the current mode + if (!m_engine.vlReceiver().receiveMode(&m_previewMode)) { + ERROR_LOG("Failed to receive mode"); + return RV_FAIL; + } + DEBUG_LOGF("Success receiving mode: %u", m_previewMode.getPatternID()); + if (!m_engine.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 (m_engine.vlReceiver().isReceiving()) { + // using uint32_t to avoid overflow, the result should be within 10 to 255 + //m_engine.leds().setAll(RGBColor(0, m_engine.vlReceiver().percentReceived(), 0)); + m_engine.leds().setRange(LED_0, (LedPos)(((uint32_t)m_engine.vlReceiver().percentReceived() * LED_COUNT) / 100), RGB_GREEN6); + } else { + m_engine.leds().setAll(RGB_WHITE0); + } +#endif +} + +bool EditorConnection::detectConnection() +{ + if (m_engine.serial().isConnected() || m_engine.serial().checkSerial()) { + return true; + } + return false; +} + +bool EditorConnection::isConnected() +{ + return m_engine.serial().isConnected(); +} + +void EditorConnection::readData(ByteStream &buffer) +{ + m_engine.serial().read(buffer); +} + +void EditorConnection::writeData(ByteStream &buffer) +{ + m_engine.serial().write(buffer); +} + +void EditorConnection::writeData(const char *message) +{ + m_engine.serial().write(message); +} diff --git a/VortexEngine/src/Menus/MenuList/EditorConnection.h b/VortexEngine/src/Menus/MenuList/EditorConnection.h index b4bd87dd6e..e6b1a4e5f3 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 b693c0e60a..616c2bf102 100644 --- a/VortexEngine/src/Menus/MenuList/ModeSharing.cpp +++ b/VortexEngine/src/Menus/MenuList/ModeSharing.cpp @@ -94,19 +94,10 @@ void ModeSharing::onLongClick() void ModeSharing::beginSendingVL() { - // if the sender is sending then cannot start again - if (m_engine.vlSender().isSending()) { - ERROR_LOG("Cannot begin sending, sender is busy"); - return; - } - m_sharingMode = ModeShareState::SHARE_SEND_VL; - // initialize it with the current mode data - m_engine.vlSender().loadMode(m_engine.modes().curMode()); // send the first chunk of data, leave if we're done - if (!m_engine.vlSender().send()) { - // when send has completed, stores time that last action was completed to calculate interval between sends - beginReceivingIR(); - } + m_engine.vlSender().send(m_engine.modes().curMode()); + // when send has completed, stores time that last action was completed to calculate interval between sends + beginReceivingIR(); } void ModeSharing::beginSendingIR() @@ -128,14 +119,10 @@ void ModeSharing::beginSendingIR() void ModeSharing::continueSendingVL() { - // if the sender isn't sending then nothing to do - if (!m_engine.vlSender().isSending()) { - return; - } - if (!m_engine.vlSender().send()) { - // when send has completed, stores time that last action was completed to calculate interval between sends - beginReceivingIR(); - } + // send the first chunk of data, leave if we're done + m_engine.vlSender().send(m_engine.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/Modes/DefaultModes.cpp b/VortexEngine/src/Modes/DefaultModes.cpp index 722a2582a5..c54fb8da25 100644 --- a/VortexEngine/src/Modes/DefaultModes.cpp +++ b/VortexEngine/src/Modes/DefaultModes.cpp @@ -18,3 +18,8 @@ const default_mode_entry default_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 || 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 e6d581252e..6122c3f794 100644 --- a/VortexEngine/src/Modes/Mode.cpp +++ b/VortexEngine/src/Modes/Mode.cpp @@ -597,6 +597,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] = m_engine.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 7d3166680b..0961e450a5 100644 --- a/VortexEngine/src/Modes/Mode.h +++ b/VortexEngine/src/Modes/Mode.h @@ -90,6 +90,12 @@ class Mode bool setPattern(PatternID pat, LedPos pos, 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); // set colorset at each position in a map diff --git a/VortexEngine/src/Modes/Modes.h b/VortexEngine/src/Modes/Modes.h index 8d858028b9..6c3968c323 100644 --- a/VortexEngine/src/Modes/Modes.h +++ b/VortexEngine/src/Modes/Modes.h @@ -130,7 +130,8 @@ class Modes // set or get flags bool setFlag(uint8_t flag, bool enable, bool save = true); - bool getFlag(uint8_t flag) { return ((m_globalFlags & flag) != 0); } + bool getFlag(uint8_t flag) { return ((m_globalFlags & flag) == flag); } + // reset flags to factory default (must save after) 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 4d17de016f..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); @@ -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 7d0c3d4ee1..95f5e61ba5 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. @@ -319,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 @@ -330,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 0 + // Variable Tickrate // // This controls whether the setTickrate function is available and @@ -541,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 // @@ -597,6 +643,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 diff --git a/VortexEngine/src/VortexEngine.cpp b/VortexEngine/src/VortexEngine.cpp index 48f4901bb5..0eafc231e0 100644 --- a/VortexEngine/src/VortexEngine.cpp +++ b/VortexEngine/src/VortexEngine.cpp @@ -32,12 +32,12 @@ VortexEngine::~VortexEngine() bool VortexEngine::init() { // all of the global controllers - if (!m_serial.init()) { - DEBUG_LOG("Serial failed to initialize"); + if (!m_time.init()) { + // time must init first but can't log failures till serialcomms inits return false; } - if (!m_time.init()) { - DEBUG_LOG("Time failed to initialize"); + if (!m_serial.init()) { + DEBUG_LOG("Serial failed to initialize"); return false; } if (!m_storage.init()) { 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/IRReceiver.cpp b/VortexEngine/src/Wireless/IRReceiver.cpp index decb4d9196..0db6fe9f7b 100644 --- a/VortexEngine/src/Wireless/IRReceiver.cpp +++ b/VortexEngine/src/Wireless/IRReceiver.cpp @@ -188,7 +188,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: diff --git a/VortexEngine/src/Wireless/IRSender.h b/VortexEngine/src/Wireless/IRSender.h index 691e46e8b0..1fb3fe67dd 100644 --- a/VortexEngine/src/Wireless/IRSender.h +++ b/VortexEngine/src/Wireless/IRSender.h @@ -25,6 +25,7 @@ class IRSender bool send(); bool isSending() { return m_isSending; } + void stopSending() { m_isSending = false; } uint32_t percentDone() { return (uint32_t)(((float)m_writeCounter / (float)m_size) * 100.0); } diff --git a/VortexEngine/src/Wireless/VLConfig.h b/VortexEngine/src/Wireless/VLConfig.h index 2ee8490d67..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 1 - // the size of IR blocks in bits #define VL_DEFAULT_BLOCK_SIZE 256 #define VL_DEFAULT_BLOCK_SPACING MS_TO_TICKS(5) @@ -25,21 +18,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) + +// 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 ((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 ((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 (uint32_t)(VL_TIMING * 16) -#define VL_HEADER_SPACE (uint32_t)(VL_TIMING * 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_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_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_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_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 aa24f3b9f5..27e2e387a1 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 @@ -18,7 +18,12 @@ VLReceiver::VLReceiver(VortexEngine &engine) : m_recvState(WAITING_HEADER_MARK), m_prevTime(0), m_pinState(0), - m_previousBytes(0) + m_previousBytes(0), + m_vlMarkThreshold(0), + m_vlSpaceThreshold(0), + m_counter(0), + m_parityBit(0), + m_legacy(false) { } @@ -41,17 +46,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 @@ -70,11 +67,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) @@ -118,16 +113,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"); @@ -138,61 +124,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 = m_engine.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: // ?? @@ -203,10 +276,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 add0f5f501..4fe033b669 100644 --- a/VortexEngine/src/Wireless/VLReceiver.h +++ b/VortexEngine/src/Wireless/VLReceiver.h @@ -42,14 +42,19 @@ class VLReceiver // reset VL receiver buffer void resetVLState(); - void recvPCIHandler(); + // turn on/off the legacy receiver + 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 + void recvPCIHandler(); private: - // reading functions // PCI handler for when VL receiver pin changes states bool read(ByteStream &data); - void handleVLTiming(uint32_t diff); + void handleVLTiming(uint16_t diff); + void handleVLTimingLegacy(uint16_t diff); // =================== // private data: @@ -65,8 +70,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 @@ -78,6 +87,19 @@ class VLReceiver // used to compare if received data has changed since last checking uint32_t m_previousBytes; + // the determined time based on sync + uint16_t m_vlMarkThreshold; + // the determined time based on sync + uint16_t m_vlSpaceThreshold; + + // count of the sync bits (similar length starter bits) + uint8_t m_counter; + + uint8_t m_parityBit; + + // legacy mode + bool m_legacy; + #ifdef VORTEX_LIB friend class Vortex; #endif diff --git a/VortexEngine/src/Wireless/VLSender.cpp b/VortexEngine/src/Wireless/VLSender.cpp index aa55a28ebe..59599e3a75 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,13 +15,8 @@ VLSender::VLSender(VortexEngine &engine) : m_engine(engine), m_serialBuf(), m_bitStream(), - m_isSending(false), - m_lastSendTime(0), m_size(0), - m_numBlocks(0), - m_remainder(0), - m_blockSize(0), - m_writeCounter(0) + m_parity(0) { } @@ -59,110 +53,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 > m_engine.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 = m_engine.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)", - m_engine.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 - m_engine.vortexLib().vcallbacks()->infraredWrite(true, time); -#else - startPWM(); - m_engine.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 + m_engine.vortexLib().vcallbacks()->infraredWrite(true, markTime); // send space timing over socket - m_engine.vortexLib().vcallbacks()->infraredWrite(false, time); + m_engine.vortexLib().vcallbacks()->infraredWrite(false, spaceTime); #else + startPWM(); + m_engine.time().delayMicroseconds(markTime); stopPWM(); - m_engine.time().delayMicroseconds(time); + m_engine.time().delayMicroseconds(spaceTime); #endif } diff --git a/VortexEngine/src/Wireless/VLSender.h b/VortexEngine/src/Wireless/VLSender.h index 707d6dce64..aa78344867 100644 --- a/VortexEngine/src/Wireless/VLSender.h +++ b/VortexEngine/src/Wireless/VLSender.h @@ -20,24 +20,18 @@ class VLSender bool init(); void cleanup(); - // initialize the VL sender with a mode to send - bool loadMode(const Mode *targetMode); - bool send(); - - bool isSending() { return m_isSending; } - - uint32_t percentDone() { return (uint32_t)(((float)m_writeCounter / (float)m_size) * 100.0); } + // send a mode + void send(const Mode *targetMode); + void sendLegacy(const Mode *targetMode); private: - // sender functions - void beginSend(); + bool loadMode(const Mode *targetMode); // send a full 8 bits in a tight loop void sendByte(uint8_t data); + // send full 8 bits legacy protocol + void sendByteLegacy(uint8_t data); // send a mark/space by turning PWM on/off - void sendMark(uint16_t time); - void sendSpace(uint16_t time); - // Pulse-Width Modulator (VL Transmitter) - void initPWM(); + void sendMarkSpace(uint16_t markTime, uint16_t spaceTime); // turn the VL transmitter on/off in realtime void startPWM(); void stopPWM(); @@ -49,21 +43,10 @@ class VLSender ByteStream m_serialBuf; // a bit walker for the serial data BitStream m_bitStream; - bool m_isSending; - uint32_t m_lastSendTime; // some runtime meta info - uint32_t m_size; - // the number of blocks that will be sent - uint8_t m_numBlocks; - // the amount in the final block - uint8_t m_remainder; - - // configuration options for the sender - uint32_t m_blockSize; - - // write total - uint32_t m_writeCounter; + uint8_t m_size; + uint8_t m_parity; }; #endif