Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ set(SOURCES
src/main/CanDeviceConfiguration.cpp
src/main/CanDiagnostics.cpp
src/main/CanDerivedStats.cpp
src/main/dynamicParsing.cpp
)

include(cmake/env.cmake)
Expand Down
4 changes: 4 additions & 0 deletions src/include/CanDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ struct CanDevice {
static std::unique_ptr<CanDevice> create(
std::string_view vendor, const CanDeviceArguments& configuration);

static std::unique_ptr<CanDevice> create_dynamic(
const std::map<std::string, std::string>& parameters,
const std::function<void(const CanFrame&)>& receiver);

protected:
/**
* @brief Constructor for the CanDevice class.
Expand Down
93 changes: 93 additions & 0 deletions src/include/dynamicParsing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#ifndef SRC_INCLUDE_DYNAMICPARSING_H_
#define SRC_INCLUDE_DYNAMICPARSING_H_

#include <functional>
#include <map>
#include <optional>
#include <set>
#include <string>

#include "CanDeviceConfiguration.h"

/**
* @namespace dynamic_parsing
* @brief Namespace containing the helpers used to build a
* CanDeviceConfiguration out of string parameters.
*/
namespace dynamic_parsing {

/**
* @brief Parser of a single configuration parameter.
*
* A parser validates the raw string value, assigns it to the corresponding
* entry of the given configuration and returns std::nullopt. If the value
* cannot be converted to the type of the parameter, the configuration is left
* untouched and the reason of the failure is returned instead.
*/
using ParameterParser = std::function<std::optional<std::string>(
const std::string&, CanDeviceConfiguration&)>;

/**
* @brief Configuration parameters accepted by a vendor.
*
*/
struct VendorParameters {
/**
* @brief Parameters that must be provided.
*/
const std::set<std::string> required;

/**
* @brief Parameters that may be provided.
*/
const std::set<std::string> optional;
};

/**
* @brief Parameters accepted by each vendor supported by
* CanDevice::create_dynamic.
*/
extern const std::map<std::string, VendorParameters> vendor_parameters;

/**
* @brief Parser of each configuration parameter of CanDeviceConfiguration,
* indexed by parameter name.
*/
extern const std::map<std::string, ParameterParser> parameter_parsers;

/**
* @brief Applies simplication rules that depend on more than one
* parameter, making it more user friendly.
*
* @param config The configuration to normalize, modified in place.
*/
void normalize_configuration(const std::string& vendor,
CanDeviceConfiguration& config) noexcept;

/**
* @brief Logs and reports an invalid set of dynamic parameters.
*
* @param reason The problem found while validating the parameters.
* @throws std::invalid_argument always.
*/
[[noreturn]] void throw_invalid_parameters(const std::string& reason);

/**
* @brief Lists the vendors supported by CanDevice::create_dynamic.
*
* @return The sorted, comma separated list of supported vendor names.
*/
std::string supported_vendors() noexcept;

/**
* @brief Lists the parameters accepted by the given vendor.
*
* @param vendor The name of a supported vendor.
* @return The sorted, comma separated list of accepted parameter names, or an
* empty string if the vendor is not supported.
*/
std::string accepted_parameters(const std::string& vendor) noexcept;

} // namespace dynamic_parsing

#endif // SRC_INCLUDE_DYNAMICPARSING_H_
39 changes: 39 additions & 0 deletions src/include/utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#ifndef SRC_INCLUDE_UTILS_H_
#define SRC_INCLUDE_UTILS_H_

#include <sstream>
#include <string>

/**
* @namespace utils
* @brief Namespace containing generic helpers used across the CAN Module.
*/
namespace utils {

/**
* @brief Joins the elements of a container of strings.
*
* @param values The strings to join, in order.
* @param separator The separator inserted between two consecutive elements.
* @return The joined string.
*/
template <typename Container>
std::string join(const Container& values,
const std::string& separator) noexcept {
std::ostringstream oss;
bool first = true;

for (const auto& value : values) {
if (!first) {
oss << separator;
}
oss << value;
first = false;
}

return oss.str();
}

} // namespace utils

#endif // SRC_INCLUDE_UTILS_H_
95 changes: 94 additions & 1 deletion src/main/CanDevice.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
#include "CanDevice.h"

#include <algorithm>
#include <functional>
#include <map>
#include <memory>
#include <stdexcept>
#include <optional>
#include <string>
#include <vector>

#include "CanLogIt.h"
#include "CanVendorAnagate.h"
#include "CanVendorLoopback.h"
#include "dynamicParsing.h"

#ifndef _WIN32
#include "CanVendorSocketCan.h"
Expand Down Expand Up @@ -174,6 +177,96 @@ std::unique_ptr<CanDevice> CanDevice::create(
return nullptr;
}

/**
* @brief Creates a CAN device instance from a map of string parameters.
*
* The parameters are validated and converted into a CanDeviceConfiguration,
* which is then forwarded to CanDevice::create.
*
* @param parameters A map holding the "vendor" key and the configuration
* parameters accepted by that vendor, listed in
* dynamic_parsing::vendor_parameters. Unsigned integers must be written as
* decimal numbers without sign and booleans as "true" or "false".
* @param receiver A function, lambda or functor called whenever a CAN frame is
* received.
* @return std::unique_ptr<CanDevice> A unique pointer to the created CAN device
* object.
* @throws std::invalid_argument on the first missing, unexpected or invalid
* parameter found, or if the vendor is not recognized.
*/
std::unique_ptr<CanDevice> CanDevice::create_dynamic(
const std::map<std::string, std::string>& parameters,
const std::function<void(const CanFrame&)>& receiver) {
LOG(Log::INF, CanLogIt::h()) << "Creating CAN device with dynamic parameters";

// Assert vendor was passed
const auto vendor_entry = parameters.find("vendor");
if (vendor_entry == parameters.end()) {
dynamic_parsing::throw_invalid_parameters(
"missing required parameter 'vendor', supported "
"vendors: " +
dynamic_parsing::supported_vendors());
}

// Assert vendor is supported
const std::string& vendor = vendor_entry->second;
const auto vendor_spec = dynamic_parsing::vendor_parameters.find(vendor);
if (vendor_spec == dynamic_parsing::vendor_parameters.end()) {
dynamic_parsing::throw_invalid_parameters(
"unsupported vendor '" + vendor +
"', supported vendors: " + dynamic_parsing::supported_vendors());
}

// Assert required parameters were given
for (const std::string& required : vendor_spec->second.required) {
if (parameters.count(required) == 0) {
dynamic_parsing::throw_invalid_parameters("missing required parameter '" +
required + "' for vendor '" +
vendor + "'");
}
}

// Build the config with the given parameters
CanDeviceConfiguration config{};
for (const auto& [key, value] : parameters) {
// Passed directly as an additional argument
if (key == "vendor") {
continue;
}

// Assert no unrecognized parameters for this vendor were given
if (vendor_spec->second.required.count(key) == 0 &&
vendor_spec->second.optional.count(key) == 0) {
dynamic_parsing::throw_invalid_parameters(
"parameter '" + key + "' is not accepted by vendor '" + vendor +
"', accepted parameters: " +
dynamic_parsing::accepted_parameters(vendor));
}

// Check for parsing errors
const std::optional<std::string> error =
dynamic_parsing::parameter_parsers.at(key)(value, config);
if (error.has_value()) {
dynamic_parsing::throw_invalid_parameters("invalid value '" + value +
"' for parameter '" + key +
"': " + error.value());
}
}

dynamic_parsing::normalize_configuration(vendor, config);

auto device = create(vendor, CanDeviceArguments{config, receiver});

// CanDevice:create returns nullptr only if vendor is not recognized
// (shouldn't happen as vendor was already verified before)
if (device == nullptr) {
dynamic_parsing::throw_invalid_parameters("the vendor '" + vendor +
"' is not recognized");
}

return device;
}

std::ostream& operator<<(std::ostream& os, CanReturnCode code) {
switch (code) {
case CanReturnCode::success:
Expand Down
Loading