/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once #include "okapi/api/control/async/asyncController.hpp" #include "okapi/api/control/controllerInput.hpp" #include "okapi/api/control/iterative/iterativeController.hpp" #include "okapi/api/control/util/settledUtil.hpp" #include "okapi/api/coreProsAPI.hpp" #include "okapi/api/util/abstractRate.hpp" #include "okapi/api/util/logging.hpp" #include "okapi/api/util/mathUtil.hpp" #include "okapi/api/util/supplier.hpp" #include #include namespace okapi { template class AsyncWrapper : virtual public AsyncController { public: /** * A wrapper class that transforms an `IterativeController` into an `AsyncController` by running * it in another task. The input controller will act like an `AsyncController`. * * @param iinput controller input, passed to the `IterativeController` * @param ioutput controller output, written to from the `IterativeController` * @param icontroller the controller to use * @param irateSupplier used for rates used in the main loop and in `waitUntilSettled` * @param iratio Any external gear ratio. * @param ilogger The logger this instance will log to. */ AsyncWrapper(const std::shared_ptr> &iinput, const std::shared_ptr> &ioutput, const std::shared_ptr> &icontroller, const Supplier> &irateSupplier, const double iratio = 1, std::shared_ptr ilogger = Logger::getDefaultLogger()) : logger(std::move(ilogger)), rateSupplier(irateSupplier), input(iinput), output(ioutput), controller(icontroller), ratio(iratio) { } AsyncWrapper(AsyncWrapper &&other) = delete; AsyncWrapper &operator=(AsyncWrapper &&other) = delete; ~AsyncWrapper() override { dtorCalled.store(true, std::memory_order_release); delete task; } /** * Sets the target for the controller. */ void setTarget(const Input itarget) override { LOG_INFO("AsyncWrapper: Set target to " + std::to_string(itarget)); hasFirstTarget = true; controller->setTarget(itarget * ratio); lastTarget = itarget; } /** * Writes the value of the controller output. This method might be automatically called in another * thread by the controller. * * @param ivalue the controller's output */ void controllerSet(const Input ivalue) override { controller->controllerSet(ivalue); } /** * Gets the last set target, or the default target if none was set. * * @return the last target */ Input getTarget() override { return controller->getTarget(); } /** * @return The most recent value of the process variable. */ Input getProcessValue() const override { return controller->getProcessValue(); } /** * Returns the last calculated output of the controller. */ Output getOutput() const { return controller->getOutput(); } /** * Returns the last error of the controller. Does not update when disabled. */ Output getError() const override { return controller->getError(); } /** * Returns whether the controller has settled at the target. Determining what settling means is * implementation-dependent. * * If the controller is disabled, this method must return true. * * @return whether the controller is settled */ bool isSettled() override { return isDisabled() || controller->isSettled(); } /** * Set time between loops. * * @param isampleTime time between loops */ void setSampleTime(const QTime &isampleTime) { controller->setSampleTime(isampleTime); } /** * Set controller output bounds. * * @param imax max output * @param imin min output */ void setOutputLimits(const Output imax, const Output imin) { controller->setOutputLimits(imax, imin); } /** * Sets the (soft) limits for the target range that controllerSet() scales into. The target * computed by controllerSet() is scaled into the range [-itargetMin, itargetMax]. * * @param itargetMax The new max target for controllerSet(). * @param itargetMin The new min target for controllerSet(). */ void setControllerSetTargetLimits(double itargetMax, double itargetMin) { controller->setControllerSetTargetLimits(itargetMax, itargetMin); } /** * Get the upper output bound. * * @return the upper output bound */ Output getMaxOutput() { return controller->getMaxOutput(); } /** * Get the lower output bound. * * @return the lower output bound */ Output getMinOutput() { return controller->getMinOutput(); } /** * Resets the controller's internal state so it is similar to when it was first initialized, while * keeping any user-configured information. */ void reset() override { LOG_INFO_S("AsyncWrapper: Reset"); controller->reset(); hasFirstTarget = false; } /** * Changes whether the controller is off or on. Turning the controller on after it was off will * cause the controller to move to its last set target, unless it was reset in that time. */ void flipDisable() override { LOG_INFO("AsyncWrapper: flipDisable " + std::to_string(!controller->isDisabled())); controller->flipDisable(); resumeMovement(); } /** * Sets whether the controller is off or on. Turning the controller on after it was off will * cause the controller to move to its last set target, unless it was reset in that time. * * @param iisDisabled whether the controller is disabled */ void flipDisable(const bool iisDisabled) override { LOG_INFO("AsyncWrapper: flipDisable " + std::to_string(iisDisabled)); controller->flipDisable(iisDisabled); resumeMovement(); } /** * Returns whether the controller is currently disabled. * * @return whether the controller is currently disabled */ bool isDisabled() const override { return controller->isDisabled(); } /** * Blocks the current task until the controller has settled. Determining what settling means is * implementation-dependent. */ void waitUntilSettled() override { LOG_INFO_S("AsyncWrapper: Waiting to settle"); auto rate = rateSupplier.get(); while (!isSettled()) { rate->delayUntil(motorUpdateRate); } LOG_INFO_S("AsyncWrapper: Done waiting to settle"); } /** * Starts the internal thread. This should not be called by normal users. This method is called * by the AsyncControllerFactory when making a new instance of this class. */ void startThread() { if (!task) { task = new CrossplatformThread(trampoline, this, "AsyncWrapper"); } } /** * Returns the underlying thread handle. * * @return The underlying thread handle. */ CrossplatformThread *getThread() const { return task; } protected: std::shared_ptr logger; Supplier> rateSupplier; std::shared_ptr> input; std::shared_ptr> output; std::shared_ptr> controller; bool hasFirstTarget{false}; Input lastTarget; double ratio; std::atomic_bool dtorCalled{false}; CrossplatformThread *task{nullptr}; static void trampoline(void *context) { if (context) { static_cast(context)->loop(); } } void loop() { auto rate = rateSupplier.get(); while (!dtorCalled.load(std::memory_order_acquire) && !task->notifyTake(0)) { if (!isDisabled()) { output->controllerSet(controller->step(input->controllerGet())); } rate->delayUntil(controller->getSampleTime()); } } /** * Resumes moving after the controller is reset. Should not cause movement if the controller is * turned off, reset, and turned back on. */ virtual void resumeMovement() { if (isDisabled()) { // This will grab the output *when disabled* output->controllerSet(controller->getOutput()); } else { if (hasFirstTarget) { setTarget(lastTarget); } } } }; } // namespace okapi