36 #include "dictionary_error.h"
38 #include "sdo_error.h"
39 #include "global_config.h"
49 : m_core(core), m_node_id(node_id), m_eds_library(m_dictionary, m_name_to_address) { }
59 throw canopen_error(
"[Device::start] EDS library not found. If and only if you make sure for yourself, that mandatory"
60 " entries and operations are available, you can catch this error and go on.");
64 throw canopen_error(
"[Device::start] Could not load mandatory dictionary entries."
65 " If and only if you make sure for yourself, that mandatory"
66 " entries and operations are available, you can catch this error and go on.");
80 return m_name_to_address.count(name) > 0 &&
has_entry(m_name_to_address[name].index, m_name_to_address[name].subindex);
84 return m_dictionary.count(
Address{index,subindex}) > 0;
92 return m_dictionary[m_name_to_address[name]].get_type();
97 throw dictionary_error(dictionary_error::type::unknown_entry, std::to_string(index)+
"sub"+std::to_string(subindex));
99 return m_dictionary[
Address{index,subindex}].get_type();
107 const Address address = m_name_to_address[name];
113 throw dictionary_error(dictionary_error::type::unknown_entry, std::to_string(index)+
"sub"+std::to_string(subindex));
116 if (access_method==ReadAccessMethod::sdo || (access_method==ReadAccessMethod::use_default && entry.
read_access_method==ReadAccessMethod::sdo)) {
117 DEBUG_LOG(
"[Device::get_entry] SDO update on read.");
128 const Address address = m_name_to_address[name];
132 void Device::set_entry(
const uint16_t index,
const uint8_t subindex,
const Value& value,
const WriteAccessMethod access_method) {
133 const std::string index_string = std::to_string(index)+
"sub"+std::to_string(subindex);
135 throw dictionary_error(dictionary_error::type::unknown_entry, index_string);
143 if (access_method==WriteAccessMethod::sdo || (access_method==WriteAccessMethod::use_default && entry.
write_access_method==WriteAccessMethod::sdo)) {
144 DEBUG_LOG(
"[Device::set_entry] SDO update on write.");
149 void Device::add_entry(
const uint16_t index,
const uint8_t subindex,
const std::string& name,
const Type type,
const AccessType access_type) {
151 if (m_name_to_address.count(entry_name)>0) {
152 throw canopen_error(
"[Device::add_entry] Entry with name \""+entry_name+
"\" already exists.");
155 throw canopen_error(
"[Device::add_entry] Entry with index "+std::to_string(index)+
"sub"+std::to_string(subindex)+
" already exists.");
157 Entry entry(index,subindex,entry_name,type,access_type);
158 const Address address{index,subindex};
159 m_dictionary.insert(std::make_pair(address, std::move(entry)));
160 m_name_to_address.insert(std::make_pair(entry_name,address));
173 Entry& entry = m_dictionary[m_name_to_address[name]];
177 if (offset+type_size > 8) {
179 "offset ("+std::to_string(offset)+
") + type_size ("+std::to_string(type_size)+
") > 8.");
186 std::lock_guard<std::mutex> lock(m_receive_pdo_mappings_mutex);
187 m_receive_pdo_mappings.push_front({cob_id,name,offset});
188 pdo_temp = &m_receive_pdo_mappings.front();
194 auto binding = std::bind(&Device::pdo_received_callback,
this, pdo, std::placeholders::_1);
205 std::lock_guard<std::mutex> lock(m_transmit_pdo_mappings_mutex);
207 m_transmit_pdo_mappings.emplace_front(m_core, m_dictionary, m_name_to_address, cob_id, transmission_type, repeat_time, mappings);
208 pdo_temp = &m_transmit_pdo_mappings.front();
213 if (transmission_type==TransmissionType::ON_CHANGE) {
220 Entry& entry = m_dictionary.at(m_name_to_address.at(entry_name));
223 DEBUG_LOG(
"[Callback] Value of "<<entry_name<<
" changed to "<<value);
232 if (repeat_time == std::chrono::milliseconds(0)) {
233 WARN(
"[Device::add_transmit_pdo_mapping] Repeat time is 0. This could overload the bus.");
236 pdo.
transmitter = std::unique_ptr<std::thread>(
new std::thread([&pdo, repeat_time](){
239 DEBUG_LOG(
"[Timer thread] Sending periodic PDO.");
241 std::this_thread::sleep_for(repeat_time);
250 void Device::pdo_received_callback(
const ReceivePDOMapping& mapping, std::vector<uint8_t> data) {
252 DEBUG_LOG(
"[Device::pdo_received_callback] Received a PDO for mapping '"<<mapping.
entry_name<<
"'!");
255 Entry& entry = m_dictionary[m_name_to_address[entry_name]];
256 const uint8_t offset = mapping.
offset;
259 if (data.size() < offset+type_size) {
261 WARN(
"[Device::pdo_received_callback] PDO has wrong size. Ignoring it...");
268 DEBUG_LOG(
"Updating entry "<<entry.
name<<
".");
269 std::vector<uint8_t> bytes(data.begin()+offset, data.begin()+offset+type_size);
276 uint32_t device_type =
get_entry(0x1000);
277 return (device_type & 0xFFFF);
280 Value Device::get_entry_via_sdo(uint32_t index, uint8_t subindex, Type type) {
282 sdo_error last_error(sdo_error::type::unknown);
286 std::vector<uint8_t> data = m_core.
sdo.
upload(m_node_id, index, subindex);
287 return Value(type, data);
288 }
catch (
const sdo_error& error) {
290 if (i<Config::repeats_on_sdo_timeout) {
291 DEBUG_LOG(
"[Device::get_entry_via_sdo] device " << std::to_string(m_node_id) <<
" " << error.what() <<
" -> Repetition "<<std::to_string(i+1)
292 <<
" of "<<std::to_string(Config::repeats_on_sdo_timeout+1)<<
".");
298 throw sdo_error(sdo_error::type::response_timeout,
"Device::get_entry_via_sdo() device " + std::to_string(m_node_id) +
" failed after "
299 +std::to_string(Config::repeats_on_sdo_timeout+1)+
" repeats. Last error: "+std::string(last_error.what()));
303 void Device::set_entry_via_sdo(uint32_t index, uint8_t subindex,
const Value& value) {
305 sdo_error last_error(sdo_error::type::unknown);
307 for (
size_t i=0; i<Config::repeats_on_sdo_timeout+1; ++i) {
309 const auto& bytes = value.get_bytes();
310 m_core.
sdo.
download(m_node_id,index,subindex,bytes.size(),bytes);
312 }
catch (
const sdo_error& error) {
314 if (i<Config::repeats_on_sdo_timeout) {
315 DEBUG_LOG(
"[Device::set_entry_via_sdo] device " << std::to_string(m_node_id) <<
" " << error.what() <<
" -> Repetition "<<std::to_string(i+1)
316 <<
" of "<<std::to_string(Config::repeats_on_sdo_timeout+1)<<
".");
322 throw sdo_error(sdo_error::type::response_timeout,
"Device::set_entry_via_sdo() device " + std::to_string(m_node_id) +
" failed after "
323 +std::to_string(Config::repeats_on_sdo_timeout+1)+
" repeats. Last error: "+std::string(last_error.what()));
329 if (!m_eds_library.
ready()) {
330 throw canopen_error(
"[Device::load_dictionary_from_library] EDS library is not available.");
333 DEBUG_LOG(
"Device::load_dictionary_from_library()...");
334 std::string eds_path =
"";
343 DEBUG_LOG(
"[Device::load_dictionary_from_library] Device "<<std::to_string(m_node_id)<<
": Successfully loaded manufacturer-specific dictionary: " << m_eds_library.
get_most_recent_eds_file_path());
344 DEBUG_LOG(
"[Device::load_dictionary_from_library] Now we will add additional mappings from standard conformal entry names to the entries...");
348 DEBUG_LOG(
"[Device::load_dictionary_from_library] Device "<<std::to_string(m_node_id)<<
": There is no manufacturer-specific EDS file available. Going on with the default dictionary...");
354 load_cia_dictionary();
355 if (eds_path.empty()) {
365 void Device::load_cia_dictionary() {
369 DEBUG_LOG(
"[Device::load_dictionary_from_library] Device "<<std::to_string(m_node_id)<<
": Successfully loaded profile-specific dictionary: " << m_eds_library.
get_most_recent_eds_file_path());
373 DEBUG_LOG(
"[Device::load_dictionary_from_library] Device "<<std::to_string(m_node_id)<<
": Successfully loaded mandatory entries: " << m_eds_library.
get_most_recent_eds_file_path());
375 throw canopen_error(
"Could not load mandatory CiA 301 dictionary entries for device with ID "+std::to_string(m_node_id)+
". This can break various parts of KaCanOpen!");
386 EDSReader reader(m_dictionary, m_name_to_address);
389 throw canopen_error(
"[EDSLibrary::load_dictionary_from_eds] Loading file not successful: "+path);
393 throw canopen_error(
"[EDSLibrary::load_dictionary_from_eds] Importing entries failed for file "+path);
397 if (m_eds_library.
ready()) {
400 add_entry(0x1000,0,
"device_type",Type::uint32,AccessType::read_only);
403 load_cia_dictionary();
406 WARN(
"[Device::load_dictionary_from_eds] Cannot load generic entry names because EDS library is not available.");
422 if (m_operations.count(name)>0) {
423 WARN(
"[Device::add_operation] Overwriting operation \""<<name<<
"\".");
425 m_operations[name] = operation;
430 if (m_operations.count(name) == 0) {
433 return m_operations[name](*
this,argument);
447 if (m_constants.count(name)>0) {
448 WARN(
"[Device::add_constant] Overwriting constant \""<<name<<
"\".");
450 m_constants[name] = constant;
455 if (m_constants.count(name) == 0) {
458 return m_constants.at(name);
463 using EntryRef = std::reference_wrapper<const kaco::Entry>;
464 std::vector<EntryRef> entries;
466 for (
const auto& pair : m_dictionary) {
467 if (!pair.second.disabled) {
468 entries.push_back(std::ref(pair.second));
473 std::sort(entries.begin(), entries.end(),
474 [](
const EntryRef& l,
const EntryRef& r) {
return l.get()<r.get(); });
476 for (
const auto& entry : entries) {
483 for (
auto& pair : m_dictionary) {
487 pair.second.disabled =
true;
488 DEBUG_LOG(
"[Device::read_complete_dictionary] SDO error for field "<<pair.second.name<<
": "<<error.
what()<<
" -> disable entry.");
This type of exception is thrown if there are problems accessing the object dictionary or arguments d...
void print_dictionary() const
Prints the dictionary together with currently cached values to command line.
virtual const char * what() const noexceptoverride
Returns error description.
void add_transmit_pdo_mapping(uint16_t cob_id, const std::vector< Mapping > &mappings, TransmissionType transmission_type=TransmissionType::ON_CHANGE, std::chrono::milliseconds repeat_time=std::chrono::milliseconds(0))
Adds a transmit PDO mapping. This means values from the dictionary cache are sent to the device...
void send_nmt_message(uint8_t node_id, Command cmd)
Sends a NMT message to a given device.
uint8_t offset
index of the first mapped byte in the PDO message
const Value & get_entry(const std::string &entry_name, const ReadAccessMethod access_method=ReadAccessMethod::use_default)
Gets the value of a dictionary entry by name internally. If there is no cached value or the entry is ...
void add_value_changed_callback(ValueChangedCallback callback)
Registers a given function to be called when the value is changed.
Tuple of object dictionary index and subindex.
Type type
Data type of the value.
void add_operation(const std::string &coperation_name, const Operation &operation)
Adds a convenience operation.
This class represents a mapping from one or more dictionary entries to one transmit PDO...
NMT nmt
The NMT sub-protocol.
PDO pdo
The PDO sub-protocol.
bool import_entries()
Import entries from the EDS file into the given dictionary.
const Value & get_value() const
Returns the value.
void download(uint8_t node_id, uint16_t index, uint8_t subindex, uint32_t size, const std::vector< uint8_t > &bytes)
SDO download: Write value into remote device's object dictionary.
static bool eds_reader_mark_entries_as_generic
If this is set to true, EDSReader will mark all entries as generic (Entry::is_generic) This is used i...
WriteAccessMethod write_access_method
Standard method for writing this entry. Used by Device::set_entry().
void set_value(const Value &value)
Sets the value.
static const std::map< uint16_t, std::map< std::string, const Operation > > operations
Convenience operations for CiA profiles. Type: map < profile number , map < operation name ...
This class allows reading EDS files (like standardized in CiA 306) and inserting all contained entrie...
uint16_t index
index in dictionary
void add_entry(const uint16_t index, const uint8_t subindex, const std::string &name, const Type type, const AccessType access_type)
Adds an entry to the dictionary. You have to take care that exactly this entry exists on the device f...
void start()
Starts the node via NMT protocol and loads mandatory entries, operations, and constants.
void add_receive_pdo_mapping(uint16_t cob_id, const std::string &entry_name, uint8_t offset)
Adds a receive PDO mapping. This means values sent by the device via PDO are saved into the dictionar...
bool load_default_eds(uint16_t device_profile_number)
Loads entries defined in generic CiA profile EDS files.
std::string name
Human-readable name Should be escaped for consitency using Utils::escape().
static std::string escape(const std::string &str)
Converts entry names to lower case and replaces all spaces and '-' by underscores.
std::unique_ptr< std::thread > transmitter
The transmitter thread.
uint8_t subindex
subindex in dictionary. if is_array==true, this variable is not used
This type of exception is thrown when there are problems while accessing devices via SDO...
This is the base class of all types of exceptions thrown by the KaCanOpen library. It can be used directly like std::runtime_error if there isn't any more specific error class.
uint16_t get_device_profile_number()
Returns the CiA profile number.
void add_constant(const std::string &constant_name, const Value &constant)
Adds a constant.
bool load_mandatory_entries()
Loads mandatory dictionary entries defined in CiA 301 standard.
std::string entry_name
Name of the mapped dictionary entry.
void send() const
Sends the PDO.
This class implements the Core of KaCanOpen It communicates with the CAN driver, sends CAN messages a...
static size_t sdo_response_timeout_ms
Timeout in milliseconds when waiting for an SDO response.
bool lookup_library(std::string path="")
Finds EDS library on disk.
static std::string type_to_string(Type type)
Converts data types to a string.
static size_t repeats_on_sdo_timeout
Number of repetitions when an SDO timeout occurs in SDO download/upload.
void add_pdo_received_callback(uint16_t cob_id, PDOReceivedCallback::Callback callback)
Adds a callback which will be called when a PDO has been received with the given COB-ID.
uint8_t get_node_id() const
Returns the node ID of the device.
ReadAccessMethod read_access_method
Standard method for reading this entry. Used by Device::get_entry().
Type type
Tyoe of the value.
Value execute(const std::string &operation_name, const Value &argument=m_dummy_value)
Executes a convenience operation. It must exist due to a previous load_operations() or add_operation(...
bool load_manufacturer_eds(Device &device)
Loads entries defined in device specific EDS files proviced by manufacturers.
std::string load_dictionary_from_library()
Tries to load the most specific EDS file available in KaCanOpen's internal EDS library. This is either device specific, CiA profile specific, or mandatory CiA 301.
This struct represents a mapping from one receive PDO to one dictionary entry. There may be multiple ...
This class represents an entry in the object dictionary of a device.
bool load_operations()
Loads convenience operations associated with the device profile.
SDO sdo
The SDO sub-protocol.
bool ready() const
Checks if lookup_library() was successful.
void print() const
Prints relevant information concerning this entry on standard output - name, index, possibly value, ... This is used by Device::print_dictionary()
static bool eds_library_clear_dictionary
If this is set to true, EDSLibrary will clear dictionary and me-to-address mappings before loading an...
void set_entry(const std::string &entry_name, const Value &value, const WriteAccessMethod access_method=WriteAccessMethod::use_default)
Sets the value of a dictionary entry by name internally. If the entry is configured to send an SDO on...
bool load_file(std::string filename)
Loads an EDS file from file system.
uint8_t subindex
Sub-index.
const Value & get_constant(const std::string &constant_name) const
Returns a constant. It must exist due to a previous load_constants() or add_constant() call...
static bool eds_reader_just_add_mappings
If this is set to true, EDSReader won't create any entries, but just adds name-to-address mappings fo...
void reset_dictionary()
Resets the dictionary and the name-address mapping.
std::string entry_name
Name of the dictionary entry.
Type get_entry_type(const std::string &entry_name)
Returns the type of a dictionary entry identified by name as it is defined in the local dictionary...
void read_complete_dictionary()
Fetches all dictionary entries from the device. Afterwards, all values exist in cache and can for exa...
std::vector< uint8_t > upload(uint8_t node_id, uint16_t index, uint8_t subindex)
SDO download: Get value from remote device's object dictionary.
void load_dictionary_from_eds(const std::string &path)
Loads the dictionary from a custom EDS file.
bool has_entry(const std::string &entry_name)
Returns true if the entry is contained in the device dictionary.
std::string get_most_recent_eds_file_path() const
Returns the path to the most recently loaded EDS file.
Device(Core &core, uint8_t node_id)
Constructor.
std::function< Value(Device &, const Value &)> Operation
Type of a operation. See Profiles::Operation in profiles.h.
std::vector< Mapping > mappings
Mapped entries with offset (see Mapping class)
bool load_constants()
Loads constants associated with the device profile.
This struct represents one mapped entry inside a TransmitPDOMapping.
static uint8_t get_type_size(Type type)
Returns the size of a data type in bytes.
This class contains a value to be stored in the object dictionary. The value can have one of the type...
static const std::map< uint16_t, std::map< std::string, const Value > > constants
Constants for CiA profiles. Type: map < profile number , map < operation name , constant value > > ...