Program Listing for File command_scheduler.cpp

Return to documentation for file (src/tap/control/command_scheduler.cpp)

/*
 * Copyright (c) 2020-2021 Advanced Robotics at the University of Washington <robomstr@uw.edu>
 *
 * This file is part of Taproot.
 *
 * Taproot is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Taproot is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Taproot.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "command_scheduler.hpp"

#include "tap/architecture/clock.hpp"
#include "tap/drivers.hpp"
#include "tap/errors/create_errors.hpp"

#include "modm/architecture/interface/assert.hpp"

#include "command.hpp"
#include "subsystem.hpp"

using namespace tap::errors;

namespace tap
{
namespace control
{
bool CommandScheduler::masterSchedulerExists = false;
Subsystem *CommandScheduler::globalSubsystemRegistrar[CommandScheduler::MAX_SUBSYSTEM_COUNT];
Command *CommandScheduler::globalCommandRegistrar[CommandScheduler::MAX_COMMAND_COUNT];
int CommandScheduler::maxSubsystemIndex = 0;
int CommandScheduler::maxCommandIndex = 0;
SafeDisconnectFunction CommandScheduler::defaultSafeDisconnectFunction;

int CommandScheduler::constructCommand(Command *command)
{
    modm_assert(command != nullptr, "CommandScheduer::constructCommand", "called with nullptr cmd");

    modm_assert(
        maxCommandIndex < MAX_COMMAND_COUNT,
        "CommandScheduler::constructCommand",
        "Too many commands constructed!");

    // Loop through the globalCommandRegistrar, find the lowest nullptr index
    for (int i = 0; i < MAX_COMMAND_COUNT; i++)
    {
        if (globalCommandRegistrar[i] == nullptr)
        {
            // Update max index if need be
            maxCommandIndex = std::max(maxCommandIndex, i + 1);
            globalCommandRegistrar[i] = command;
            return i;
        }
    }

    // Will never happen
    return -1;
}

int CommandScheduler::constructSubsystem(Subsystem *subsystem)
{
    modm_assert(
        subsystem != nullptr,
        "CommandScheduer::constructSubsystem",
        "called with nullptr sub");

    modm_assert(
        maxSubsystemIndex < MAX_SUBSYSTEM_COUNT,
        "CommandScheduler::constructSubsystem",
        "Too many subsystems constructed!");

    // Loop through the globalSubsystemRegistrar, find the lowest nullptr index
    for (int i = 0; i < MAX_SUBSYSTEM_COUNT; i++)
    {
        if (globalSubsystemRegistrar[i] == nullptr)
        {
            // Update max index if need be
            maxSubsystemIndex = std::max(maxSubsystemIndex, i + 1);
            globalSubsystemRegistrar[i] = subsystem;
            return i;
        }
    }

    // This will never happen
    return -1;
}

void CommandScheduler::destructCommand(Command *command)
{
    modm_assert(command != nullptr, "CommandScheduer::destructCommand", "called with nullptr cmd");

    auto cmdId = command->getGlobalIdentifier();

    modm_assert(
        cmdId >= 0 && cmdId < MAX_COMMAND_COUNT,
        "CommandScheduler::destructCommand",
        "Trying to destruct command with invalid identifier");

    globalCommandRegistrar[cmdId] = nullptr;
    if (cmdId == maxCommandIndex - 1)
    {
        maxCommandIndex--;
    }
}

void CommandScheduler::destructSubsystem(Subsystem *subsystem)
{
    modm_assert(
        subsystem != nullptr,
        "CommandScheduer::destructSubsystem",
        "called with nullptr sub");

    auto subId = subsystem->getGlobalIdentifier();
    modm_assert(
        subId >= 0 && subId < MAX_SUBSYSTEM_COUNT,
        "CommandScheduler::destructSubsystem",
        "Trying to destruct subsystem with invalid identifier");

    globalSubsystemRegistrar[subId] = nullptr;
    if (subId == maxSubsystemIndex - 1)
    {
        maxSubsystemIndex--;
    }
}

CommandScheduler::CommandScheduler(
    Drivers *drivers,
    bool masterScheduler,
    SafeDisconnectFunction *safeDisconnectFunction)
    : drivers(drivers),
      safeDisconnectFunction(safeDisconnectFunction)
{
    if (masterScheduler && masterSchedulerExists)
    {
        RAISE_ERROR(drivers, "master scheduler already exists");
    }
    else
    {
        isMasterScheduler = masterScheduler;
        if (masterScheduler)
        {
            masterSchedulerExists = true;
        }
    }
}

CommandScheduler::~CommandScheduler()
{
    if (isMasterScheduler)
    {
        masterSchedulerExists = false;
    }
}

void CommandScheduler::run()
{
#ifndef PLATFORM_HOSTED
    uint32_t runStart = arch::clock::getTimeMicroseconds();
#endif

    if (safeDisconnected())
    {
        // End all commands running. They were interrupted by the remote disconnecting.
        for (auto it = cmdMapBegin(); it != cmdMapEnd(); it++)
        {
            removeCommand(*it, true);
        }
    }
    else
    {
        // Execute commands in the addedCommandBitmap, remove any that are finished
        for (auto it = cmdMapBegin(); it != cmdMapEnd(); it++)
        {
            (*it)->execute();
            if ((*it)->isFinished())
            {
                removeCommand(*it, false);
            }
        }
    }

    // Only refresh subsystems if this is the master scheduler
    if (isMasterScheduler)
    {
        // Refresh subsystems in the registeredSubsystemBitmap
        for (auto it = subMapBegin(); it != subMapEnd(); it++)
        {
            Command *testCommand;
            if (!safeDisconnected() &&
                !(subsystemsAssociatedWithCommandBitmap &
                  (LSB_ONE_HOT_SUBSYSTEM_BITMAP << (*it)->getGlobalIdentifier())) &&
                (testCommand = (*it)->getTestCommand()) != nullptr)
            {
                if (testCommand->isFinished())
                {
                    this->subsystemsPassingHardwareTests |=
                        (LSB_ONE_HOT_SUBSYSTEM_BITMAP << (*it)->getGlobalIdentifier());
                }
            }

            // Call appropriate refresh function for each of the subsystems
            if (safeDisconnected())
            {
                (*it)->refreshSafeDisconnect();
            }
            else
            {
                (*it)->refresh();
            }

            Command *defaultCmd;
            // If the remote is connected given the scheduler is in safe disconnect mode and
            // the current subsystem does not have an associated command and the current
            // subsystem has a default command, add it
            if (!safeDisconnected() &&
                !(subsystemsAssociatedWithCommandBitmap &
                  (LSB_ONE_HOT_SUBSYSTEM_BITMAP << (*it)->getGlobalIdentifier())) &&
                ((defaultCmd = (*it)->getDefaultCommand()) != nullptr))
            {
                addCommand(defaultCmd);
            }
        }
    }

#ifndef PLATFORM_HOSTED
    // make sure we are not going over tolerable runtime, otherwise something is really
    // wrong with the code
    if (arch::clock::getTimeMicroseconds() - runStart > MAX_ALLOWABLE_SCHEDULER_RUNTIME)
    {
        // shouldn't take more than MAX_ALLOWABLE_SCHEDULER_RUNTIME microseconds
        // to complete all this stuff, if it does something
        // is seriously wrong (i.e. you are adding subsystems unchecked or the scheduler
        // itself is broken).
        RAISE_ERROR(drivers, "scheduler took longer than MAX_ALLOWABLE_SCHEDULER_RUNTIME");
    }
#endif
}

void CommandScheduler::addCommand(Command *commandToAdd)
{
    if (safeDisconnected())
    {
        return;
    }
    else if (commandToAdd == nullptr)
    {
        RAISE_ERROR(drivers, "attempting to add nullptr command");
        return;
    }
    else if (!commandToAdd->isReady())
    {
        // Do not add command if it is not ready to be scheduled.
        return;
    }

    subsystem_scheduler_bitmap_t requirementsBitwise = commandToAdd->getRequirementsBitwise();

    // Check to see if all the requirements are in the subsytemToCommandMap
    if ((requirementsBitwise & registeredSubsystemBitmap) != requirementsBitwise ||
        requirementsBitwise == static_cast<subsystem_scheduler_bitmap_t>(0))
    {
        // the command you are trying to add has a subsystem that is not in the
        // scheduler, so you cannot add it (will lead to undefined control behavior)
        RAISE_ERROR(drivers, "Attempting to add a command without subsystem in the scheduler");
        return;
    }

    // End all commands running that used the subsystem requirements. They were interrupted.
    for (auto it = cmdMapBegin(); it != cmdMapEnd(); it++)
    {
        // Does this command's requierments intersect the new command?
        if (((*it)->getRequirementsBitwise() & requirementsBitwise) !=
            static_cast<subsystem_scheduler_bitmap_t>(0))
        {
            removeCommand(*it, true);
        }
    }

    // Add the subsystem requirements to the subsystems associated with command bitmap
    subsystemsAssociatedWithCommandBitmap |= requirementsBitwise;
    commandToAdd->initialize();
    // Add the command to the command bitmap
    addedCommandBitmap |= LSB_ONE_HOT_COMMAND_BITMAP << commandToAdd->getGlobalIdentifier();
}

bool CommandScheduler::isCommandScheduled(const Command *command) const
{
    return command != nullptr &&
           (addedCommandBitmap & (LSB_ONE_HOT_COMMAND_BITMAP << command->getGlobalIdentifier()));
}

void CommandScheduler::removeCommand(Command *command, bool interrupted)
{
    if (command == nullptr)
    {
        RAISE_ERROR(drivers, "trying to remove nullptr command");
        return;
    }
    else if (!isCommandScheduled(command))
    {
        return;
    }

    command->end(interrupted);

    // Remove all subsystem requirements from the subsystem associated with command bitmap
    subsystemsAssociatedWithCommandBitmap &= ~command->getRequirementsBitwise();

    // Remove the command from the command bitmap
    addedCommandBitmap &= ~(LSB_ONE_HOT_COMMAND_BITMAP << command->getGlobalIdentifier());
}

void CommandScheduler::setSafeDisconnectFunction(SafeDisconnectFunction *func)
{
    this->safeDisconnectFunction = func;
}

bool CommandScheduler::safeDisconnected() { return this->safeDisconnectFunction->operator()(); }

void CommandScheduler::registerSubsystem(Subsystem *subsystem)
{
    if (subsystem == nullptr)
    {
        RAISE_ERROR(drivers, "trying to register nullptr subsystem");
    }
    else if (isSubsystemRegistered(subsystem))
    {
        RAISE_ERROR(drivers, "subsystem is already added");
    }
    else
    {
        // Add the subsystem to the registered subsystem bitmap
        registeredSubsystemBitmap |=
            (LSB_ONE_HOT_SUBSYSTEM_BITMAP << subsystem->getGlobalIdentifier());
    }
}

bool CommandScheduler::isSubsystemRegistered(const Subsystem *subsystem) const
{
    return subsystem != nullptr &&
           ((LSB_ONE_HOT_SUBSYSTEM_BITMAP << subsystem->getGlobalIdentifier()) &
            registeredSubsystemBitmap);
}

void CommandScheduler::runAllHardwareTests()
{
    for (auto it = subMapBegin(); it != subMapEnd(); it++)
    {
        this->runHardwareTest(*it);
    }
}

void CommandScheduler::runHardwareTest(const Subsystem *subsystem)
{
    Command *testCommand = subsystem->getTestCommand();
    if (testCommand != nullptr)
    {
        this->subsystemsPassingHardwareTests &=
            ~(LSB_ONE_HOT_SUBSYSTEM_BITMAP << subsystem->getGlobalIdentifier());
        this->addCommand(testCommand);
    }
}

void CommandScheduler::stopAllHardwareTests()
{
    Command *testCommand;
    // Start hardware tests
    for (auto it = subMapBegin(); it != subMapEnd(); it++)
    {
        // schedule the test command if it exists
        if ((testCommand = (*it)->getTestCommand()) != nullptr)
        {
            this->removeCommand(testCommand, true);
        }
    }
}

void CommandScheduler::stopHardwareTest(const Subsystem *subsystem)
{
    Command *testCommand = subsystem->getTestCommand();
    if (testCommand != nullptr)
    {
        this->removeCommand(testCommand, true);
    }
}

int CommandScheduler::countRunningHardwareTests()
{
    int total = 0;
    for (auto it = subMapBegin(); it != subMapEnd(); it++)
    {
        if (this->isRunningTest(*it))
        {
            total += 1;
        }
    }

    return total;
}

bool CommandScheduler::isRunningTest(const Subsystem *subsystem)
{
    return this->isCommandScheduled(subsystem->getTestCommand());
}

bool CommandScheduler::hasPassedTest(const Subsystem *subsystem)
{
    return (this->subsystemsPassingHardwareTests &
            (LSB_ONE_HOT_SUBSYSTEM_BITMAP << subsystem->getGlobalIdentifier())) != 0;
}

int CommandScheduler::subsystemListSize() const
{
    int size = 0;
    for (int i = 0; i < maxSubsystemIndex; i++)
    {
        if (registeredSubsystemBitmap & (LSB_ONE_HOT_SUBSYSTEM_BITMAP << i))
        {
            size++;
        }
    }
    return size;
}

int CommandScheduler::commandListSize() const
{
    int size = 0;
    for (int i = 0; i < maxCommandIndex; i++)
    {
        if (addedCommandBitmap & (LSB_ONE_HOT_COMMAND_BITMAP << i))
        {
            size++;
        }
    }
    return size;
}

CommandScheduler::CommandIterator CommandScheduler::cmdMapBegin()
{
    return CommandIterator(this, 0);
}

CommandScheduler::CommandIterator CommandScheduler::cmdMapEnd()
{
    return CommandIterator(this, INVALID_ITER_INDEX);
}

CommandScheduler::SubsystemIterator CommandScheduler::subMapBegin()
{
    return SubsystemIterator(this, 0);
}

CommandScheduler::SubsystemIterator CommandScheduler::subMapEnd()
{
    return SubsystemIterator(this, INVALID_ITER_INDEX);
}

CommandScheduler::CommandIterator::CommandIterator(CommandScheduler *scheduler, int i)
    : scheduler(scheduler),
      currIndex(i)
{
    // Set to invalid iterator if the index passed in is invalid
    if (i < 0 || i >= maxCommandIndex)
    {
        currIndex = INVALID_ITER_INDEX;
    }
    else
    {
        // If the curr index is pointing somewhere in the valid range of commands but the command
        // associated with the index is not in the current added commands bitmap, increment the
        // iterator to find the next valid index
        if (!(scheduler->addedCommandBitmap & (LSB_ONE_HOT_COMMAND_BITMAP << currIndex)))
        {
            (*this)++;
        }
    }
}

CommandScheduler::CommandIterator::pointer CommandScheduler::CommandIterator::operator*()
{
    return currIndex == INVALID_ITER_INDEX ? nullptr : globalCommandRegistrar[currIndex];
}

CommandScheduler::CommandIterator &CommandScheduler::CommandIterator::operator++()
{
    if (currIndex == INVALID_ITER_INDEX)
    {
        return *this;
    }

    currIndex++;
    while (currIndex < maxCommandIndex)
    {
        // Is the current index in the bitmap of added commands?
        if (scheduler->addedCommandBitmap & (LSB_ONE_HOT_COMMAND_BITMAP << currIndex))
        {
            // We found the correct index
            return *this;
        }
        currIndex++;
    }
    // We didn't find the correct index, set currindex to invalid index
    currIndex = INVALID_ITER_INDEX;
    return *this;
}

CommandScheduler::CommandIterator CommandScheduler::CommandIterator::operator++(int)
{
    CommandIterator tmp = *this;
    ++(*this);
    return tmp;
}

bool operator==(
    const CommandScheduler::CommandIterator &a,
    const CommandScheduler::CommandIterator &b)
{
    return a.currIndex == b.currIndex && a.scheduler == b.scheduler;
};

bool operator!=(
    const CommandScheduler::CommandIterator &a,
    const CommandScheduler::CommandIterator &b)
{
    return !(a == b);
};

CommandScheduler::SubsystemIterator::SubsystemIterator(CommandScheduler *scheduler, int i)
    : scheduler(scheduler),
      currIndex(i)
{
    // Set to invalid iterator if the index passed in is invalid
    if (currIndex < 0 || currIndex >= maxSubsystemIndex)
    {
        currIndex = INVALID_ITER_INDEX;
    }
    else
    {
        // If the curr index is pointing somewhere in the valid range of subsystems but the
        // subsystem associated with the index is not in the current registered subsystem bitmap,
        // increment the iterator to find the next valid index
        if (!(scheduler->registeredSubsystemBitmap & (LSB_ONE_HOT_SUBSYSTEM_BITMAP << currIndex)))
        {
            (*this)++;
        }
    }
}

CommandScheduler::SubsystemIterator::pointer CommandScheduler::SubsystemIterator::operator*()
{
    return currIndex == INVALID_ITER_INDEX ? nullptr : globalSubsystemRegistrar[currIndex];
}

CommandScheduler::SubsystemIterator &CommandScheduler::SubsystemIterator::operator++()
{
    if (currIndex == INVALID_ITER_INDEX)
    {
        return *this;
    }

    currIndex++;
    while (currIndex < maxSubsystemIndex)
    {
        // Is the current index in the bitmap of added commands?
        if (scheduler->registeredSubsystemBitmap & (LSB_ONE_HOT_SUBSYSTEM_BITMAP << currIndex))
        {
            // We found the correct index
            return *this;
        }
        currIndex++;
    }
    // We didn't find the correct index, set currindex to invalid index
    currIndex = INVALID_ITER_INDEX;
    return *this;
}

CommandScheduler::SubsystemIterator CommandScheduler::SubsystemIterator::operator++(int)
{
    CommandScheduler::SubsystemIterator tmp = *this;
    ++(*this);
    return tmp;
}

bool operator==(
    const CommandScheduler::SubsystemIterator &a,
    const CommandScheduler::SubsystemIterator &b)
{
    return a.currIndex == b.currIndex && a.scheduler == b.scheduler;
}

bool operator!=(
    const CommandScheduler::SubsystemIterator &a,
    const CommandScheduler::SubsystemIterator &b)
{
    return !(a == b);
}
}  // namespace control
}  // namespace tap