Implemented new command-response-based interface for the TargetController

This commit is contained in:
Nav
2022-04-17 23:55:34 +01:00
parent 8d26340c41
commit 13f5c13065
9 changed files with 440 additions and 111 deletions

View File

@@ -0,0 +1,68 @@
#pragma once
#include <memory>
#include <chrono>
#include <optional>
#include "Commands/Command.hpp"
#include "Responses/Response.hpp"
#include "Responses/Error.hpp"
#include "TargetControllerComponent.hpp"
#include "src/Exceptions/Exception.hpp"
#include "src/Logger/Logger.hpp"
namespace Bloom::TargetController
{
class CommandManager
{
public:
template<class CommandType>
requires
std::is_base_of_v<Commands::Command, CommandType>
&& std::is_base_of_v<Responses::Response, typename CommandType::SuccessResponseType>
auto sendCommandAndWaitForResponse(
std::unique_ptr<CommandType> command,
std::chrono::milliseconds timeout
) {
using SuccessResponseType = typename CommandType::SuccessResponseType;
Logger::debug("Issuing " + CommandType::name + " command to TargetController");
const auto commandId = command->id;
TargetControllerComponent::registerCommand(std::move(command));
auto optionalResponse = TargetControllerComponent::waitForResponse(commandId, timeout);
if (!optionalResponse.has_value()) {
Logger::debug(
"Timed out whilst waiting for TargetController to respond to " + CommandType::name + " command"
);
throw Exceptions::Exception("Command timed out");
}
auto& response = optionalResponse.value();
if (response->getType() == Responses::ResponseType::ERROR) {
const auto errorResponse = dynamic_cast<Responses::Error*>(response.get());
Logger::debug(
"TargetController returned error in response to " + CommandType::name + " command. Error: "
+ errorResponse->errorMessage
);
throw Exceptions::Exception(errorResponse->errorMessage);
}
// Only downcast if the command's SuccessResponseType is not the generic Response type.
if constexpr (!std::is_same_v<SuccessResponseType, Responses::Response>) {
assert(response->getType() == SuccessResponseType::type);
return std::unique_ptr<SuccessResponseType>(
dynamic_cast<SuccessResponseType*>(response.release())
);
}
return std::move(response);
}
};
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <string>
#include <atomic>
#include <cstdint>
#include "CommandTypes.hpp"
#include "src/TargetController/Responses/Response.hpp"
namespace Bloom::TargetController::Commands
{
using CommandIdType = int;
static_assert(std::atomic<CommandIdType>::is_always_lock_free);
class Command
{
public:
using SuccessResponseType = Responses::Response;
CommandIdType id = ++(Command::lastCommandId);
static constexpr CommandType type = CommandType::GENERIC;
static inline const std::string name = "GenericCommand";
Command() = default;
virtual ~Command() = default;
Command(const Command& other) = default;
Command(Command&& other) = default;
Command& operator = (const Command& other) = default;
Command& operator = (Command&& other) = default;
[[nodiscard]] virtual CommandType getType() const {
return Command::type;
}
private:
static inline std::atomic<CommandIdType> lastCommandId = 0;
};
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include <cstdint>
namespace Bloom::TargetController::Commands
{
enum class CommandType: std::uint8_t
{
GENERIC,
};
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "Response.hpp"
namespace Bloom::TargetController::Responses
{
class Error: public Response
{
public:
static constexpr ResponseType type = ResponseType::ERROR;
std::string errorMessage;
explicit Error(const std::string& errorMessage)
: errorMessage(errorMessage)
{}
[[nodiscard]] ResponseType getType() const override {
return Error::type;
}
};
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "ResponseTypes.hpp"
namespace Bloom::TargetController::Responses
{
class Response
{
public:
static constexpr ResponseType type = ResponseType::GENERIC;
Response() = default;
virtual ~Response() = default;
Response(const Response& other) = default;
Response(Response&& other) = default;
Response& operator = (const Response& other) = default;
Response& operator = (Response&& other) = default;
[[nodiscard]] virtual ResponseType getType() const {
return Response::type;
}
};
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include <cstdint>
namespace Bloom::TargetController::Responses
{
enum class ResponseType: std::uint8_t
{
GENERIC,
ERROR,
};
}

View File

@@ -20,6 +20,9 @@ namespace Bloom::TargetController
using namespace Bloom::Events; using namespace Bloom::Events;
using namespace Bloom::Exceptions; using namespace Bloom::Exceptions;
using Commands::Command;
using Commands::CommandIdType;
using Responses::Response;
TargetControllerComponent::TargetControllerComponent( TargetControllerComponent::TargetControllerComponent(
const ProjectConfig& projectConfig, const ProjectConfig& projectConfig,
const EnvironmentConfig& environmentConfig const EnvironmentConfig& environmentConfig
@@ -41,7 +44,10 @@ namespace Bloom::TargetController
this->fireTargetEvents(); this->fireTargetEvents();
} }
this->eventListener->waitAndDispatch(60); TargetControllerComponent::notifier.waitForNotification(std::chrono::milliseconds(60));
this->processQueuedCommands();
this->eventListener->dispatchCurrentEvents();
} catch (const DeviceFailure& exception) { } catch (const DeviceFailure& exception) {
/* /*
@@ -76,6 +82,48 @@ namespace Bloom::TargetController
this->shutdown(); this->shutdown();
} }
void TargetControllerComponent::registerCommand(std::unique_ptr<Command> command) {
auto commandQueueLock = TargetControllerComponent::commandQueue.acquireLock();
TargetControllerComponent::commandQueue.getValue().push(std::move(command));
TargetControllerComponent::notifier.notify();
}
std::optional<std::unique_ptr<Responses::Response>> TargetControllerComponent::waitForResponse(
CommandIdType commandId,
std::optional<std::chrono::milliseconds> timeout
) {
auto response = std::unique_ptr<Response>(nullptr);
const auto predicate = [commandId, &response] {
auto& responsesByCommandId = TargetControllerComponent::responsesByCommandId.getValue();
auto responseIt = responsesByCommandId.find(commandId);
if (responseIt != responsesByCommandId.end()) {
response.swap(responseIt->second);
responsesByCommandId.erase(responseIt);
return true;
}
return false;
};
auto responsesByCommandIdLock = TargetControllerComponent::responsesByCommandId.acquireLock();
if (timeout.has_value()) {
TargetControllerComponent::responsesByCommandIdCv.wait_for(
responsesByCommandIdLock,
timeout.value(),
predicate
);
} else {
TargetControllerComponent::responsesByCommandIdCv.wait(responsesByCommandIdLock, predicate);
}
return (response != nullptr) ? std::optional(std::move(response)) : std::nullopt;
}
void TargetControllerComponent::startup() { void TargetControllerComponent::startup() {
this->setName("TC"); this->setName("TC");
Logger::info("Starting TargetController"); Logger::info("Starting TargetController");
@@ -102,6 +150,151 @@ namespace Bloom::TargetController
this->resume(); this->resume();
} }
std::map<
std::string,
std::function<std::unique_ptr<DebugTool>()>
> TargetControllerComponent::getSupportedDebugTools() {
return std::map<std::string, std::function<std::unique_ptr<DebugTool>()>> {
{
"atmel-ice",
[] {
return std::make_unique<DebugToolDrivers::AtmelIce>();
}
},
{
"power-debugger",
[] {
return std::make_unique<DebugToolDrivers::PowerDebugger>();
}
},
{
"snap",
[] {
return std::make_unique<DebugToolDrivers::MplabSnap>();
}
},
{
"pickit-4",
[] {
return std::make_unique<DebugToolDrivers::MplabPickit4>();
}
},
{
"xplained-pro",
[] {
return std::make_unique<DebugToolDrivers::XplainedPro>();
}
},
{
"xplained-mini",
[] {
return std::make_unique<DebugToolDrivers::XplainedMini>();
}
},
{
"xplained-nano",
[] {
return std::make_unique<DebugToolDrivers::XplainedNano>();
}
},
{
"curiosity-nano",
[] {
return std::make_unique<DebugToolDrivers::CuriosityNano>();
}
},
};
}
std::map<
std::string,
std::function<std::unique_ptr<Targets::Target>()>
> TargetControllerComponent::getSupportedTargets() {
using Avr8TargetDescriptionFile = Targets::Microchip::Avr::Avr8Bit::TargetDescription::TargetDescriptionFile;
auto mapping = std::map<std::string, std::function<std::unique_ptr<Targets::Target>()>>({
{
"avr8",
[] {
return std::make_unique<Targets::Microchip::Avr::Avr8Bit::Avr8>();
}
},
});
// Include all targets from AVR8 target description files
auto avr8PdMapping = Avr8TargetDescriptionFile::getTargetDescriptionMapping();
for (auto mapIt = avr8PdMapping.begin(); mapIt != avr8PdMapping.end(); mapIt++) {
// Each target signature maps to an array of targets, as numerous targets can possess the same signature.
auto targets = mapIt.value().toArray();
for (auto targetIt = targets.begin(); targetIt != targets.end(); targetIt++) {
auto targetName = targetIt->toObject().find("targetName").value().toString()
.toLower().toStdString();
auto targetSignatureHex = mapIt.key().toLower().toStdString();
if (!mapping.contains(targetName)) {
mapping.insert({
targetName,
[targetName, targetSignatureHex] {
return std::make_unique<Targets::Microchip::Avr::Avr8Bit::Avr8>(
targetName,
Targets::Microchip::Avr::TargetSignature(targetSignatureHex)
);
}
});
}
}
}
return mapping;
}
void TargetControllerComponent::processQueuedCommands() {
auto commands = std::queue<std::unique_ptr<Command>>();
{
auto queueLock = TargetControllerComponent::commandQueue.acquireLock();
commands.swap(TargetControllerComponent::commandQueue.getValue());
}
while (!commands.empty()) {
const auto command = std::move(commands.front());
commands.pop();
const auto commandId = command->id;
const auto commandType = command->getType();
try {
if (!this->commandHandlersByCommandType.contains(commandType)) {
throw Exception("No handler registered for this command.");
}
this->registerCommandResponse(
commandId,
this->commandHandlersByCommandType.at(commandType)(*(command.get()))
);
} catch (const Exception& exception) {
this->registerCommandResponse(
commandId,
std::make_unique<Responses::Error>(exception.getMessage())
);
}
}
}
void TargetControllerComponent::registerCommandResponse(
CommandIdType commandId,
std::unique_ptr<Response> response
) {
auto responseMappingLock = TargetControllerComponent::responsesByCommandId.acquireLock();
TargetControllerComponent::responsesByCommandId.getValue().insert(
std::pair(commandId, std::move(response))
);
TargetControllerComponent::responsesByCommandIdCv.notify_all();
}
void TargetControllerComponent::checkUdevRules() { void TargetControllerComponent::checkUdevRules() {
auto bloomRulesPath = std::string("/etc/udev/rules.d/99-bloom.rules"); auto bloomRulesPath = std::string("/etc/udev/rules.d/99-bloom.rules");
auto latestBloomRulesPath = Paths::resourcesDirPath() + "/UDevRules/99-bloom.rules"; auto latestBloomRulesPath = Paths::resourcesDirPath() + "/UDevRules/99-bloom.rules";
@@ -283,8 +476,8 @@ namespace Bloom::TargetController
auto debugToolName = this->environmentConfig.debugToolConfig.name; auto debugToolName = this->environmentConfig.debugToolConfig.name;
auto targetName = this->environmentConfig.targetConfig.name; auto targetName = this->environmentConfig.targetConfig.name;
auto supportedDebugTools = TargetControllerComponent::getSupportedDebugTools(); static auto supportedDebugTools = this->getSupportedDebugTools();
auto supportedTargets = TargetControllerComponent::getSupportedTargets(); static auto supportedTargets = this->getSupportedTargets();
if (!supportedDebugTools.contains(debugToolName)) { if (!supportedDebugTools.contains(debugToolName)) {
throw Exceptions::InvalidConfig( throw Exceptions::InvalidConfig(

View File

@@ -1,6 +1,10 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <queue>
#include <condition_variable>
#include <optional>
#include <chrono>
#include <map> #include <map>
#include <string> #include <string>
#include <functional> #include <functional>
@@ -8,6 +12,15 @@
#include <QJsonArray> #include <QJsonArray>
#include "src/Helpers/Thread.hpp" #include "src/Helpers/Thread.hpp"
#include "src/Helpers/SyncSafe.hpp"
#include "src/Helpers/ConditionVariableNotifier.hpp"
// Commands
#include "Commands/Command.hpp"
// Responses
#include "Responses/Response.hpp"
#include "TargetControllerState.hpp" #include "TargetControllerState.hpp"
#include "src/DebugToolDrivers/DebugTools.hpp" #include "src/DebugToolDrivers/DebugTools.hpp"
@@ -44,7 +57,24 @@ namespace Bloom::TargetController
*/ */
void run(); void run();
static void registerCommand(std::unique_ptr<Commands::Command> command);
static std::optional<std::unique_ptr<Responses::Response>> waitForResponse(
Commands::CommandIdType commandId,
std::optional<std::chrono::milliseconds> timeout = std::nullopt
);
private: private:
static inline SyncSafe<
std::queue<std::unique_ptr<Commands::Command>>
> commandQueue;
static inline SyncSafe<
std::map<Commands::CommandIdType, std::unique_ptr<Responses::Response>>
> responsesByCommandId;
static inline ConditionVariableNotifier notifier = ConditionVariableNotifier();
static inline std::condition_variable responsesByCommandIdCv = std::condition_variable();
/** /**
* The TC starts off in a suspended state. TargetController::resume() is invoked from the startup routine. * The TC starts off in a suspended state. TargetController::resume() is invoked from the startup routine.
*/ */
@@ -61,6 +91,11 @@ namespace Bloom::TargetController
std::unique_ptr<Targets::Target> target = nullptr; std::unique_ptr<Targets::Target> target = nullptr;
std::unique_ptr<DebugTool> debugTool = nullptr; std::unique_ptr<DebugTool> debugTool = nullptr;
std::map<
Commands::CommandType,
std::function<std::unique_ptr<Responses::Response>(Commands::Command&)>
> commandHandlersByCommandType;
EventListenerPointer eventListener = std::make_shared<EventListener>("TargetControllerEventListener"); EventListenerPointer eventListener = std::make_shared<EventListener>("TargetControllerEventListener");
/** /**
@@ -85,116 +120,14 @@ namespace Bloom::TargetController
*/ */
std::map<Targets::TargetMemoryType, Targets::TargetMemoryAddressRange> registerAddressRangeByMemoryType; std::map<Targets::TargetMemoryType, Targets::TargetMemoryAddressRange> registerAddressRangeByMemoryType;
/** template<class CommandType>
* Constructs a mapping of supported debug tool names to lambdas. The lambdas should *only* instantiate void registerCommandHandler(std::function<std::unique_ptr<Responses::Response>(CommandType&)> callback) {
* and return an instance to the derived DebugTool class. They should not attempt to establish auto parentCallback = [callback] (Commands::Command& command) {
* a connection to the device. // Downcast the command to the expected type
* return callback(dynamic_cast<CommandType&>(command));
* @return
*/
static auto getSupportedDebugTools() {
static auto mapping = std::map<std::string, std::function<std::unique_ptr<DebugTool>()>> {
{
"atmel-ice",
[] {
return std::make_unique<DebugToolDrivers::AtmelIce>();
}
},
{
"power-debugger",
[] {
return std::make_unique<DebugToolDrivers::PowerDebugger>();
}
},
{
"snap",
[] {
return std::make_unique<DebugToolDrivers::MplabSnap>();
}
},
{
"pickit-4",
[] {
return std::make_unique<DebugToolDrivers::MplabPickit4>();
}
},
{
"xplained-pro",
[] {
return std::make_unique<DebugToolDrivers::XplainedPro>();
}
},
{
"xplained-mini",
[] {
return std::make_unique<DebugToolDrivers::XplainedMini>();
}
},
{
"xplained-nano",
[] {
return std::make_unique<DebugToolDrivers::XplainedNano>();
}
},
{
"curiosity-nano",
[] {
return std::make_unique<DebugToolDrivers::CuriosityNano>();
}
},
}; };
return mapping; this->commandHandlersByCommandType.insert(std::pair(CommandType::type, parentCallback));
}
/**
* Constructs a mapping of supported target names to lambdas. The lambdas should instantiate and return an
* instance to the appropriate Target class.
*
* @return
*/
static auto getSupportedTargets() {
static auto mapping = std::map<std::string, std::function<std::unique_ptr<Targets::Target>()>>();
if (mapping.empty()) {
mapping = {
{
"avr8",
[] {
return std::make_unique<Targets::Microchip::Avr::Avr8Bit::Avr8>();
}
},
};
// Include all targets from AVR8 target description files
auto avr8PdMapping =
Targets::Microchip::Avr::Avr8Bit::TargetDescription::TargetDescriptionFile::getTargetDescriptionMapping();
for (auto mapIt = avr8PdMapping.begin(); mapIt != avr8PdMapping.end(); mapIt++) {
// Each target signature maps to an array of targets, as numerous targets can possess the same signature.
auto targets = mapIt.value().toArray();
for (auto targetIt = targets.begin(); targetIt != targets.end(); targetIt++) {
auto targetName = targetIt->toObject().find("targetName").value().toString()
.toLower().toStdString();
auto targetSignatureHex = mapIt.key().toLower().toStdString();
if (!mapping.contains(targetName)) {
mapping.insert({
targetName,
[targetName, targetSignatureHex] {
return std::make_unique<Targets::Microchip::Avr::Avr8Bit::Avr8>(
targetName,
Targets::Microchip::Avr::TargetSignature(targetSignatureHex)
);
}
});
}
}
}
}
return mapping;
} }
/** /**
@@ -215,6 +148,27 @@ namespace Bloom::TargetController
*/ */
void startup(); void startup();
/**
* Constructs a mapping of supported debug tool names to lambdas. The lambdas should *only* instantiate
* and return an instance to the derived DebugTool class. They should not attempt to establish
* a connection to the device.
*
* @return
*/
std::map<std::string, std::function<std::unique_ptr<DebugTool>()>> getSupportedDebugTools();
/**
* Constructs a mapping of supported target names to lambdas. The lambdas should instantiate and return an
* instance to the appropriate Target class.
*
* @return
*/
std::map<std::string, std::function<std::unique_ptr<Targets::Target>()>> getSupportedTargets();
void processQueuedCommands();
void registerCommandResponse(Commands::CommandIdType commandId, std::unique_ptr<Responses::Response> response);
/** /**
* Installs Bloom's udev rules on user's machine. Rules are copied from build/Distribution/Resources/UdevRules * Installs Bloom's udev rules on user's machine. Rules are copied from build/Distribution/Resources/UdevRules
* to /etc/udev/rules.d/. This method will report an error if Bloom isn't running as root (as root privileges * to /etc/udev/rules.d/. This method will report an error if Bloom isn't running as root (as root privileges

View File

@@ -3,6 +3,7 @@
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
#include "CommandManager.hpp"
#include "TargetControllerState.hpp" #include "TargetControllerState.hpp"
#include "src/EventManager/EventListener.hpp" #include "src/EventManager/EventListener.hpp"
@@ -171,6 +172,7 @@ namespace Bloom::TargetController
void resetTarget(); void resetTarget();
private: private:
CommandManager commandManager = CommandManager();
EventListener& eventListener; EventListener& eventListener;
std::chrono::milliseconds defaultTimeout = std::chrono::milliseconds(20000); std::chrono::milliseconds defaultTimeout = std::chrono::milliseconds(20000);