KaCanOpen
 All Classes Functions Variables Typedefs Enumerations Pages
eds_reader.cpp
1 /*
2  * Copyright (c) 2015, Thomas Keh
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:M
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in the
13  * documentation and/or other materials provided with the distribution.
14  *
15  * 3. Neither the name of the copyright holder nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include "logger.h"
33 #include "eds_reader.h"
34 #include "entry.h"
35 #include "utils.h"
36 #include "global_config.h"
37 
38 #include <regex>
39 #include <string>
40 #include <cassert>
41 #include <utility>
42 
43 #include <boost/property_tree/ptree.hpp> // property_tree
44 #include <boost/property_tree/ini_parser.hpp>
45 #include <boost/algorithm/string/predicate.hpp> // string::starts_with()
46 #include <boost/algorithm/string/trim.hpp> // trim()
47 
48 namespace kaco {
49 
50 EDSReader::EDSReader(std::unordered_map<Address, Entry>& dictionary, std::unordered_map<std::string, Address>& name_to_address)
51  : m_dictionary(dictionary), m_name_to_address(name_to_address)
52  { }
53 
54 bool EDSReader::load_file(std::string filename) {
55 
56  DEBUG_LOG_EXHAUSTIVE("Trying to read EDS file " << filename);
57  try {
58  boost::property_tree::ini_parser::read_ini(filename, m_ini);
59  return true;
60  } catch (const std::exception& e) {
61  ERROR("[EDSReader::load_file] Could not open file: "<<e.what());
62  return false;
63  }
64 
65 }
66 
68 
69  bool success = true;
70 
71  for (const auto& section_node : m_ini) {
72  const std::string& section_name = section_node.first;
73  //const boost::property_tree::ptree& section = section_node.second;
74 
75  try {
76 
77  std::smatch matches;
78 
79  if (std::regex_match(section_name, std::regex("[[:xdigit:]]{1,4}"))) {
80 
81  uint16_t index = (uint16_t) Utils::hexstr_to_uint(section_name);
82  DEBUG_LOG_EXHAUSTIVE("Section "<<section_name<<" corresponds to index "<<index<<".");
83  success = parse_index(section_name, index) && success; // mind order!
84 
85  }
86  /*
87  else if (std::regex_match(section_name, matches, std::regex("([[:xdigit:]]{1,4})sub([[:xdigit:]]{1,2})"))) {
88 
89  // Ignoring subindex entries here!
90 
91  } else {
92 
93  // Ignoring metadata entries here!
94  DEBUG_LOG_EXHAUSTIVE("Section "<<section_name<<" contains meta data. Doing nothing.");
95 
96  }
97  */
98 
99  } catch (std::regex_error& e) {
100  ERROR("[EDSReader::import_entries] " << parse_regex_error(e.code(), section_name));
101  success = false;
102  }
103 
104  }
105 
106  return success;
107 
108 }
109 
110 bool EDSReader::parse_index(const std::string& section, uint16_t index) {
111 
112  std::string str_object_type = trim(m_ini.get(section+".ObjectType", ""));
113 
114  uint8_t object_code = (uint8_t) ObjectType::VAR;
115  if (str_object_type.empty()) {
116  DEBUG_LOG("Field ObjectType missing. Assuming ObjectType::VAR (according to DS 306 V1.3 page 16).");
117  } else {
118  object_code = (uint8_t) Utils::hexstr_to_uint(str_object_type);
119  }
120 
121 
122  if (object_code == (uint8_t) ObjectType::VAR) {
123  return parse_var(section, index, 0);
124  } else if ((object_code == (uint8_t) ObjectType::RECORD) || (object_code == (uint8_t) ObjectType::ARRAY)) {
125  return parse_array_or_record(section, index);
126  }
127 
128  DEBUG_LOG("This is not a variable and no array. Ignoring.")
129  return true;
130 
131 }
132 
133 bool EDSReader::parse_var(const std::string& section, uint16_t index, uint8_t subindex, const std::string& name_prefix) {
134 
135  std::string var_name = Utils::escape(trim(m_ini.get(section+".ParameterName", "")));
136 
137  if (var_name.empty()) {
138  ERROR("[EDSReader::parse_var] Field ParameterName missing");
139  return false;
140  }
141 
142  if (!name_prefix.empty()) {
143  var_name = name_prefix + "/" + var_name;
144  }
145 
146  DEBUG_LOG("[EDSReader::parse_var] Parsing variable "<<section<<": "<<var_name);
147 
148  std::string str_sub_number = trim(m_ini.get(section+".SubNumber", ""));
149  std::string str_object_type = trim(m_ini.get(section+".ObjectType", ""));
150  std::string str_data_type = trim(m_ini.get(section+".DataType", ""));
151  std::string str_low_limit = trim(m_ini.get(section+".LowLimit", ""));
152  std::string str_high_limit = trim(m_ini.get(section+".HighLimit", ""));
153  std::string str_access_type = trim(m_ini.get(section+".AccessType", ""));
154  std::string str_default_value = trim(m_ini.get(section+".DefaultValue", ""));
155  std::string str_pdo_mapping = trim(m_ini.get(section+".PDOMapping", ""));
156  std::string str_obj_flags = trim(m_ini.get(section+".ObjFlags", ""));
157 
158  Entry entry;
159  entry.name = var_name;
160  entry.type = Utils::type_code_to_type((uint16_t) Utils::hexstr_to_uint(str_data_type));
161  entry.index = index;
162  entry.subindex = subindex;
163 
165  entry.is_generic = true;
166  }
167 
168  if (entry.type == Type::invalid) {
169  ERROR("[EDSReader::parse_var] "<<entry.name<<": Ignoring entry due to unsupported data type.");
170  return true;
171  // TODO: return false; ? At the moment, unsupported entries are not considered as error.
172  }
173 
174  entry.access_type = Utils::string_to_access_type(str_access_type);
175 
176  const Address address = Address{entry.index,entry.subindex};
177 
178  // --- insert entry --- //
179 
181 
182  // Resolve name conflics...
183 
184  while (m_name_to_address.count(var_name)>0) {
185 
186  WARN("[EDSReader::parse_var] Entry "<<var_name<<" already exists. Adding or increasing counter.");
187 
188  try {
189  std::smatch matches;
190  if (std::regex_match(var_name, matches, std::regex("^(.+)_([[:xdigit:]]{1,3})$"))) {
191  assert(matches.size()>2);
192  uint8_t count = Utils::decstr_to_uint(matches[2]);
193  ++count;
194  var_name = std::string(matches[1])+"_"+std::to_string(count);
195  } else {
196  var_name = var_name+"_1";
197  }
198  } catch (std::regex_error& e) {
199  WARN("[EDSReader::parse_var] "<<parse_regex_error(e.code(), var_name));
200  return false;
201  }
202 
203  DEBUG_LOG("[EDSReader::parse_var] New entry name: "<<var_name);
204  entry.name = var_name;
205 
206  }
207 
208  DEBUG_LOG("[EDSReader::parse_var] Inserting entry "<<var_name<<".");
209 
210  // The following line isn't possible because STL would require copy constructor:
211  // m_dictionary[Address{entry.index,entry.subindex}] = std::move(entry);
212 
213  m_dictionary.insert(std::make_pair(address, std::move(entry)));
214  m_name_to_address.insert(std::make_pair(var_name,address));
215 
216  } else {
217 
218  // Entering this path means that a generic EDS file is loaded on top of
219  // a manufacturer-specific dictionary. Only additional generic
220  // entry names shall be added.
221 
222  if (m_dictionary.count(address)>0) {
223  // entry exists.
224  if (m_dictionary[address].name != var_name) {
225  DEBUG_LOG("[EDSReader::parse_var] Manufacturer-specific entry name \""<<m_dictionary[address].name<<"\" differs from CiA standard \""<<var_name<<"\".");
226  if (m_name_to_address.count(var_name)>0) {
227  WARN("[EDSReader::parse_var] Conflict with existing mapping \""<<var_name<<"\"->0x"
228  <<std::hex<<m_name_to_address[var_name].index<<"sub"<<std::dec<<m_name_to_address[var_name].subindex<<".");
229  DUMP_HEX(index);
230  DUMP_HEX(subindex);
231  } else {
232  m_name_to_address.insert(std::make_pair(var_name,address));
233  DEBUG_LOG("[EDSReader::parse_var] Added additional name-to-address mapping.");
234  }
235  } else {
236  DEBUG_LOG_EXHAUSTIVE("[EDSReader::parse_var] Standard conformal entry: "<<var_name);
237  }
238  } else {
239  DEBUG_LOG_EXHAUSTIVE("[EDSReader::parse_var] Standard entry not available in manufacturer-specific EDS: "<<var_name);
240  }
241  }
242 
243  // new method:
244  /*const Address address = Address{entry.index,entry.subindex}:
245  if (m_dictionary.count(address)>0) {
246  // replace entry
247  // TODO: keep generic name?
248 
249  std::string existing_name = m_dictionary[address].name;
250  if (existing_name != var_name) {
251  // insert additional name
252  if (m_name_to_address.count(var_name)>0) {
253  const Entry& conflicting_entry = m_dictionary.at(m_name_to_address[var_name]);
254  WARNING("Conflict! Entry name \""<<var_name<<"\" already exists for Entry 0x"<<std::hex<<conflicting_entry.index<<"sub"
255  <<std::dec<<conflicting_entry.subindex<<". Erasing old mapping...");
256  m_name_to_address.erase(var_name);
257  }
258  }
259 
260  DEBUG_LOG("[EDSReader::parse_var] Entry already exists "<<var_name<<".");
261 
262  // C++17: insert_or_assign...
263  m_dictionary.erase(address);
264  }
265 
266  m_dictionary.insert(std::make_pair(address, std::move(entry)));
267  m_name_to_address.insert(std::make_pair(var_name,address));*/
268 
269  return true;
270 
271 }
272 
273 bool EDSReader::parse_array_or_record(const std::string& section, uint16_t index) {
274 
275  std::string array_name = Utils::escape(trim(m_ini.get(section+".ParameterName", "")));
276 
277  if (array_name.empty()) {
278  ERROR("[EDSReader::parse_array_or_record] Field ParameterName missing");
279  return false;
280  }
281 
282  DEBUG_LOG_EXHAUSTIVE("[EDSReader::parse_array_or_record] Parsing array/record "<<section<<": "<<array_name);
283 
284  for (const auto& section_node : m_ini) {
285  const std::string& section_name = section_node.first;
286  //const boost::property_tree::ptree& parameters = section_node.second;
287 
288  if (boost::starts_with(section_name, section)) {
289 
290  DEBUG_LOG_EXHAUSTIVE("[EDSReader::parse_array_or_record] Found record/array entry: "<<section_name);
291 
292  try {
293 
294  std::smatch matches;
295 
296  if (std::regex_match(section_name, matches, std::regex("([[:xdigit:]]{1,4})sub([[:xdigit:]]{1,2})"))) {
297 
298  assert(matches.size()>2);
299  assert(Utils::hexstr_to_uint(matches[1])==index);
300  uint8_t subindex = Utils::hexstr_to_uint(matches[2]);
301  bool success = parse_var(section_name, index, subindex, array_name);
302  if (!success) {
303  ERROR("[EDSReader::parse_array_or_record] Malformed variable entry: "<<section_name);
304  return false;
305  }
306 
307  } else if (section_name == section) {
308  // ignore own entry
309  continue;
310  } else {
311  ERROR("[EDSReader::parse_array_or_record] Malformed array entry: "<<section_name);
312  return false;
313  }
314 
315  } catch (std::regex_error& e) {
316  ERROR("[EDSReader::parse_array_or_record] "<<parse_regex_error(e.code(), section_name));
317  return false;
318  }
319 
320 
321  }
322 
323  }
324 
325  return true;
326 
327 }
328 
329 std::string EDSReader::parse_regex_error(const std::regex_constants::error_type& etype, const std::string element_name) const {
330  std::string result;
331  switch (etype) {
332  case std::regex_constants::error_collate:
333  result = "error_collate: invalid collating element request";
334  break;
335  case std::regex_constants::error_ctype:
336  result = "error_ctype: invalid character class";
337  break;
338  case std::regex_constants::error_escape:
339  result = "error_escape: invalid escape character or trailing escape";
340  break;
341  case std::regex_constants::error_backref:
342  result = "error_backref: invalid back reference";
343  break;
344  case std::regex_constants::error_brack:
345  result = "error_brack: mismatched bracket([ or ])";
346  break;
347  case std::regex_constants::error_paren:
348  result = "error_paren: mismatched parentheses(( or ))";
349  break;
350  case std::regex_constants::error_brace:
351  result = "error_brace: mismatched brace({ or })";
352  break;
353  case std::regex_constants::error_badbrace:
354  result = "error_badbrace: invalid range inside a { }";
355  break;
356  case std::regex_constants::error_range:
357  result = "erro_range: invalid character range(e.g., [z-a])";
358  break;
359  case std::regex_constants::error_space:
360  result = "error_space: insufficient memory to handle this regular expression";
361  break;
362  case std::regex_constants::error_badrepeat:
363  result = "error_badrepeat: a repetition character (*, ?, +, or {) was not preceded by a valid regular expression";
364  break;
365  case std::regex_constants::error_complexity:
366  result = "error_complexity: the requested match is too complex";
367  break;
368  case std::regex_constants::error_stack:
369  result = "error_stack: insufficient memory to evaluate a match";
370  break;
371  default:
372  result = "";
373  break;
374  }
375  result += " in element " + element_name;
376  return result;
377 }
378 
379 std::string EDSReader::trim(const std::string& str) {
380  std::string result = str;
381  size_t found = result.find("#");
382  if (found!=std::string::npos) {
383  result = result.substr(0,found);
384  }
385  boost::algorithm::trim(result);
386  return result;
387 }
388 
389 } // end namespace kaco
static unsigned long long hexstr_to_uint(std::string str)
Converts a string containing a hexadecimal numer to unsigned.
Definition: utils.cpp:174
static Type type_code_to_type(uint16_t code)
Maps type codes from an EDS file to a data type.
Definition: utils.cpp:137
bool import_entries()
Import entries from the EDS file into the given dictionary.
Definition: eds_reader.cpp:67
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...
Definition: global_config.h:51
static std::string escape(const std::string &str)
Converts entry names to lower case and replaces all spaces and '-' by underscores.
Definition: utils.cpp:166
static AccessType string_to_access_type(std::string str)
Converts a string representation of AccessType from an EDS file to AccessType.
Definition: utils.cpp:198
EDSReader(std::unordered_map< Address, Entry > &dictionary, std::unordered_map< std::string, Address > &name_to_address)
Constructor.
Definition: eds_reader.cpp:50
static unsigned long long decstr_to_uint(std::string str)
Converts a string containing a decimal numer to unsigned.
Definition: utils.cpp:186
bool load_file(std::string filename)
Loads an EDS file from file system.
Definition: eds_reader.cpp:54
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...
Definition: global_config.h:55