diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..de2ed94 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,65 @@ +FROM rust:1.84 as builder + +RUN git clone https://github.com/vexide/vex-v5-qemu.git /vex-v5-qemu + +WORKDIR /vex-v5-qemu +RUN cd packages/kernel; cargo build --target-dir /target/kernel + +# FIXME: This should probably be removed in the future since it's very fragile +RUN rustup toolchain install nightly + +RUN cd packages/client-cli; cargo +nightly install --path . --root /target/client-cli + +FROM mcr.microsoft.com/devcontainers/cpp:1-noble +# ------------ +# Install Required Packages +# ------------ +RUN sudo apt-get update +COPY ./.devcontainer/packagelist /packagelist +RUN sudo apt-get -y install $(cat /packagelist | sed 's/\r//') +RUN rm /packagelist # Cleanup image +RUN sudo apt-get clean # Cleanup image + +# ------------ +# Install Clangd +# ------------ +RUN curl -sLo clangd.zip $( \ + curl -s https://api.github.com/repos/clangd/clangd/releases/latest \ + | jq -r '[.assets[] | select(.name | test ("^clangd-linux"))][0].browser_download_url' \ + ) \ + && unzip clangd.zip -d /usr/local/share \ + && mv /usr/local/share/clangd_*/ /usr/local/share/clangd \ + && rm clangd.zip + +ENV PATH="$PATH:/usr/local/share/clangd/bin" + +# ------------ +# Install PROS CLI +# ------------ +RUN pip install --break-system-packages pros-cli + +# ------------ +# Install ARM Toolchain +# ------------ +COPY --from=ghcr.io/lemlib/pros-build:v2.0.2 /gcc-arm-none-eabi-10.3-2021.10 /usr/local/share/arm-none-eabi +ENV PATH="$PATH:/usr/local/share/arm-none-eabi/bin" + +# Copy the simulator binary +COPY --from=builder /target/client-cli/bin/client-cli /usr/local/bin/simulator + +# Clone pros kernel source so we can reference it when debugging +COPY ./project.pros /project.pros +ENV PROS_SOURCE_PATH="$HOME/.pros" +RUN git clone https://github.com/purduesigbots/pros.git $PROS_SOURCE_PATH \ + --depth 1 \ + --branch $(> ~/.bashrc -popd diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 29121bb..213e552 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -11,11 +11,15 @@ on: jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + path: + - './src/gamepad' + - './include/gamepad' steps: - uses: actions/checkout@v2 - - uses: DoozyX/clang-format-lint-action@v0.18 + - uses: jidicula/clang-format-action@v4.14.0 with: - source: './src/gamepad ./include/gamepad' - extensions: 'hpp,cpp' - clangFormatVersion: 18 + clang-format-version: '19' + check-path: ${{ matrix.path }} diff --git a/.github/workflows/pros-build-release.yml b/.github/workflows/pros-build-release.yml new file mode 100644 index 0000000..301a532 --- /dev/null +++ b/.github/workflows/pros-build-release.yml @@ -0,0 +1,24 @@ +name: Build Release + +on: + release: + types: [published] + +jobs: + build-release: + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + - name: Build Template + id: build_step + uses: LemLib/pros-build@v2.0.2 + with: + lib_folder_name: "gamepad" + copy_readme_and_license_to_include: true + no_commit_hash: true + - name: Upload Template To Release + uses: svenstaro/upload-release-action@v2 + with: + file: ${{ github.workspace }}/${{ steps.build_step.outputs.name }} diff --git a/.github/workflows/pros-build.yml b/.github/workflows/pros-build.yml index 88566b5..d34f97d 100644 --- a/.github/workflows/pros-build.yml +++ b/.github/workflows/pros-build.yml @@ -12,7 +12,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: LemLib/pros-build@v2.0.2 + - name: Checkout Code + uses: actions/checkout@v4 + - name: Build Template + id: build_step + uses: LemLib/pros-build@v2.0.2 with: - library-path: gamepad + lib_folder_name: "gamepad" + copy_readme_and_license_to_include: true + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.build_step.outputs.name }} + path: ${{ github.workspace }}/template/* diff --git a/.gitignore b/.gitignore index 37b2094..c64778c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ # PROS bin/ -.vscode/ +.vscode/* .cache/ compile_commands.json temp.log @@ -21,3 +21,9 @@ temp.errors # MacOS .DS_Store + +# Linux +debug.log + +# Always include development settings +!.vscode/settings.json \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0120171 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "main" + ], + "conventionalCommits.scopes": [ + "buttons", + "display", + "joysticks" + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index b996582..5f38925 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,8 @@ +All files in this repository are licensed under the MIT license as follows, +unless otherwise noted in another LICENSE file + +===== BEGIN LICENSE ===== + MIT License Copyright (c) 2024 Jamie Maki-Fern, Ayaan (ion098), and other contributors diff --git a/Makefile b/Makefile index d97a45b..d06743f 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ EXCLUDE_COLD_LIBRARIES:= # Set this to 1 to add additional rules to compile your project as a PROS library template IS_LIBRARY:=1 LIBNAME:=gamepad -VERSION:=0.2.0 +VERSION:=0.3.0 # EXCLUDE_SRC_FROM_LIB= $(SRCDIR)/unpublishedfile.c # this line excludes opcontrol.c and similar files EXCLUDE_SRC_FROM_LIB+=$(foreach file, $(SRCDIR)/main,$(foreach cext,$(CEXTS),$(file).$(cext)) $(foreach cxxext,$(CXXEXTS),$(file).$(cxxext))) diff --git a/include/gamepad/api.hpp b/include/gamepad/api.hpp index 835385a..e0fca39 100644 --- a/include/gamepad/api.hpp +++ b/include/gamepad/api.hpp @@ -1,4 +1,5 @@ #pragma once #include "gamepad/event_handler.hpp" // IWYU pragma: export -#include "gamepad/controller.hpp" // IWYU pragma: export \ No newline at end of file +#include "gamepad/gamepad.hpp" // IWYU pragma: export +#include "gamepad/screens/alertScreen.hpp" // IWYU pragma: export diff --git a/include/gamepad/button.hpp b/include/gamepad/button.hpp index b867da7..1c651ea 100644 --- a/include/gamepad/button.hpp +++ b/include/gamepad/button.hpp @@ -42,14 +42,14 @@ class Button { * @b Example: * @code {.cpp} * // change the threshold - * gamepad::master.Left.set_long_press_threshold(5000); + * gamepad::master.Left.setLongPressThreshold(5000); * // then call the function * gamepad::master.Left.onLongPress("longPress1", []() { * std::cout << "I was held for 5000ms instead of the 500ms default!" << std::endl; * }); * @endcode */ - void set_long_press_threshold(uint32_t threshold) const; + void setLongPressThreshold(uint32_t threshold) const; /** * @brief Set the interval for the repeatPress event to repeat * @@ -60,21 +60,21 @@ class Button { * @b Example: * @code {.cpp} * // change the threshold - * gamepad::master.Up.set_repeat_cooldown(100); + * gamepad::master.Up.setRepeatCooldown(100); * // then call the function * gamepad::master.Up.onRepeatPress("repeatPress1", []() { * std::cout << "I'm being repeated every 100ms instead of the 50ms default!" << std::endl; * }); * @endcode */ - void set_repeat_cooldown(uint32_t cooldown) const; + void setRepeatCooldown(uint32_t cooldown) const; /** * @brief Register a function to run when the button is pressed. * * @param listenerName The name of the listener, this must be a unique name * @param func The function to run when the button is pressed, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) + * @return 0 The listener was successfully registered + * @return INT32_MAX The listener was not successfully registered (there is already a listener with this name) * * @b Example: * @code {.cpp} @@ -84,20 +84,20 @@ class Button { * gamepad::master.Up.onPress("upPress1", []() { std::cout << "I was pressed!" << std::endl; }); * @endcode */ - bool onPress(std::string listenerName, std::function func) const; + int32_t onPress(std::string listenerName, std::function func) const; /** * @brief Register a function to run when the button is long pressed. * * By default, onLongPress will fire when the button has been held down for - * 500ms or more, this threshold can be adjusted via the set_long_press_threshold() method. + * 500ms or more, this threshold can be adjusted via the setLongPressThreshold() method. * * @warning When using this event along with onPress, both the onPress - * and onlongPress listeners may fire together. + * and onLongPress listeners may fire together. * * @param listenerName The name of the listener, this must be a unique name * @param func The function to run when the button is long pressed, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) + * @return 0 The listener was successfully registered + * @return INT32_MAX The listener was not successfully registered (there is already a listener with this name) * * @b Example: * @code {.cpp} @@ -108,14 +108,14 @@ class Button { * std::endl; }); * @endcode */ - bool onLongPress(std::string listenerName, std::function func) const; + int32_t onLongPress(std::string listenerName, std::function func) const; /** * @brief Register a function to run when the button is released. * * @param listenerName The name of the listener, this must be a unique name * @param func The function to run when the button is released, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) + * @return 0 The listener was successfully registered + * @return INT32_MAX The listener was not successfully registered (there is already a listener with this name) * * @b Example: * @code {.cpp} @@ -125,19 +125,19 @@ class Button { * gamepad::master.Y.onRelease("stopIntake", []() { intake.move(0); }); * @endcode */ - bool onRelease(std::string listenerName, std::function func) const; + int32_t onRelease(std::string listenerName, std::function func) const; /** * @brief Register a function to run when the button is short released. * * By default, shortRelease will fire when the button has been released before 500ms, this threshold can be - * adjusted via the set_long_press_threshold() method. + * adjusted via the setLongPressThreshold() method. * * @note This event will most likely be used along with the longPress event. * * @param listenerName The name of the listener, this must be a unique name * @param func The function to run when the button is short released, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) + * @return 0 The listener was successfully registered + * @return INT32_MAX The listener was not successfully registered (there is already a listener with this name) * * @b Example: * @code {.cpp} @@ -147,17 +147,17 @@ class Button { * gamepad::master.B.onShortRelease("intakeOnePiece", []() { intake.move_relative(600, 100); }); * @endcode */ - bool onShortRelease(std::string listenerName, std::function func) const; + int32_t onShortRelease(std::string listenerName, std::function func) const; /** * @brief Register a function to run when the button is long released. * * By default, longRelease will fire when the button has been released after 500ms, this threshold can be - * adjusted via the set_long_press_threshold() method. + * adjusted via the setLongPressThreshold() method. * * @param listenerName The name of the listener, this must be a unique name * @param func The function to run when the button is long released, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) + * @return 0 The listener was successfully registered + * @return INT32_MAX The listener was not successfully registered (there is already a listener with this name) * * @b Example: * @code {.cpp} @@ -168,17 +168,17 @@ class Button { * @endcode * */ - bool onLongRelease(std::string listenerName, std::function func) const; + int32_t onLongRelease(std::string listenerName, std::function func) const; /** * @brief Register a function to run periodically after its been held * * By default repeatPress will start repeating after 500ms and repeat every 50ms, this can be adjusted via the - * set_long_press_threshold() and set_repeat_cooldown() methods respectively + * setLongPressThreshold() and setRepeatCooldown() methods respectively * * @param listenerName The name of the listener, this must be a unique name * @param func the function to run periodically when the button is held, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) + * @return 0 The listener was successfully registered + * @return INT32_MAX The listener was not successfully registered (there is already a listener with this name) * * @b Example: * @code {.cpp} @@ -189,15 +189,15 @@ class Button { * @endcode * */ - bool onRepeatPress(std::string listenerName, std::function func) const; + int32_t onRepeatPress(std::string listenerName, std::function func) const; /** * @brief Register a function to run for a given event. * * @param event Which event to register the listener on. * @param listenerName The name of the listener, this must be a unique name * @param func The function to run for the given event, the function MUST NOT block - * @return true The listener was successfully registered - * @return false The listener was not successfully registered (there is already a listener with this name) + * @return 0 The listener was successfully registered + * @return INT32_MAX The listener was not successfully registered (there is already a listener with this name) * * @b Example: * @code {.cpp} @@ -207,24 +207,25 @@ class Button { * gamepad::master.L1.addListener(gamepad::ON_RELEASE, "stop_spin", []() { motor1.brake(); }); * @endcode */ - bool addListener(EventType event, std::string listenerName, std::function func) const; + int32_t addListener(EventType event, std::string listenerName, std::function func) const; /** * @brief Removes a listener from the button * @warning Usage of this function is discouraged. * + * @param event the event type of the listener * @param listenerName The name of the listener to remove - * @return true The specified listener was successfully removed - * @return false The specified listener could not be removed + * @return 0 The specified listener was successfully removed + * @return INT32_MAX The specified listener could not be removed * * @b Example: * @code {.cpp} * // Add an event listener... * gamepad::master.L1.addListener(gamepad::ON_PRESS, "do_something", doSomething); * // ...and now get rid of it - * gamepad::master.L1.removeListener("do_something"); + * gamepad::master.L1.removeListener(gamepad::ON_PRESS, "do_something"); * @endcode */ - bool removeListener(std::string listenerName) const; + int32_t removeListener(EventType event, std::string listenerName) const; /** * @brief Returns a value indicating whether the button is currently being held. @@ -240,21 +241,29 @@ class Button { * @param is_held Whether or not the button is currently held down */ void update(bool is_held); + /** + * @brief Get the handler object for the given event type + * + * @param event The desired event type + * @return nullptr The event value is invalid + * @return _impl::EventHandler* A pointer to the given event's handler + */ + _impl::EventHandler* get_handler(EventType event) const; /// How long the threshold should be for the longPress and shortRelease events - mutable uint32_t long_press_threshold = 500; + mutable uint32_t m_long_press_threshold = 500; /// How often repeatPress is called - mutable uint32_t repeat_cooldown = 50; + mutable uint32_t m_repeat_cooldown = 50; /// The last time the update function was called - uint32_t last_update_time = pros::millis(); + uint32_t m_last_update_time = pros::millis(); /// The last time the long press event was fired - uint32_t last_long_press_time = 0; + uint32_t m_last_long_press_time = 0; /// The last time the repeat event was called - uint32_t last_repeat_time = 0; - mutable _impl::EventHandler onPressEvent {}; - mutable _impl::EventHandler onLongPressEvent {}; - mutable _impl::EventHandler onReleaseEvent {}; - mutable _impl::EventHandler onShortReleaseEvent {}; - mutable _impl::EventHandler onLongReleaseEvent {}; - mutable _impl::EventHandler onRepeatPressEvent {}; + uint32_t m_last_repeat_time = 0; + mutable _impl::EventHandler m_on_press_event {}; + mutable _impl::EventHandler m_on_long_press_event {}; + mutable _impl::EventHandler m_on_release_event {}; + mutable _impl::EventHandler m_on_short_release_event {}; + mutable _impl::EventHandler m_on_long_release_event {}; + mutable _impl::EventHandler m_on_repeat_press_event {}; }; } // namespace gamepad \ No newline at end of file diff --git a/include/gamepad/controller.hpp b/include/gamepad/controller.hpp deleted file mode 100644 index cd1aebc..0000000 --- a/include/gamepad/controller.hpp +++ /dev/null @@ -1,105 +0,0 @@ -#pragma once - -#include "pros/misc.h" -#include - -#include "button.hpp" -#include "pros/misc.hpp" - -namespace gamepad { -class Gamepad { - public: - /** - * @brief Updates the state of the gamepad (all joysticks and buttons), and also runs - * any registered listeners. - * - * @note This function should be called at the beginning of every loop iteration. - * - * @b Example: - * @code {.cpp} - * while (true) { - * gamepad::master.update(); - * // do robot control stuff here... - * pros::delay(25); - * } - * @endcode - * - */ - void update(); - /** - * @brief Get the state of a button on the controller. - * - * @param button Which button to return - * - * @b Example: - * @code {.cpp} - * if(gamepad::master[DIGITAL_L1]) { - * // do something here... - * } - * @endcode - * - */ - const Button& operator[](pros::controller_digital_e_t button); - /** - * @brief Get the value of a joystick axis on the controller. - * - * @param joystick Which joystick axis to return - * - * @b Example: - * @code {.cpp} - * // control a motor with a joystick - * intake.move(gamepad::master[ANALOG_RIGHT_Y]); - * @endcode - * - */ - float operator[](pros::controller_analog_e_t joystick); - const Button& L1 {m_L1}; - const Button& L2 {m_L2}; - const Button& R1 {m_R1}; - const Button& R2 {m_R2}; - const Button& Up {m_Up}; - const Button& Down {m_Down}; - const Button& Left {m_Left}; - const Button& Right {m_Right}; - const Button& X {m_X}; - const Button& B {m_B}; - const Button& Y {m_Y}; - const Button& A {m_A}; - const float& LeftX = m_LeftX; - const float& LeftY = m_LeftY; - const float& RightX = m_RightX; - const float& RightY = m_RightY; - /// The master controller, same as @ref gamepad::master - static Gamepad master; - /// The partner controller, same as @ref gamepad::partner - static Gamepad partner; - private: - Gamepad(pros::controller_id_e_t id) - : controller(id) {} - - Button m_L1 {}, m_L2 {}, m_R1 {}, m_R2 {}, m_Up {}, m_Down {}, m_Left {}, m_Right {}, m_X {}, m_B {}, m_Y {}, - m_A {}; - float m_LeftX = 0, m_LeftY = 0, m_RightX = 0, m_RightY = 0; - Button Fake {}; - /** - * @brief Gets a unique name for a listener that will not conflict with user listener names. - * - * @important: when using the function, you must register the listener by - * directly calling add_listener on the EventHandler, do NOT use onPress/addListener,etc. - * - * @return std::string A unique listener name - */ - static std::string unique_name(); - static Button Gamepad::*button_to_ptr(pros::controller_digital_e_t button); - void updateButton(pros::controller_digital_e_t button_id); - pros::Controller controller; -}; - -inline Gamepad Gamepad::master {pros::E_CONTROLLER_MASTER}; -inline Gamepad Gamepad::partner {pros::E_CONTROLLER_PARTNER}; -/// The master controller -inline Gamepad& master = Gamepad::master; -/// The partner controller -inline Gamepad& partner = Gamepad::partner; - -} // namespace gamepad diff --git a/include/gamepad/event_handler.hpp b/include/gamepad/event_handler.hpp index bf51b20..c9f3d47 100644 --- a/include/gamepad/event_handler.hpp +++ b/include/gamepad/event_handler.hpp @@ -24,44 +24,44 @@ template class EventHandler { * * @param key The listener key (this must be a unique key value) * @param func The function to run when this event is fired - * @return true The listener was successfully added - * @return false The listener was NOT successfully added (there is already a listener with the same key) + * @return 0 The listener was successfully added + * @return INT32_MAX The listener was NOT successfully added (there is already a listener with the same key) */ - bool add_listener(Key key, Listener func) { - std::lock_guard lock(mutex); - if (std::find(keys.begin(), keys.end(), key) != keys.end()) return false; - keys.push_back(key); - listeners.push_back(func); - return true; + int32_t addListener(Key key, Listener func) { + std::lock_guard lock(m_mutex); + if (std::find(m_keys.begin(), m_keys.end(), key) != m_keys.end()) return INT32_MAX; + m_keys.push_back(key); + m_listeners.push_back(func); + return 0; } /** * @brief Remove a listener from the list of listeners * * @param key The listener key (this must be a unique key value) - * @return true The listener was successfully removed - * @return false The listener was NOT successfully removed (there is no listener with the same key) + * @return 0 The listener was successfully removed + * @return INT32_MAX The listener was NOT successfully removed (there is no listener with the same key) */ - bool remove_listener(Key key) { - std::lock_guard lock(mutex); - auto i = std::find(keys.begin(), keys.end(), key); - if (i != keys.end()) { - keys.erase(i); - listeners.erase(listeners.begin() + (i - keys.begin())); - return true; + int32_t removeListener(Key key) { + std::lock_guard lock(m_mutex); + auto i = std::find(m_keys.begin(), m_keys.end(), key); + if (i != m_keys.end()) { + m_keys.erase(i); + m_listeners.erase(m_listeners.begin() + (i - m_keys.begin())); + return 0; } - return false; + return INT32_MAX; } /** - * @brief Whther or not there are any listeners registered + * @brief Whether or not there are any listeners registered * * @return true There are listeners registered * @return false There are no listeners registered */ - bool is_empty() { - std::lock_guard lock(mutex); - return listeners.empty(); + bool isEmpty() { + std::lock_guard lock(m_mutex); + return m_listeners.empty(); } /** @@ -70,12 +70,12 @@ template class EventHandler { * @param args The parameters to pass to each listener */ void fire(Args... args) { - std::lock_guard lock(mutex); - for (auto listener : listeners) { listener(args...); } + std::lock_guard lock(m_mutex); + for (auto listener : m_listeners) { listener(args...); } } private: - std::vector keys {}; - std::vector listeners {}; - gamepad::_impl::RecursiveMutex mutex {}; + std::vector m_keys {}; + std::vector m_listeners {}; + gamepad::_impl::RecursiveMutex m_mutex {}; }; } // namespace gamepad::_impl diff --git a/include/gamepad/gamepad.hpp b/include/gamepad/gamepad.hpp new file mode 100644 index 0000000..e9e8a80 --- /dev/null +++ b/include/gamepad/gamepad.hpp @@ -0,0 +1,275 @@ +#pragma once + +#include "joystick_transformation.hpp" +#include "pros/misc.h" +#include "screens/defaultScreen.hpp" +#include +#include +#include +#include +#include "screens/abstractScreen.hpp" +#include "button.hpp" +#include "pros/misc.hpp" + +namespace gamepad { +class Gamepad { + public: + /** + * @brief Updates the state of the gamepad (all joysticks and buttons), and also runs + * any registered listeners. + * + * @note This function should be called at the beginning of every loop iteration. + * + * @b Example: + * @code {.cpp} + * while (true) { + * gamepad::master.update(); + * // do robot control stuff here... + * pros::delay(25); + * } + * @endcode + * + */ + void update(); + /** + * @brief Add a screen to the screen update loop that can update the controller's screen + * + * @param screen the `AbstractScreen` to add to the screen queue + * + * @b Example: + * @code {.cpp} + * // initialize the alerts screen so we can have alerts on the controller + * std::shared_ptr alerts = std::make_shared(); + * + * gamepad::master.add_screen(alerts); + */ + void addScreen(std::shared_ptr screen); + /** + * @brief print a line to the console like pros (low priority) + * + * @param line the line number to print the string on (0-2) + * @param str the string to print onto the controller (\n to go to the next line) + * + * This function uses the following value(s) of errno when an error state is reached: + * + * EINVAL: The line number is not in the interval [0, 2] + * EMSGSIZE: The string is more than 3 lines long + * + * @b Example: + * @code {.cpp} + * gamepad::master.printLine(1, "This will print on the middle line"); + * gamepad::master.printLine(0, "this will print\n\naround the middle line"); + * @endcode + * + * @return 0 if the line was printed successfully + * @return INT32_MAX if there was an error, setting errno + */ + int32_t printLine(uint8_t line, std::string str); + /** + * @brief clears all lines on the controller, similar to the pros function (low priority) + * + * @b Example: + * @code {.cpp} + * // clears the whole screen on the controller + * gamepad::master.clear() + * @endcode + */ + void clear(); + /** + * @brief clears the specific line on the controller, similar to the pros function clear_line (low priority) + * + * @param line the line to clear (0-2) + * + * This function uses the following value(s) of errno when an error state is reached: + * + * EINVAL: The line number is not in the interval [0, 2] + * + * @b Example: + * @code {.cpp} + * // clears the center line on the controller + * gamepad::master.clear(1); + * @endcode + * + * @return 0 if the line was cleared successfully + * @return INT32_MAX if there was an error, setting errno + */ + int32_t clear(uint8_t line); + /** + * makes the controller rumble like pros (low priority) + * + * @param rumble_pattern A string consisting of the characters '.', '-', and ' ', where dots are short rumbles, + * dashes are long rumbles, and spaces are pauses. Maximum supported length is 8 characters. + * + * This function uses the following value(s) of errno when an error state is reached: + * + * EINVAL: The rumble pattern contains a character other than '.', '-', or ' ' + * EMSGSIZE: The pattern is more than 8 characters long + * + * @b Example: + * @code {.cpp} + * // rumbles in the following pattern: short, pause, long, short short + * gamepad::master.rumble(". -.."); + * @endcode + * + * @return 0 if the rumble was successful + * @return INT32_MAX if there was an error, setting errno + */ + void rumble(std::string rumble_pattern); + /** + * @brief Get the state of a button on the controller. + * + * @param button Which button to return + * + * @b Example: + * @code {.cpp} + * if(gamepad::master[DIGITAL_L1]) { + * // do something here... + * } + * @endcode + * + */ + const Button& operator[](pros::controller_digital_e_t button); + /** + * @brief Get the value of a joystick axis on the controller. + * + * @param joystick Which joystick axis to return + * + * @b Example: + * @code {.cpp} + * // control a motor with a joystick + * intake.move(gamepad::master[ANALOG_RIGHT_Y]); + * @endcode + * + */ + float operator[](pros::controller_analog_e_t joystick); + + /// The L1 button on the top of the controller. + const Button& buttonL1(); + + /// The L2 button on the top of the controller. + const Button& buttonL2(); + + /// The R1 button on the top of the controller. + const Button& buttonR1(); + + /// The R2 button on the top of the controller. + const Button& buttonR2(); + + /// The up arrow button on the front of the controller. + const Button& buttonUp(); + + /// The down arrow button on the front of the controller. + const Button& buttonDown(); + + /// The left arrow button on the front of the controller. + const Button& buttonLeft(); + + /// The right arrow button on the front of the controller. + const Button& buttonRight(); + + /// The X arrow button on the front of the controller. + const Button& buttonX(); + + /// The B arrow button on the front of the controller. + const Button& buttonB(); + + /// The Y arrow button on the front of the controller. + const Button& buttonY(); + + /// The A arrow button on the front of the controller. + const Button& buttonA(); + + /** + * @brief Gets the value of the left joystick's x axis, optionally applying a curve. + * + * @param use_curve (optional) Whether or not to use the curve; defaults to true. + * @return float The value of the left joystick's x-axis, between -1.0 and 1.0. + */ + float axisLeftX(bool use_curve = true); + + /** + * @brief Gets the value of the left joystick's y axis, optionally applying a curve. + * + * @param use_curve (optional) Whether or not to use the curve; defaults to true. + * @return float The value of the left joystick's y-axis, between -1.0 and 1.0. + */ + float axisLeftY(bool use_curve = true); + + /** + * @brief Gets the value of the right joystick's x axis, optionally applying a curve. + * + * @param use_curve (optional) Whether or not to use the curve; defaults to true. + * @return float The value of the right joystick's x-axis, between -1.0 and 1.0. + */ + float axisRightX(bool use_curve = true); + + /** + * @brief Gets the value of the right joystick's y axis, optionally applying a curve. + * + * @param use_curve (optional) Whether or not to use the curve; defaults to true. + * @return float The value of the right joystick's y-axis, between -1.0 and 1.0. + */ + float axisRightY(bool use_curve = true); + + /** + * @brief Set the transformation to be used for the left joystick. + * + * @param left_transformation The transformation to be used + */ + void set_left_transform(Transformation left_transformation); + + /** + * @brief Set the transformation to be used for the right joystick. + * + * @param right_transformation The transformation to be used + */ + void set_right_transform(Transformation right_transformation); + + /// The master controller, same as @ref gamepad::master + static Gamepad master; + /// The partner controller, same as @ref gamepad::partner + static Gamepad partner; + private: + Gamepad(pros::controller_id_e_t id); + + Button m_L1 {}, m_L2 {}, m_R1 {}, m_R2 {}, m_Up {}, m_Down {}, m_Left {}, m_Right {}, m_X {}, m_B {}, m_Y {}, + m_A {}; + float m_LeftX = 0, m_LeftY = 0, m_RightX = 0, m_RightY = 0; + Button Fake {}; + std::optional m_left_transformation {std::nullopt}; + std::optional m_right_transformation {std::nullopt}; + /** + * @brief Gets a unique name for a listener that will not conflict with user listener names. + * + * @important: when using the function, you must register the listener by + * directly calling addListener on the EventHandler, do NOT use onPress/addListener,etc. + * + * @return std::string A unique listener name + */ + static std::string uniqueName(); + static Button Gamepad::* buttonToPtr(pros::controller_digital_e_t button); + void updateButton(pros::controller_digital_e_t button_id); + + void updateScreens(); + + std::shared_ptr m_default_screen = std::make_shared(); + std::vector> m_screens = {}; + ScreenBuffer m_current_screen = {}; + ScreenBuffer m_next_buffer = {}; + pros::Controller m_controller; + + uint8_t m_last_printed_line = 0; + uint32_t m_last_print_time = 0; + uint32_t m_last_update_time = 0; + bool m_screen_cleared = false; + pros::Mutex m_mutex {}; +}; + +inline Gamepad Gamepad::master {pros::E_CONTROLLER_MASTER}; +inline Gamepad Gamepad::partner {pros::E_CONTROLLER_PARTNER}; +/// The master controller +inline Gamepad& master = Gamepad::master; +/// The partner controller +inline Gamepad& partner = Gamepad::partner; + +} // namespace gamepad diff --git a/include/gamepad/joystick_transformation.hpp b/include/gamepad/joystick_transformation.hpp new file mode 100644 index 0000000..62df78d --- /dev/null +++ b/include/gamepad/joystick_transformation.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include +#include +#include +#include + +namespace gamepad { + +/** + * @brief An abstract class for joystick transformations. + * + * A transformation takes a coordinate representing the value of the joystick, and returns a transformed coordinate + * value + * + */ +class AbstractTransformation { + public: + /** + * @brief Get the transformed coordinate given the original. + * + * @param original The original value of the joystick + * @return std::pair The transformed value + */ + virtual std::pair get_value(std::pair original) = 0; + virtual ~AbstractTransformation() = default; +}; + +/** + * @brief A joystick transformation that applies a deadband to the joystick values + * + * A deadband makes the joystick value zero when the value is close to zero. This helps prevent drifting, since + * joysticks often do not read exactly zero when released. + */ +class Deadband : public AbstractTransformation { + public: + /** + * @brief Construct a new Deadband object + * + * @param x_deadband The deadband to apply for the x axis. + * @param y_deadband The deadband to apply for the x axis. + * @param x_spread How much the deadband for the x axis should widen. + * @param y_spread How much the deadband for the y axis should widen. + */ + Deadband(float x_deadband, float y_deadband, float x_spread, float y_spread) + : m_x_deadband(x_deadband), + m_y_deadband(y_deadband), + m_x_spread(x_spread), + m_y_spread(y_spread) {} + + /** + * @brief Construct a new Deadband object + * + * @param x_deadband The deadband to apply for the x axis. + * @param y_deadband The deadband to apply for the y axis. + */ + Deadband(float x_deadband, float y_deadband) + : Deadband(x_deadband, y_deadband, 0.0, 0.0) {} + + /** + * @brief Get the joystick coordinate after applying the deadband + * + * @param original The value of the joystick before applying the deadband + * @return std::pair The joystick coordinate, with a deadband applied + */ + std::pair get_value(std::pair original) override; + private: + /** + * @brief Applies a deadband to a joystick axis + * + * @param value The value of the joystick axis + * @param deadband The deadband to use + * @return float The joystick axis value with deadband applied + */ + static float apply_deadband(float value, float deadband); + + float m_x_deadband; + float m_y_deadband; + float m_x_spread; + float m_y_spread; +}; + +/** + * @brief A joystick transformation that applies an expo curve to the joystick values + * + * An expo curve allows greater control of the joystick, by reducing the joystick values at low speeds, while still + * allowing you to attain the maximum value of the joysticks. + */ +class ExpoCurve : public AbstractTransformation { + public: + /** + * @brief Construct a new Expo Curve object + * + * @param x_curve How much the x axis should be curved. A higher value curves the joystick value more. + * @param y_curve How much the y axis should be curved. A higher value curves the joystick value more. + */ + ExpoCurve(float x_curve, float y_curve) + : m_x_curve(x_curve), + m_y_curve(y_curve) {} + + /** + * @brief Get the joystick coordinate after applying the curve + * + * @param original The value of the joystick before applying the curve + * @return std::pair The joystick coordinate, with a curve applied + */ + std::pair get_value(std::pair original) override; + private: + float m_x_curve; + float m_y_curve; +}; + +/** + * @brief A joystick transformation that applies a fisheye to the joystick values + * + * The vex controller joysticks don't reach their maximum value in the corners. This can be an issue, especially when + * using single stick arcade. The fisheye "stretches" the joystick values so that they attain their maximum value even + * in the corners of the joysticks. + */ +class Fisheye : public AbstractTransformation { + public: + /** + * @brief Construct a new Fisheye object + * + * @param radius The radius of the rounded circle that forms the corners of the joystick's housing. + */ + Fisheye(float radius) + : m_radius(radius) {} + + /** + * @brief Get the joystick coordinate after applying the fisheye + * + * @param original The value of the joystick before applying the fisheye + * @return std::pair The joystick coordinate, with a fisheye applied + */ + std::pair get_value(std::pair original) override; + private: + float m_radius; +}; + +/** + * @brief A chain of transformations. This class should not be directly used, but should be constructed using the + * TransformationBuilder class. + */ +class Transformation final { + friend class TransformationBuilder; + public: + std::pair get_value(std::pair); + private: + std::vector> m_all_transforms; +}; + +/** + * @brief A class to create a chain of transformations. + * + */ +class TransformationBuilder final { + public: + /** + * @brief Construct a new Transformation Builder object + * + * @param first The transformation that should be used first + */ + template T> TransformationBuilder(T first) { + m_transform.m_all_transforms.push_back(std::make_unique(std::move(first))); + } + + TransformationBuilder() = delete; + + /** + * @brief Add a transformation to the list of transformations to be applied. + * + * @param next The next transformation to be applied after the previous specified transformation + * @return TransformationBuilder& The original Transformation Builder. + */ + template T> TransformationBuilder& and_then(T next) { + m_transform.m_all_transforms.push_back(std::make_unique(std::move(next))); + return *this; + } + + /** + * @brief Generate the final chained transformation + * + * @return Transformation The final chained transformation. This can be passed to + * set_left_transform/set_right_transform + */ + Transformation build() { return std::move(m_transform); } + + /** + * @brief Generate the final chained transformation + * + * @return Transformation The final chained transformation. This can be passed to + * set_left_transform/set_right_transform + */ + operator Transformation() { return std::move(m_transform); } + private: + Transformation m_transform {}; +}; +} // namespace gamepad diff --git a/include/gamepad/recursive_mutex.hpp b/include/gamepad/recursive_mutex.hpp index cd84052..0df16c3 100644 --- a/include/gamepad/recursive_mutex.hpp +++ b/include/gamepad/recursive_mutex.hpp @@ -24,7 +24,7 @@ class RecursiveMutex { bool take(std::uint32_t timeout = TIMEOUT_MAX) { return pros::c::mutex_recursive_take(mutex, timeout); } /** - * @brief Locks the mutex, waiting indefinetely until the mutex is acquired + * @brief Locks the mutex, waiting indefinitely until the mutex is acquired * */ void lock() { diff --git a/include/gamepad/screens/abstractScreen.hpp b/include/gamepad/screens/abstractScreen.hpp new file mode 100644 index 0000000..2e0f91d --- /dev/null +++ b/include/gamepad/screens/abstractScreen.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "pros/misc.h" + +namespace gamepad { + +/** + * @brief type for conveying a full screen with the first 3 being the lines + * of text on the controller screen and the last being a rumble pattern + */ +typedef std::array, 4> ScreenBuffer; + +/** + * @brief The abstract class for interacting with the controller screen + * + */ +class AbstractScreen { + public: + AbstractScreen(uint32_t priority) + : m_priority(priority) {} + + /** + * @brief runs every time the controller's update function is called + * use this if you need to update something regardless of if there is an + * available slot in the screen + * + * @param delta_time the time since the last update in milliseconds + */ + virtual void update(uint32_t delta_time) {} + + /** + * @brief runs if there is an empty line that is available to print + * + * @param visible_lines a set that contains the line numbers of all lines that + * are empty and available for printing + * + * @returns a the lines to be printed, any lines that are not available will be ignored + */ + virtual ScreenBuffer getScreen(std::set visible_lines) = 0; + + /** + * @brief a function where button events are pushed, use this to handle button events. + * + * @param button_events a set of the button events that happened this update + */ + virtual void handleEvents(std::set button_events) {} + + /** + * @brief returns the priority of the screen + * + * @warning it is not recommended to override this function + */ + uint32_t getPriority() { return m_priority; } + protected: + const uint32_t m_priority; +}; + +} // namespace gamepad diff --git a/include/gamepad/screens/alertScreen.hpp b/include/gamepad/screens/alertScreen.hpp new file mode 100644 index 0000000..3c8edc6 --- /dev/null +++ b/include/gamepad/screens/alertScreen.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include "abstractScreen.hpp" +#include "pros/rtos.hpp" +#include "gamepad/screens/abstractScreen.hpp" + +namespace gamepad { + +/** + * @brief a screen that sends alerts to the controller, duration of alerts can be customized + * + * @note priority: UINT32_MAX - 100 + */ +class AlertScreen : public AbstractScreen { + public: + AlertScreen() + : AbstractScreen(UINT32_MAX - 100) {} + + /** + * @brief updates the alert loop + * + * @param delta_time the time since the last update + */ + void update(uint32_t delta_time); + + /** + * @brief return the next alert to print if there is space for it on the screen + * + * @param visible_lines a set that contains the line numbers of all lines that + * are empty and available for printing + * + * @returns a the lines to be printed, any lines that are not available will be ignored + */ + ScreenBuffer getScreen(std::set visible_lines); + + /** + * @brief add an alert to the alert queue, to be printed as soon as there is an available space + * + * @param line the line number to print the alert at (0-2) + * @param strs the string to print on the controller, "\n" to go to the next line + * lines that go over 2 will be cropped out + * @param duration how long the alert should persist on the screen + * @param rumble A string consisting of the characters '.', '-', and ' ', where dots are short rumbles, + * dashes are long rumbles, and spaces are pauses. Maximum supported length is 8 characters. + * + * This function uses the following value(s) of errno when an error state is reached: + * + * EINVAL: The line number is not in the interval [0, 2] + * EMSGSIZE: The alert is more than 3 lines long + * + * @return 0 if the alert was added successfully + * @return INT32_MAX if there was an error, setting errno + * + */ + int32_t addAlerts(uint8_t line, std::string strs, uint32_t duration, std::string rumble = ""); + private: + struct AlertBuffer { + ScreenBuffer screen; + uint32_t duration; + }; + + std::deque m_screen_buffer {}; + std::optional m_screen_contents {}; + uint32_t m_line_set_time = 0; + pros::Mutex m_mutex {}; +}; + +} // namespace gamepad diff --git a/include/gamepad/screens/defaultScreen.hpp b/include/gamepad/screens/defaultScreen.hpp new file mode 100644 index 0000000..6c46c82 --- /dev/null +++ b/include/gamepad/screens/defaultScreen.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "gamepad/screens/abstractScreen.hpp" +#include "pros/rtos.hpp" + +namespace gamepad { + +/** + * @brief A basic screen that allows basic prints, similar to pros controller api + * + * @note The gamepad class has wrappers around this class + * @note priority: 1 + */ +class DefaultScreen : public AbstractScreen { + public: + DefaultScreen() + : AbstractScreen(1) {} + + /** + * @brief returns any lines that have space to print on the controller + * + * @param visible_lines a set that contains the line numbers of all lines that + * are empty and available for printing + * + * @returns a the lines to be printed, any lines that are not available will be ignored + */ + ScreenBuffer getScreen(std::set visible_lines); + + /** + * @brief print a line to the console like pros + * + * @param line the line number to print the string on (0-2) + * @param str the string to print onto the controller (\n to go to the next line) + * + * This function uses the following value(s) of errno when an error state is reached: + * + * EINVAL: The line number is not in the interval [0, 2] + * EMSGSIZE: The string is more than 3 lines long + * + * @return 0 if the alert was added successfully + * @return INT32_MAX if there was an error, setting errno + */ + int32_t printLine(uint8_t line, std::string str); + + /** + * makes the controller rumble like pros + * + * @param rumble_pattern A string consisting of the characters '.', '-', and ' ', where dots are short rumbles, + * dashes are long rumbles, and spaces are pauses. Maximum supported length is 8 characters. + * + * This function uses the following value(s) of errno when an error state is reached: + * + * EINVAL: The rumble pattern contains a character other than '.', '-', or ' ' + * EMSGSIZE: The pattern is more than 8 characters long + * + * @return 0 if the alert was added successfully + * @return INT32_MAX if there was an error, setting errno + */ + int32_t rumble(std::string rumble_pattern); + private: + ScreenBuffer m_current_buffer {}; + pros::Mutex m_mutex {}; +}; + +} // namespace gamepad diff --git a/include/gamepad/todo.hpp b/include/gamepad/todo.hpp index c1e0b7b..4b0ffd4 100644 --- a/include/gamepad/todo.hpp +++ b/include/gamepad/todo.hpp @@ -1,5 +1,13 @@ #pragma once #define DO_PRAGMA(x) _Pragma(#x) -#define TODO(x) DO_PRAGMA(message("TODO - " #x)) -#define FIXME(x) DO_PRAGMA(warning("FIXME - " #x)) \ No newline at end of file + +// We only define the TODO/FIXME macros if the file is being compiled by Microsoft Intellisense +// or clangd. This way, the TODO/FIXME messages don't clutter the compilation messages. +#if defined(_debug) || defined(__clang__) +#define TODO(x) DO_PRAGMA(message("TODO - " x)) +#define FIXME(x) DO_PRAGMA(warning("FIXME - " x)) +#else +#define TODO(x) +#define FIXME(x) +#endif \ No newline at end of file diff --git a/src/gamepad/button.cpp b/src/gamepad/button.cpp index 703c246..f343eeb 100644 --- a/src/gamepad/button.cpp +++ b/src/gamepad/button.cpp @@ -2,90 +2,104 @@ #include "gamepad/todo.hpp" #include "pros/rtos.hpp" #include -#include namespace gamepad { -void Button::set_long_press_threshold(uint32_t threshold) const { this->long_press_threshold = threshold; } +_impl::EventHandler* Button::get_handler(EventType event) const { + switch (event) { + case gamepad::EventType::ON_PRESS: return &m_on_press_event; + case gamepad::EventType::ON_LONG_PRESS: return &m_on_long_press_event; + case gamepad::EventType::ON_RELEASE: return &m_on_release_event; + case gamepad::EventType::ON_SHORT_RELEASE: return &m_on_short_release_event; + case gamepad::EventType::ON_LONG_RELEASE: return &m_on_long_release_event; + case gamepad::EventType::ON_REPEAT_PRESS: return &m_on_repeat_press_event; + default: return nullptr; + } +} -void Button::set_repeat_cooldown(uint32_t cooldown) const { this->repeat_cooldown = cooldown; } +void Button::setLongPressThreshold(uint32_t threshold) const { m_long_press_threshold = threshold; } -bool Button::onPress(std::string listenerName, std::function func) const { - return this->onPressEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +void Button::setRepeatCooldown(uint32_t cooldown) const { m_repeat_cooldown = cooldown; } + +int32_t Button::onPress(std::string listenerName, std::function func) const { + return m_on_press_event.addListener(std::move(listenerName) + "_user", std::move(func)); } -bool Button::onLongPress(std::string listenerName, std::function func) const { - return this->onLongPressEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +int32_t Button::onLongPress(std::string listenerName, std::function func) const { + return m_on_long_press_event.addListener(std::move(listenerName) + "_user", std::move(func)); } -bool Button::onRelease(std::string listenerName, std::function func) const { - return this->onReleaseEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +int32_t Button::onRelease(std::string listenerName, std::function func) const { + return m_on_release_event.addListener(std::move(listenerName) + "_user", std::move(func)); } -bool Button::onShortRelease(std::string listenerName, std::function func) const { - return this->onShortReleaseEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +int32_t Button::onShortRelease(std::string listenerName, std::function func) const { + return m_on_short_release_event.addListener(std::move(listenerName) + "_user", std::move(func)); } -bool Button::onLongRelease(std::string listenerName, std::function func) const { - return this->onLongReleaseEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +int32_t Button::onLongRelease(std::string listenerName, std::function func) const { + return m_on_long_release_event.addListener(std::move(listenerName) + "_user", std::move(func)); } -bool Button::onRepeatPress(std::string listenerName, std::function func) const { - return this->onRepeatPressEvent.add_listener(std::move(listenerName) + "_user", std::move(func)); +int32_t Button::onRepeatPress(std::string listenerName, std::function func) const { + return m_on_repeat_press_event.addListener(std::move(listenerName) + "_user", std::move(func)); } -bool Button::addListener(EventType event, std::string listenerName, std::function func) const { - switch (event) { - case gamepad::EventType::ON_PRESS: return this->onPress(std::move(listenerName), std::move(func)); - case gamepad::EventType::ON_LONG_PRESS: return this->onLongPress(std::move(listenerName), std::move(func)); - case gamepad::EventType::ON_RELEASE: return this->onRelease(std::move(listenerName), std::move(func)); - case gamepad::EventType::ON_SHORT_RELEASE: - return this->onShortRelease(std::move(listenerName), std::move(func)); - case gamepad::EventType::ON_LONG_RELEASE: return this->onLongRelease(std::move(listenerName), std::move(func)); - case gamepad::EventType::ON_REPEAT_PRESS: return this->onRepeatPress(std::move(listenerName), std::move(func)); - default: - TODO("add error logging") - errno = EINVAL; - return false; +int32_t Button::addListener(EventType event, std::string listenerName, std::function func) const { + auto handler = this->get_handler(event); + if (handler != nullptr) { + return handler->addListener(listenerName + "_user", func); + } else { + TODO("add error logging") + errno = EINVAL; + return INT32_MAX; } } -bool Button::removeListener(std::string listenerName) const { - return this->onPressEvent.remove_listener(listenerName + "_user") || - this->onLongPressEvent.remove_listener(listenerName + "_user") || - this->onReleaseEvent.remove_listener(listenerName + "_user") || - this->onShortReleaseEvent.remove_listener(listenerName + "_user") || - this->onLongReleaseEvent.remove_listener(listenerName + "_user") || - this->onRepeatPressEvent.remove_listener(listenerName + "_user"); +int32_t Button::removeListener(EventType event, std::string listenerName) const { + return m_on_press_event.removeListener(listenerName + "_user") || + m_on_long_press_event.removeListener(listenerName + "_user") || + m_on_release_event.removeListener(listenerName + "_user") || + m_on_short_release_event.removeListener(listenerName + "_user") || + m_on_long_release_event.removeListener(listenerName + "_user") || + m_on_repeat_press_event.removeListener(listenerName + "_user"); + auto handler = this->get_handler(event); + if (handler != nullptr) { + return handler->removeListener(listenerName + "_user"); + } else { + TODO("add error logging") + errno = EINVAL; + return INT32_MAX; + } } void Button::update(const bool is_held) { this->rising_edge = !this->is_pressed && is_held; this->falling_edge = this->is_pressed && !is_held; this->is_pressed = is_held; - if (is_held) this->time_held += pros::millis() - this->last_update_time; - else this->time_released += pros::millis() - this->last_update_time; + if (is_held) this->time_held += pros::millis() - m_last_update_time; + else this->time_released += pros::millis() - m_last_update_time; if (this->rising_edge) { - this->onPressEvent.fire(); - } else if (this->is_pressed && this->time_held >= this->long_press_threshold && - this->last_long_press_time <= pros::millis() - this->time_held) { - this->onLongPressEvent.fire(); - this->last_long_press_time = pros::millis(); - this->last_repeat_time = pros::millis() - this->repeat_cooldown; + m_on_press_event.fire(); + } else if (this->is_pressed && this->time_held >= m_long_press_threshold && + m_last_long_press_time <= pros::millis() - this->time_held) { + m_on_long_press_event.fire(); + m_last_long_press_time = pros::millis(); + m_last_repeat_time = pros::millis() - m_repeat_cooldown; this->repeat_iterations = 0; - } else if (this->is_pressed && this->time_held >= this->long_press_threshold && - pros::millis() - this->last_repeat_time >= this->repeat_cooldown) { + } else if (this->is_pressed && this->time_held >= m_long_press_threshold && + pros::millis() - m_last_repeat_time >= m_repeat_cooldown) { this->repeat_iterations++; - this->onRepeatPressEvent.fire(); - this->last_repeat_time = pros::millis(); + m_on_repeat_press_event.fire(); + m_last_repeat_time = pros::millis(); } else if (this->falling_edge) { - this->onReleaseEvent.fire(); - if (this->time_held < this->long_press_threshold) this->onShortReleaseEvent.fire(); - else this->onLongReleaseEvent.fire(); + m_on_release_event.fire(); + if (this->time_held < m_long_press_threshold) m_on_short_release_event.fire(); + else m_on_long_release_event.fire(); } if (this->rising_edge) this->time_held = 0; if (this->falling_edge) this->time_released = 0; - this->last_update_time = pros::millis(); + m_last_update_time = pros::millis(); } -} // namespace gamepad \ No newline at end of file +} // namespace gamepad diff --git a/src/gamepad/controller.cpp b/src/gamepad/controller.cpp deleted file mode 100644 index fff75f2..0000000 --- a/src/gamepad/controller.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "gamepad/button.hpp" -#include "gamepad/controller.hpp" -#include "gamepad/todo.hpp" -#include "pros/misc.h" -#include - -namespace gamepad { -void Gamepad::updateButton(pros::controller_digital_e_t button_id) { - Button Gamepad::*button = Gamepad::button_to_ptr(button_id); - bool is_held = this->controller.get_digital(button_id); - (this->*button).update(is_held); -} - -void Gamepad::update() { - for (int i = pros::E_CONTROLLER_DIGITAL_L1; i <= pros::E_CONTROLLER_DIGITAL_A; ++i) { - this->updateButton(static_cast(i)); - } - - this->m_LeftX = this->controller.get_analog(pros::E_CONTROLLER_ANALOG_LEFT_X); - this->m_LeftY = this->controller.get_analog(pros::E_CONTROLLER_ANALOG_LEFT_Y); - this->m_RightX = this->controller.get_analog(pros::E_CONTROLLER_ANALOG_RIGHT_X); - this->m_RightY = this->controller.get_analog(pros::E_CONTROLLER_ANALOG_RIGHT_Y); -} - -const Button& Gamepad::operator[](pros::controller_digital_e_t button) { return this->*Gamepad::button_to_ptr(button); } - -float Gamepad::operator[](pros::controller_analog_e_t axis) { - switch (axis) { - case pros::E_CONTROLLER_ANALOG_LEFT_X: return this->LeftX; - case pros::E_CONTROLLER_ANALOG_LEFT_Y: return this->LeftY; - case pros::E_CONTROLLER_ANALOG_RIGHT_X: return this->RightX; - case pros::E_CONTROLLER_ANALOG_RIGHT_Y: return this->RightY; - default: - TODO("add error logging") - errno = EINVAL; - return 0; - } -} - -std::string Gamepad::unique_name() { - static std::atomic i = 0; - return std::to_string(i++) + "_internal"; -} - -Button Gamepad::*Gamepad::button_to_ptr(pros::controller_digital_e_t button) { - switch (button) { - case pros::E_CONTROLLER_DIGITAL_L1: return &Gamepad::m_L1; - case pros::E_CONTROLLER_DIGITAL_L2: return &Gamepad::m_L2; - case pros::E_CONTROLLER_DIGITAL_R1: return &Gamepad::m_R1; - case pros::E_CONTROLLER_DIGITAL_R2: return &Gamepad::m_R2; - case pros::E_CONTROLLER_DIGITAL_UP: return &Gamepad::m_Up; - case pros::E_CONTROLLER_DIGITAL_DOWN: return &Gamepad::m_Down; - case pros::E_CONTROLLER_DIGITAL_LEFT: return &Gamepad::m_Left; - case pros::E_CONTROLLER_DIGITAL_RIGHT: return &Gamepad::m_Right; - case pros::E_CONTROLLER_DIGITAL_X: return &Gamepad::m_X; - case pros::E_CONTROLLER_DIGITAL_B: return &Gamepad::m_B; - case pros::E_CONTROLLER_DIGITAL_Y: return &Gamepad::m_Y; - case pros::E_CONTROLLER_DIGITAL_A: return &Gamepad::m_A; - default: - TODO("add error logging") - errno = EINVAL; - return &Gamepad::Fake; - } -} -} // namespace gamepad diff --git a/src/gamepad/gamepad.cpp b/src/gamepad/gamepad.cpp new file mode 100644 index 0000000..45c2d70 --- /dev/null +++ b/src/gamepad/gamepad.cpp @@ -0,0 +1,227 @@ +#include "gamepad/gamepad.hpp" +#include "gamepad/todo.hpp" +#include "pros/misc.h" +#include "pros/rtos.hpp" +#include "screens/abstractScreen.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace gamepad { +Gamepad::Gamepad(pros::controller_id_e_t id) + : m_controller(id) { + this->addScreen(m_default_screen); +} + +void Gamepad::updateButton(pros::controller_digital_e_t button_id) { + Button Gamepad::* button = Gamepad::buttonToPtr(button_id); + bool is_held = m_controller.get_digital(button_id); + (this->*button).update(is_held); +} + +void Gamepad::updateScreens() { + // Lock Mutexes for Thread Safety + std::lock_guard guard_scheduling(m_mutex); + + // Disable screen updates if the controller is disconnected + if (!m_controller.is_connected()) { + if (m_screen_cleared) { + m_next_buffer = std::move(m_current_screen); + m_current_screen = {}; + m_screen_cleared = false; + } + return; + } + + // Clear current screen and reset last update time on reconnect + if (m_controller.is_connected() && !m_screen_cleared) { + m_current_screen = {}; + m_last_update_time = pros::millis(); + } + + // Get new button presses + std::set buttonUpdates; + for (int i = pros::E_CONTROLLER_DIGITAL_L1; i <= pros::E_CONTROLLER_DIGITAL_A; ++i) { + if ((this->*this->buttonToPtr(static_cast(i))).rising_edge) { + buttonUpdates.emplace(static_cast(i)); + } + } + + // Update all screens, and send new button presses, also note deltatime + for (std::shared_ptr screen : m_screens) { + screen->update(pros::millis() - m_last_update_time); + screen->handleEvents(buttonUpdates); + } + m_last_update_time = pros::millis(); + + // Check if enough time has passed for the Gamepad to poll for updates + if (pros::millis() - m_last_print_time <= 50) return; + + for (std::shared_ptr screen : m_screens) { + // get all lines that aren't being used by a higher priority screen + std::set visible_lines; + for (uint8_t j = 0; j < 4; j++) + if (!m_next_buffer[j].has_value()) visible_lines.emplace(j); + + // get the buffer of the next lower priority screen and set it to be printed + ScreenBuffer buffer = screen->getScreen(visible_lines); + for (uint8_t j = 0; j < 4; j++) + if (buffer[j].has_value() && !buffer[j]->empty() && !m_next_buffer[j].has_value()) + m_next_buffer[j] = std::move(buffer[j]); + } + + for (int i = 0; i < 4; i++) { + // start from the line thats after the line thats been set so we dont get stuck setting the first line + int line = (m_last_printed_line + i) % 4; + + // theres nothing on this line so we can skip it + if (!m_next_buffer[line].has_value()) continue; + + if (!m_screen_cleared && line != 3) { + m_controller.clear(); + m_screen_cleared = true; + m_current_screen = {}; + m_last_print_time = pros::millis(); + return; + } + + // text on screen is the same as last frame's text so no use updating + if (m_current_screen[line] == m_next_buffer[line] && line != 3) { + m_next_buffer[line] = std::nullopt; + continue; + } + + // print to screen or rumble + if (line == 3) m_controller.rumble(m_next_buffer[line].value_or("").c_str()); + else m_controller.set_text(line, 0, m_next_buffer[line].value_or("") + std::string(40, ' ')); + if (line != 3) m_current_screen[line] = std::move(m_next_buffer[line]); + m_next_buffer[line] = std::nullopt; + m_last_printed_line = line; + m_last_print_time = pros::millis(); + return; + } +} + +void Gamepad::update() { + for (int i = pros::E_CONTROLLER_DIGITAL_L1; i <= pros::E_CONTROLLER_DIGITAL_A; ++i) { + this->updateButton(static_cast(i)); + } + + m_LeftX = m_controller.get_analog(pros::E_CONTROLLER_ANALOG_LEFT_X) / 127.0; + m_LeftY = m_controller.get_analog(pros::E_CONTROLLER_ANALOG_LEFT_Y) / 127.0; + m_RightX = m_controller.get_analog(pros::E_CONTROLLER_ANALOG_RIGHT_X) / 127.0; + m_RightY = m_controller.get_analog(pros::E_CONTROLLER_ANALOG_RIGHT_Y) / 127.0; + + this->updateScreens(); +} + +void Gamepad::addScreen(std::shared_ptr screen) { + uint32_t last = UINT32_MAX; + uint32_t pos = 0; + for (pos = 0; pos < m_screens.size(); pos++) { + if (m_screens[pos]->getPriority() < screen->getPriority() && last >= screen->getPriority()) break; + last = m_screens[pos]->getPriority(); + } + m_screens.emplace(m_screens.begin() + pos, screen); +} + +int32_t Gamepad::printLine(uint8_t line, std::string str) { return m_default_screen->printLine(line, str); } + +void Gamepad::clear() { m_default_screen->printLine(0, " \n \n "); } + +int32_t Gamepad::clear(uint8_t line) { return m_default_screen->printLine(line, " "); } + +void Gamepad::rumble(std::string rumble_pattern) { m_default_screen->rumble(rumble_pattern); } + +const Button& Gamepad::operator[](pros::controller_digital_e_t button) { return this->*Gamepad::buttonToPtr(button); } + +float Gamepad::operator[](pros::controller_analog_e_t axis) { + switch (axis) { + case pros::E_CONTROLLER_ANALOG_LEFT_X: return m_LeftX; + case pros::E_CONTROLLER_ANALOG_LEFT_Y: return m_LeftY; + case pros::E_CONTROLLER_ANALOG_RIGHT_X: return m_RightX; + case pros::E_CONTROLLER_ANALOG_RIGHT_Y: return m_RightY; + default: TODO("add error logging") return 0; + } +} + +const Button& Gamepad::buttonL1() { return m_L1; } + +const Button& Gamepad::buttonL2() { return m_L2; } + +const Button& Gamepad::buttonR1() { return m_R1; } + +const Button& Gamepad::buttonR2() { return m_R2; } + +const Button& Gamepad::buttonUp() { return m_Up; } + +const Button& Gamepad::buttonDown() { return m_Down; } + +const Button& Gamepad::buttonLeft() { return m_Left; } + +const Button& Gamepad::buttonRight() { return m_Right; } + +const Button& Gamepad::buttonX() { return m_X; } + +const Button& Gamepad::buttonB() { return m_B; } + +const Button& Gamepad::buttonY() { return m_Y; } + +const Button& Gamepad::buttonA() { return m_A; } + +float Gamepad::axisLeftX(bool use_curve) { + if (use_curve && m_left_transformation) return m_left_transformation->get_value({m_LeftX, m_LeftY}).first; + else return m_LeftX; +} + +float Gamepad::axisLeftY(bool use_curve) { + if (use_curve && m_left_transformation) return m_left_transformation->get_value({m_LeftX, m_LeftY}).second; + else return m_LeftY; +} + +float Gamepad::axisRightX(bool use_curve) { + if (use_curve && m_right_transformation) return m_right_transformation->get_value({m_RightX, m_RightY}).first; + else return m_RightX; +} + +float Gamepad::axisRightY(bool use_curve) { + if (use_curve && m_right_transformation) return m_right_transformation->get_value({m_RightX, m_RightY}).second; + else return m_RightY; +} + +void Gamepad::set_left_transform(Transformation left_transformation) { + m_left_transformation = std::move(left_transformation); +} + +void Gamepad::set_right_transform(Transformation right_transformation) { + m_right_transformation = std::move(right_transformation); +} + +std::string Gamepad::uniqueName() { + static std::atomic i = 0; + return std::to_string(i++) + "_internal"; +} + +Button Gamepad::* Gamepad::buttonToPtr(pros::controller_digital_e_t button) { + switch (button) { + case pros::E_CONTROLLER_DIGITAL_L1: return &Gamepad::m_L1; + case pros::E_CONTROLLER_DIGITAL_L2: return &Gamepad::m_L2; + case pros::E_CONTROLLER_DIGITAL_R1: return &Gamepad::m_R1; + case pros::E_CONTROLLER_DIGITAL_R2: return &Gamepad::m_R2; + case pros::E_CONTROLLER_DIGITAL_UP: return &Gamepad::m_Up; + case pros::E_CONTROLLER_DIGITAL_DOWN: return &Gamepad::m_Down; + case pros::E_CONTROLLER_DIGITAL_LEFT: return &Gamepad::m_Left; + case pros::E_CONTROLLER_DIGITAL_RIGHT: return &Gamepad::m_Right; + case pros::E_CONTROLLER_DIGITAL_X: return &Gamepad::m_X; + case pros::E_CONTROLLER_DIGITAL_B: return &Gamepad::m_B; + case pros::E_CONTROLLER_DIGITAL_Y: return &Gamepad::m_Y; + case pros::E_CONTROLLER_DIGITAL_A: return &Gamepad::m_A; + default: TODO("add error logging") return &Gamepad::Fake; + } +} +} // namespace gamepad diff --git a/src/gamepad/joystick_transformation.cpp b/src/gamepad/joystick_transformation.cpp new file mode 100644 index 0000000..a0722a8 --- /dev/null +++ b/src/gamepad/joystick_transformation.cpp @@ -0,0 +1,53 @@ +#include "joystick_transformation.hpp" +#include +#include + +using std::abs; +using std::copysign; +using std::pow; + +namespace gamepad { +float Deadband::apply_deadband(float value, float deadband) { + float abs_val = abs(value); + return copysign(abs_val < deadband ? 0 : (abs_val - deadband) / (1.0 - deadband), value); +} + +std::pair Deadband::get_value(std::pair value) { + float x = value.first; + float y = value.second; + float x_deadband = m_x_deadband + abs(y) * m_x_spread; + float y_deadband = m_y_deadband + abs(x) * m_y_spread; + x = apply_deadband(x, x_deadband); + y = apply_deadband(y, y_deadband); + return {x, y}; +} + +std::pair ExpoCurve::get_value(std::pair value) { + float x = value.first; + float y = value.second; + x = copysign(pow(abs(x), m_x_curve), x); + y = copysign(pow(abs(y), m_y_curve), y); + return {x, y}; +} + +std::pair Fisheye::get_value(std::pair value) { + float x = value.first; + float y = value.second; + float x_abs = abs(x); + float y_abs = abs(y); + float j = std::sqrt(m_radius * m_radius - 1.0 * 1.0); + if (x_abs >= j && y_abs >= j) { + float scale = std::hypot(std::min(x_abs / y_abs, y_abs / x_abs), 1.0) / m_radius; + x_abs *= scale; + y_abs *= scale; + } + x = std::copysign(std::min(1.0f, x_abs), x); + y = std::copysign(std::min(1.0f, y_abs), y); + return {x, y}; +} + +std::pair Transformation::get_value(std::pair value) { + return std::accumulate(m_all_transforms.begin(), m_all_transforms.end(), value, + [](auto last_val, auto& next_transform) { return next_transform->get_value(last_val); }); +} +} // namespace gamepad \ No newline at end of file diff --git a/src/gamepad/screens/alertScreen.cpp b/src/gamepad/screens/alertScreen.cpp new file mode 100644 index 0000000..b7e9c1b --- /dev/null +++ b/src/gamepad/screens/alertScreen.cpp @@ -0,0 +1,70 @@ +#include "gamepad/screens/alertScreen.hpp" +#include "gamepad/todo.hpp" +#include "pros/rtos.hpp" +#include +#include +#include +#include +#include +#include + +namespace gamepad { + +ScreenBuffer AlertScreen::getScreen(std::set visible_lines) { + std::lock_guard guard(m_mutex); + if (m_screen_contents.has_value()) { + m_screen_contents->screen.at(3) = std::nullopt; + return m_screen_contents->screen; + } + if (m_screen_buffer.size() < 1) return ScreenBuffer(); + + for (uint8_t i = 0; i < 4; i++) { + if (!m_screen_buffer[0].screen[i].has_value()) continue; + if (m_screen_buffer[0].screen[i].has_value() && !visible_lines.contains(i)) return ScreenBuffer(); + } + m_screen_contents = std::move(m_screen_buffer[0]); + m_screen_buffer.pop_front(); + m_line_set_time = pros::millis(); + return m_screen_contents->screen; +} + +void AlertScreen::update(uint32_t delta_time) { + std::lock_guard guard(m_mutex); + if (pros::millis() - m_line_set_time >= m_screen_contents->duration) m_screen_contents = std::nullopt; +} + +int32_t AlertScreen::addAlerts(uint8_t line, std::string str, uint32_t duration, std::string rumble) { + int32_t ret_val = 0; + if (line > 2) { + TODO("add error logging") + errno = EINVAL; + return INT32_MAX; + } + + if (std::ranges::count(str, '\n') > 2) { + TODO("add warn logging") + errno = EMSGSIZE; + ret_val = INT32_MAX; + } + + std::vector strs(3, ""); + std::stringstream ss(str); + + // split string by newlines but only take the first 3 lines + for (int i = line; i < 3; i++) { + if (!std::getline(ss, strs[i], '\n')) break; + } + + ScreenBuffer buffer; + + if (strs[0] != "") buffer[0] = std::move(strs[0]); + if (strs[1] != "") buffer[1] = std::move(strs[1]); + if (strs[2] != "") buffer[2] = std::move(strs[2]); + if (rumble != "") buffer[3] = std::move(rumble); + + std::lock_guard guard(m_mutex); + m_screen_buffer.push_back({buffer, duration}); + return ret_val; +} + +} // namespace gamepad diff --git a/src/gamepad/screens/defaultScreen.cpp b/src/gamepad/screens/defaultScreen.cpp new file mode 100644 index 0000000..70941b1 --- /dev/null +++ b/src/gamepad/screens/defaultScreen.cpp @@ -0,0 +1,79 @@ +#include "gamepad/screens/defaultScreen.hpp" +#include "gamepad/screens/abstractScreen.hpp" +#include "gamepad/todo.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace gamepad { + +ScreenBuffer DefaultScreen::getScreen(std::set visible_lines) { + ScreenBuffer output; + const std::lock_guard guard(m_mutex); + + for (auto i = visible_lines.begin(); i != visible_lines.end(); ++i) { + output[*i] = std::move(m_current_buffer[*i]); + m_current_buffer[*i] = std::nullopt; + } + return output; +} + +int32_t DefaultScreen::printLine(uint8_t line, std::string str) { + int32_t ret_val = 0; + if (line > 2) { + TODO("add error logging") + errno = EINVAL; + return INT32_MAX; + } + + const std::lock_guard guard(m_mutex); + + if (str.find('\n') != std::string::npos) { + if (std::ranges::count(str, '\n') > 2) { + TODO("add warn logging for too many lines") + errno = EMSGSIZE; + ret_val = INT32_MAX; + } + + std::vector strs(3); + std::stringstream ss(str); + + for (int i = line; i < 3; i++) { + if (!std::getline(ss, strs[i], '\n')) break; + } + + for (uint8_t l = 0; l < 3; l++) { + if (!strs[l].empty()) m_current_buffer[l] = (strs[l]); + } + return ret_val; + } + + m_current_buffer[line] = std::move(str); + return ret_val; +} + +int32_t DefaultScreen::rumble(std::string rumble_pattern) { + int32_t ret_val = 0; + if (rumble_pattern.size() > 8) { + TODO("add error logging") + errno = EMSGSIZE; + ret_val = INT32_MAX; + rumble_pattern.resize(8); + } + + if (rumble_pattern.find_first_not_of(".- ") != std::string::npos) { + TODO("add error logging") + errno = EINVAL; + return INT32_MAX; + } + + std::lock_guard guard(m_mutex); + m_current_buffer[3] = std::move(rumble_pattern); + return ret_val; +} + +} // namespace gamepad \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b0bef76..27cfe8d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,14 @@ #include "main.h" #include "gamepad/api.hpp" -#include "gamepad/controller.hpp" +#include "gamepad/gamepad.hpp" +#include "gamepad/screens/alertScreen.hpp" #include "pros/rtos.hpp" -#include #include +#include +#include + +// initialize the alerts screen so we can have alerts on the controller +std::shared_ptr alerts = std::make_shared(); /** * Runs initialization code. This occurs as soon as the program is started. @@ -11,47 +16,33 @@ * All other competition modes are blocked by initialize; it is recommended * to keep execution time for this mode under a few seconds. */ +void initialize() { + // VERY IMPORTANT, this actually adds the alerts screen to the gamepad + // it wouldn't work without this line + gamepad::master.addScreen(alerts); -uint32_t last_repeat_press_time = pros::millis(); - -void downPress1() { printf("Down Press!\n"); } - -void upRelease1() { printf("Up Release!\n"); } - -void leftLongPress1() { printf("Left Long Press!\n"); } + // When the A button is pressed, schedule an alert that spans all three + // lines, lasts 3 seconds and rumbles in a long-short-long pattern + gamepad::master.buttonA().onPress("alert", []() { + alerts->addAlerts(0, "a very\nimportant alert\nat " + std::to_string(pros::millis()) + " ms", 3000, "-.-"); + }); -void leftShortRelease1() { printf("Left Short Release!\n"); } + // Normally print a string on the first and third line without overriding + // the second line when the B button is pressed + gamepad::master.buttonB().onPress( + "print02", []() { gamepad::master.printLine(0, "the time is\n\n" + std::to_string(pros::millis()) + " ms"); }); -void leftLongRelease1() { printf("Left Long Release!\n"); } + // rumbles 3 times for a short duration when the X button is pressed + gamepad::master.buttonX().onPress("rumble", []() { gamepad::master.rumble("..."); }); -void aPress1() { - last_repeat_press_time = pros::millis(); - printf("A Pressed!\n"); -} + // when the Y button is pressed and held the text should show up, and when + // the button is released it should be cleared + gamepad::master.buttonY().onPress("print1", []() { gamepad::master.printLine(1, "this should be cleared"); }); + gamepad::master.buttonY().onRelease("clear1", []() { gamepad::master.clear(1); }); -void aRepeatPress1() { - printf("A Repeat Pressed %ims after last\n", pros::millis() - last_repeat_press_time); - last_repeat_press_time = pros::millis(); -} - -void initialize() { - // We can register functions to run when buttons are pressed - gamepad::master.Down.onPress("downPress1", downPress1); - // ...or when they're released - gamepad::master.Up.onRelease("downRelease1", upRelease1); - // There's also the longPress event - gamepad::master.Left.onLongPress("leftLongPress1", leftLongPress1); - // We can have two or even more functions on one button, - // just remember to give them different names - gamepad::master.Left.onShortRelease("leftShortRelease", leftShortRelease1); - gamepad::master.Left.onLongRelease("leftLongRelease", leftLongRelease1); - // We also have the repeat press event, where we can adjust the timing - gamepad::master.A.set_long_press_threshold(1000); // in ms - gamepad::master.A.set_repeat_cooldown(100); // in ms - gamepad::master.A.onPress("aStartPress", aPress1); - gamepad::master.A.onRepeatPress("aRepeatPress", aRepeatPress1); - // And we can use lambda's too - gamepad::master.X.onShortRelease("xShortRelease1", []() { printf("X Short Release!\n"); }); + // set up controller curves: + gamepad::master.set_left_transform( + gamepad::TransformationBuilder(gamepad::Deadband(0.05, 0.05)).and_then(gamepad::ExpoCurve(2, 2))); } /** @@ -101,15 +92,14 @@ void autonomous() {} void opcontrol() { pros::MotorGroup left_mg({1, -2, 3}); // Creates a motor group with forwards ports 1 & 3 and reversed port 2 pros::MotorGroup right_mg({-4, 5, -6}); // Creates a motor group with forwards port 4 and reversed ports 4 & 6 - while (true) { // Remember to ALWAYS call update at the start of your while loop! gamepad::master.update(); // We'll use the arcade control scheme - int dir = gamepad::master.LeftY; // Gets amount forward/backward from left joystick - int turn = gamepad::master.RightX; // Gets the turn left/right from right joystick + int dir = gamepad::master.axisLeftY() * 127; // Gets amount forward/backward from left joystick + int turn = gamepad::master.axisRightX() * 127; // Gets the turn left/right from right joystick left_mg.move(dir - turn); // Sets left motor voltage right_mg.move(dir + turn); // Sets right motor voltage pros::delay(25); // Wait for 25 ms, then update the motor values again } -} \ No newline at end of file +}