Skip to content
Merged
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 lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ if (ENABLE_ESI_PARSER)
find_package(tinyxml2 CONFIG REQUIRED)
list(APPEND KICKCAT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/ESI/Parser.cc)
list(APPEND KICKCAT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/ESI/SIIBuilder.cc)
list(APPEND KICKCAT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/utils/xml.cc)
list(APPEND OS_LIBRARIES tinyxml2::tinyxml2)
endif()

Expand Down
9 changes: 7 additions & 2 deletions lib/include/kickcat/CoE/OD.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <array>
#include <vector>
#include <string>
#include <string_view>
#include <optional>
#include <cstring>
#include <cstdint>
#include <tuple>
Expand Down Expand Up @@ -108,6 +110,9 @@ namespace kickcat::CoE
};
char const* toString(enum DataType type);

// ETG.1020 base data type label as ESI and ENI files write it (e.g. "UINT", "BOOL").
std::optional<DataType> dataTypeFromLabel(std::string_view label);

// Wire properties of a basic CoE data type, for code that interprets raw entry
// bytes (the name is already available via toString). size 0 = string.
struct DataTypeInfo
Expand Down Expand Up @@ -257,8 +262,8 @@ namespace kickcat::CoE

struct Object // ETG1000.5 6.1.4.2.1 Formal model
{
uint16_t index;
ObjectCode code;
uint16_t index = 0;
ObjectCode code = ObjectCode::NIL;
std::string name;
std::vector<Entry> entries;
};
Expand Down
13 changes: 13 additions & 0 deletions lib/include/kickcat/CoE/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@
#define KICKCAT_COE_PROTOCOL_H

#include "kickcat/CoE/OD.h"
#include "kickcat/protocol.h"

namespace kickcat::CoE
{
// SDO access a configuration file (ESI or ENI) asks for during ESM transitions.
struct InitCmd
{
Transitions transitions = 0;
uint16_t index = 0;
uint8_t subindex = 0;
std::vector<uint8_t> data;
bool complete_access = false;
std::string comment;
};

constexpr uint16_t SM_COM_TYPE = 0x1C00; // each sub-entry described SM[x] com type (mailbox in/out, PDO in/out, not used)
constexpr uint16_t SM_CHANNEL = 0x1C10; // each entry is associated with the mapped PDOs (if in used)

Expand All @@ -16,6 +28,7 @@ namespace kickcat::CoE
uint16_t index;
uint8_t subindex;
uint8_t bitlen;
DataType type = DataType::UNKNOWN; // not part of the mapping word
};

constexpr uint32_t toMappingWord(PdoMappingEntry entry)
Expand Down
35 changes: 7 additions & 28 deletions lib/include/kickcat/ESI/Device.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <vector>

#include "kickcat/CoE/OD.h"
#include "kickcat/CoE/protocol.h"
#include "kickcat/protocol.h"

namespace kickcat::ESI
Expand Down Expand Up @@ -45,39 +46,17 @@ namespace kickcat::ESI
bool op_only = false;
};

// ETG.2000 InitCmd Transition: ESM transitions during which the InitCmd applies.
namespace transition
{
enum Type : uint8_t
{
IP = 0, // Init -> PreOp
PS = 1, // PreOp -> SafeOp
SO = 2, // SafeOp -> Op
SP = 3, // SafeOp -> PreOp
OP = 4, // Op -> PreOp
OS = 5, // Op -> SafeOp
};
char const* toString(Type const& t);
void fromString(std::string_view text, Type& out);
}

struct Mailbox
{
bool data_link_layer = false;
bool real_time_mode = false;

struct CoE
{
struct InitCmd
struct InitCmd : kickcat::CoE::InitCmd
{
std::vector<transition::Type> transitions;
uint16_t index = 0;
uint8_t subindex = 0;
std::vector<uint8_t> data;
bool adapt_automatically = false;
bool complete_access = false;
bool overwritten_by_module = false;
std::string comment;
bool adapt_automatically = false;
bool overwritten_by_module = false;
};

bool sdo_info = false;
Expand All @@ -97,7 +76,7 @@ namespace kickcat::ESI
{
struct InitCmd
{
std::vector<transition::Type> transitions;
Transitions transitions = 0;
int32_t type = 0;
std::vector<uint8_t> data;
std::string comment;
Expand All @@ -115,7 +94,7 @@ namespace kickcat::ESI
{
struct InitCmd
{
std::vector<transition::Type> transitions;
Transitions transitions = 0;
int32_t idn = 0;
int32_t channel = 0;
std::vector<uint8_t> data;
Expand All @@ -131,7 +110,7 @@ namespace kickcat::ESI
{
struct InitCmd
{
std::vector<transition::Type> transitions;
Transitions transitions = 0;
std::vector<uint8_t> data;
std::string comment;
};
Expand Down
21 changes: 5 additions & 16 deletions lib/include/kickcat/ESI/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <tinyxml2.h>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>

#include "kickcat/CoE/OD.h"
Expand Down Expand Up @@ -39,13 +38,7 @@ namespace kickcat::ESI
static CoE::Object buildMappingObject (Pdo const& pdo, bool is_rx);
static CoE::Object buildAssignmentObject(std::vector<Pdo> const& pdos, uint16_t index, bool is_rx);

// Map an ESI basic-type label ("BOOL", "UINT", ...) to its CoE::DataType,
// whose value is the ETG SII data-type code. nullopt for unknown labels.
static std::optional<CoE::DataType> coeTypeFromLabel(std::string const& label);

private:
static std::optional<uint32_t> readHexDecAttr(tinyxml2::XMLElement* node, char const* name);

void openFile (std::string const& file);
void openString(std::string const& xml);
void resolveTopLevel();
Expand Down Expand Up @@ -77,18 +70,16 @@ namespace kickcat::ESI
// for objects that aren't already declared in <Dictionary>/<Objects>.
void synthesizePdoMappingObjects(Device& device);

std::vector<uint8_t> loadHexBinary(tinyxml2::XMLElement* node);

void loadDefaultData(tinyxml2::XMLNode* node, CoE::Object& obj, CoE::Entry& entry);
uint16_t loadAccess(tinyxml2::XMLNode* node);
void loadDefaultData(tinyxml2::XMLElement* node, CoE::Object& obj, CoE::Entry& entry);
uint16_t loadAccess(tinyxml2::XMLElement* node);

static constexpr int MAX_TYPE_DEPTH = 16;

std::tuple<CoE::DataType, uint16_t, uint16_t> parseType(tinyxml2::XMLNode* node);
std::tuple<CoE::DataType, uint16_t, uint16_t> parseType(tinyxml2::XMLElement* node);
CoE::DataType resolveType (std::string const& type_name, int depth = 0);
tinyxml2::XMLNode* findNodeType(tinyxml2::XMLNode* node, std::string const& where);
tinyxml2::XMLElement* findNodeType(tinyxml2::XMLElement* node);

CoE::Object createObject(tinyxml2::XMLNode* node);
CoE::Object createObject(tinyxml2::XMLElement* node, uint16_t index);

tinyxml2::XMLDocument doc_;
tinyxml2::XMLElement* root_ = nullptr;
Expand All @@ -99,8 +90,6 @@ namespace kickcat::ESI

std::string vendor_name_;
std::string profile_no_;

static const std::unordered_map<std::string, CoE::DataType> BASIC_TYPES;
};
}

Expand Down
28 changes: 28 additions & 0 deletions lib/include/kickcat/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,34 @@ namespace kickcat
char const* toString(State state);
char const* toShortString(State state);

// ESM transitions as ESI (ETG.2000) and ENI (ETG.2100) InitCmds name them.
namespace transition
{
enum Type : uint16_t
{
NONE = 0,
IP = 1 << 0, // Init -> PreOp
PS = 1 << 1, // PreOp -> SafeOp
PI = 1 << 2, // PreOp -> Init
SP = 1 << 3, // SafeOp -> PreOp
SO = 1 << 4, // SafeOp -> Op
SI = 1 << 5, // SafeOp -> Init
OS = 1 << 6, // Op -> SafeOp
OP = 1 << 7, // Op -> PreOp
OI = 1 << 8, // Op -> Init
IB = 1 << 9, // Init -> Bootstrap
BI = 1 << 10, // Bootstrap -> Init
II = 1 << 11, // Init -> Init
PP = 1 << 12, // PreOp -> PreOp
SS = 1 << 13, // SafeOp -> SafeOp
PO = 1 << 14, // PreOp -> Op (ENI XSD only)
};
char const* toString(Type t);
Type fromString(std::string_view text); // throws std::invalid_argument
Type between(State from, State to); // NONE if no transition matches
}
using Transitions = uint16_t; // OR of transition::Type

char const* ALStatus_to_string(int32_t code);

enum StatusCode : uint16_t
Expand Down
146 changes: 146 additions & 0 deletions lib/include/kickcat/utils/xml.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#ifndef KICKCAT_UTILS_XML_H
#define KICKCAT_UTILS_XML_H

#include <cstdint>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <vector>

#include <tinyxml2.h>

// Reading ESI (ETG.2000) and ENI (ETG.2100) documents.
// Errors throw std::invalid_argument naming the element they come from; withContext() lets a caller
// add what it was reading (the document kind, an object...).
namespace kickcat::xml
{
/// Element names from the root, with the position among same-name siblings when there are several,
/// e.g. "EtherCATConfig/Config/Slave[1]/InitCmds/InitCmd".
std::string pathOf(tinyxml2::XMLElement const* elem);

/// Throw "<what> in <path of where>".
[[noreturn]] void fail(tinyxml2::XMLElement const* where, std::string const& what);

/// Run parse(), prefixing the message of the std::invalid_argument it may throw with "<context>: ".
template<typename F>
auto withContext(std::string const& context, F&& parse) -> decltype(parse())
{
try
{
return parse();
}
catch (std::invalid_argument const& e)
{
throw std::invalid_argument(context + ": " + e.what());
}
}

/// First child called name; throws when there is none.
tinyxml2::XMLElement* require(tinyxml2::XMLElement* parent, char const* name);

/// Every text and CDATA child, so that a comment inside a value does not cut it, without the
/// surrounding XML whitespace.
std::string textOf(tinyxml2::XMLElement const* elem);

/// Text of the first child called name; empty when there is none.
std::string optionalText(tinyxml2::XMLElement const* parent, char const* name);

/// Text of the first child called name; throws when there is none or when it is empty.
std::string requireText(tinyxml2::XMLElement* parent, char const* name);

/// Attribute without the surrounding XML whitespace; nullopt when absent.
std::optional<std::string> attributeOf(tinyxml2::XMLElement const* elem, char const* name);

enum Reading : uint8_t
{
VALUE, // the number must fit the type
BIT_PATTERN, // also its two's complement: xs:int identifiers write 0xFFFFFFFF as -1, positions -1 as 65535
};

/// HexDecValue read from text found in elem.
int64_t parseInteger(tinyxml2::XMLElement const* elem, std::string const& text);

template<typename T>
T narrow(tinyxml2::XMLElement const* elem, int64_t value, Reading reading = VALUE)
{
static_assert(sizeof(T) < sizeof(int64_t), "narrow() needs a type narrower than int64_t");
using U = std::make_unsigned_t<T>;
int64_t lo = static_cast<int64_t>(std::numeric_limits<T>::min());
int64_t hi = static_cast<int64_t>(std::numeric_limits<T>::max());
if (reading == BIT_PATTERN)
{
lo = static_cast<int64_t>(std::numeric_limits<std::make_signed_t<T>>::min());
hi = static_cast<int64_t>(std::numeric_limits<U>::max());
}
if ((value < lo) or (value > hi))
{
fail(elem, "value " + std::to_string(value) + " out of range [" + std::to_string(lo) + ", " + std::to_string(hi) + "]");
}
return static_cast<T>(static_cast<U>(value));
}

template<typename T>
T numberOf(tinyxml2::XMLElement const* elem, Reading reading = VALUE)
{
return narrow<T>(elem, parseInteger(elem, textOf(elem)), reading);
}

template<typename T>
T requireNumber(tinyxml2::XMLElement* parent, char const* name, Reading reading = VALUE)
{
return numberOf<T>(require(parent, name), reading);
}

template<typename T>
std::optional<T> optionalNumber(tinyxml2::XMLElement const* parent, char const* name, Reading reading = VALUE)
{
tinyxml2::XMLElement const* child = parent->FirstChildElement(name);
if (child == nullptr)
{
return std::nullopt;
}
return numberOf<T>(child, reading);
}

template<typename T>
std::optional<T> numberAttribute(tinyxml2::XMLElement const* elem, char const* name, Reading reading = VALUE)
{
std::optional<std::string> raw = attributeOf(elem, name);
if (not raw)
{
return std::nullopt;
}
return withContext(std::string{"@"} + name, [&]()
{
return narrow<T>(elem, parseInteger(elem, *raw), reading);
});
}

/// xs:boolean read from text found in elem.
bool parseBool(tinyxml2::XMLElement const* elem, std::string const& text);

/// xs:boolean child called name; false when there is none.
bool optionalBool(tinyxml2::XMLElement const* parent, char const* name);

/// xs:boolean attribute; false when absent.
bool boolAttribute(tinyxml2::XMLElement const* elem, char const* name);

/// xs:hexBinary content of elem.
std::vector<uint8_t> hexBinary(tinyxml2::XMLElement const* elem);

/// parse() applied to every child called name, in document order.
template<typename F>
auto all(tinyxml2::XMLElement* parent, char const* name, F parse)
{
std::vector<decltype(parse(parent))> out;
for (tinyxml2::XMLElement* child = parent->FirstChildElement(name); child != nullptr; child = child->NextSiblingElement(name))
{
out.push_back(parse(child));
}
return out;
}
}

#endif
3 changes: 3 additions & 0 deletions lib/include/kickcat/utils/xsd.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
// Errors throw std::invalid_argument without context: callers add the element they were reading.
namespace kickcat::xsd
{
/// Strip the XML whitespace (space, tab, CR, LF) around text.
std::string_view trim(std::string_view text);

/// HexDecValue: [+-]?[0-9]+ or #x (0x accepted) followed by hex digits, surrounding spaces ignored.
/// Unsigned values up to 2^64 - 1 keep their bit pattern.
int64_t parseHexDec(std::string_view text);
Expand Down
1 change: 1 addition & 0 deletions lib/master/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
target_sources(kickcat PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/AbstractLink.cc
${CMAKE_CURRENT_SOURCE_DIR}/src/Bus.cc
${CMAKE_CURRENT_SOURCE_DIR}/src/CoE.cc
${CMAKE_CURRENT_SOURCE_DIR}/src/CoE/CiA/DS402/Drive.cc
Expand Down
Loading
Loading