added support for diagnostic messages.
[apps/agl-service-can-low-level.git] / src / main.cpp
1 #include <exception>\r
2 #include <fstream>\r
3 #include <iostream>\r
4 #include <string>\r
5 #include <numeric>\r
6 #include <iterator>\r
7 #include <boost/program_options.hpp>\r
8 #include <boost/filesystem.hpp>\r
9 #include <json.hpp>\r
10 #include "openxc/message_set.hpp"\r
11 \r
12 #define EXIT_SUCCESS                            0\r
13 #define EXIT_UNKNOWN_ERROR                      1\r
14 #define EXIT_COMMAND_LINE_ERROR         2\r
15 #define EXIT_PROGRAM_ERROR                      3\r
16 \r
17 template <typename T>\r
18 struct generator\r
19 {\r
20         T v_;\r
21         std::string line_prefix_;\r
22         generator(T v, std::string line_prefix = "") : v_{v}, line_prefix_{line_prefix} {}\r
23 };\r
24 \r
25 template <>\r
26 struct generator<openxc::signal>\r
27 {\r
28         const openxc::signal& v_;\r
29         std::uint32_t index_;\r
30         std::string line_prefix_;\r
31         generator(const openxc::signal& v, std::uint32_t index, std::string line_prefix = "")\r
32                 : v_{v}, index_{index}, line_prefix_{line_prefix}\r
33         {\r
34         }\r
35 };\r
36 \r
37 template <typename T>\r
38 generator<T> gen(const T& v, std::string line_prefix = "") { return generator<T>(v, line_prefix); }\r
39 \r
40 generator<openxc::signal> gen(const openxc::signal& v, std::uint32_t index, std::string line_prefix = "")\r
41 {\r
42         return generator<openxc::signal>(v, index, line_prefix);\r
43 }\r
44 \r
45 template <typename T>\r
46 std::ostream& operator<<(std::ostream& o, const generator<T>& v)\r
47 {\r
48         o << v.line_prefix_ << v.v_;\r
49         return o;\r
50 }\r
51 \r
52 template <>\r
53 std::ostream& operator<<(std::ostream& o, const generator<bool>& v)\r
54 {\r
55         o << v.line_prefix_ << (v.v_ ? "true" : "false");\r
56         return o;\r
57 }\r
58 \r
59 template <>\r
60 std::ostream& operator<<(std::ostream& o, const generator<float>& v)\r
61 {\r
62         o << v.line_prefix_ << std::showpoint << v.v_ << "f";\r
63         return o;\r
64 }\r
65 \r
66 template <>\r
67 std::ostream& operator<<(std::ostream& o, const generator<std::string>& v)\r
68 {\r
69         o << v.line_prefix_ << '\"' << v.v_ << '\"';\r
70         return o;\r
71 }\r
72 \r
73 template <typename T>\r
74 std::ostream& operator<<(std::ostream& o, const generator<std::vector<T>>& v)\r
75 {\r
76         o << v.line_prefix_ << "{\n";\r
77         auto sz = v.v_.size();\r
78         for(const T& i : v.v_)\r
79         {\r
80                 o << gen(i, v.line_prefix_ + '\t');\r
81                 if (sz > 1) o << ",";\r
82                 --sz;\r
83                 o << '\n';\r
84         }\r
85         o << v.line_prefix_ << '}';\r
86         return o;\r
87 }\r
88 \r
89 template <>\r
90 std::ostream& operator<<(std::ostream& o, const generator<openxc::message_set>& v)\r
91 {\r
92         o       << v.line_prefix_\r
93                 << '{'\r
94                 << "0, "\r
95                 << gen(v.v_.name()) << ", "\r
96                 << v.v_.buses().size() << ", "\r
97                 << v.v_.messages().size() << ", "\r
98                 << std::accumulate(\r
99                         std::begin(v.v_.messages()),\r
100                         std::end(v.v_.messages()),\r
101                         0,\r
102                         [](int sum, const openxc::can_message& p) { return sum + p.signals().size(); }\r
103                         ) << ", "\r
104                 << v.v_.commands().size() << ", "\r
105                 << v.v_.diagnostic_messages().size() << "}";\r
106         return o;\r
107 }\r
108 \r
109 template <>\r
110 std::ostream& operator<<(std::ostream& o, const generator<openxc::can_message>& v)\r
111 {\r
112         o       << v.line_prefix_\r
113                 << "can_message_definition_t("\r
114                 << "0, "\r
115                 << gen(v.v_.bus()) << ", "\r
116                 << v.v_.id() << ", "\r
117                 << "can_message_format_t::STANDARD, "\r
118                 << "frequency_clock_t(" << gen(v.v_.max_frequency()) << "), "\r
119                 << gen(v.v_.force_send_changed())\r
120                 << ')';\r
121         return o;\r
122 }\r
123 \r
124 template <>\r
125 std::ostream& operator<<(std::ostream& o, const generator<std::map<std::string, std::vector<std::uint32_t>>>& v)\r
126 {\r
127         o << v.line_prefix_ << "{\n";\r
128         std::uint32_t c1 = v.v_.size();\r
129         for(const auto& state : v.v_)\r
130         {\r
131                 std::uint32_t c2 = state.second.size();\r
132                 for(const auto& i : state.second)\r
133                 {\r
134                         o << v.line_prefix_ << "\t" << "{" << i << ", " << gen(state.first) << "}";\r
135                         if (c1 > 1 || c2 > 1) o << ',';\r
136                         o << '\n';\r
137                         --c2;\r
138                 }\r
139                 --c1;\r
140         }\r
141         o << v.line_prefix_ << "}";\r
142         return o;\r
143 }\r
144 \r
145 template <>\r
146 std::ostream& operator<<(std::ostream& o, const generator<openxc::signal>& v)\r
147 {\r
148         o       << v.line_prefix_ << "{\n"\r
149                 << v.line_prefix_ << "\t0,\n"\r
150                 << v.line_prefix_ << "\t" << v.index_ << ",\n"\r
151                 << v.line_prefix_ << "\t" << gen(v.v_.generic_name()) << ",\n"\r
152                 << v.line_prefix_ << "\t" << v.v_.bit_position() << ",\n"\r
153                 << v.line_prefix_ << "\t" << v.v_.bit_size() << ",\n"\r
154                 << v.line_prefix_ << "\t" << gen(v.v_.factor()) << ", \n"\r
155                 << v.line_prefix_ << "\t" << v.v_.offset() << ", \n"\r
156                 << v.line_prefix_ << "\t" << "0,\n"\r
157                 << v.line_prefix_ << "\t" << "0,\n"\r
158                 << v.line_prefix_ << "\tfrequency_clock_t(" << gen(v.v_.max_frequency()) << "),\n"\r
159                 << v.line_prefix_ << "\t" << gen(v.v_.send_same()) << ",\n"\r
160                 << v.line_prefix_ << "\t" << gen(v.v_.force_send_changed()) << ",\n"\r
161                 << gen(v.v_.states(), v.line_prefix_ + '\t') << ",\n"\r
162                 << v.line_prefix_ << '\t' << gen(v.v_.writable()) << ",\n"\r
163                 << v.line_prefix_ << '\t' << (v.v_.decoder().size() ? v.v_.decoder() : "nullptr") << ",\n"\r
164                 << v.line_prefix_ << '\t' << (v.v_.encoder().size() ? v.v_.encoder() : "nullptr") << ",\n"\r
165                 << v.line_prefix_ << '\t' << "false\n"\r
166                 << v.line_prefix_ << "}";\r
167         return o;\r
168 }\r
169 \r
170 template <>\r
171 std::ostream& operator<<(std::ostream& o, const generator<openxc::diagnostic_message>& v)\r
172 {\r
173         o       << v.line_prefix_ << "{\n"\r
174                 << v.line_prefix_ << "\t" << v.v_.pid() << ",\n"\r
175                 << v.line_prefix_ << "\t" << gen(v.v_.name()) << ",\n"\r
176                 << v.line_prefix_ << "\t" << 0 << ",\n"\r
177                 << v.line_prefix_ << "\t" << 0 << ",\n"\r
178                 << v.line_prefix_ << "\t" << "UNIT::INVALID" << ",\n"\r
179                 << v.line_prefix_ << "\t" << gen(v.v_.frequency()) << ",\n"\r
180                 << v.line_prefix_ << "\t" << (v.v_.decoder().size() ? v.v_.decoder() : "nullptr") << ",\n"\r
181                 << v.line_prefix_ << "\t" << (v.v_.callback().size() ? v.v_.callback() : "nullptr") << ",\n"\r
182                 << v.line_prefix_ << "\t" << "true" << "\n"\r
183                 << v.line_prefix_ << "}";\r
184         return o;\r
185 }\r
186 \r
187 /// @brief Generate the configuration code.\r
188 /// @param[in] header Content to be inserted as a header.\r
189 /// @param[in] footer Content to be inserted as a footer.\r
190 /// @param[in] message_set Configuration read from the json file.\r
191 /// @param[in] out Stream to write on.\r
192 void generate(const std::string& header, const std::string& footer, const openxc::message_set& message_set, std::ostream& out)\r
193 {\r
194         out << "#include \"configuration.hpp\"\n"\r
195                 << "#include \"can/can-decoder.hpp\"\n\n";\r
196 \r
197         if (header.size()) out << header << "\n";\r
198 \r
199         out     << "configuration_t::configuration_t()\n"\r
200                 << "    : can_message_set_{" << gen(message_set) << "}\n"\r
201                 << "    , can_message_definition_\n"\r
202                 << "    {\n"\r
203                                 << gen(message_set.messages(), "\t\t") << '\n'\r
204                 << "    }\n"\r
205                 << "    , can_signals_\n"\r
206                 << "    {\n";\r
207                 std::uint32_t message_count = message_set.messages().size();\r
208                 std::uint32_t index = 0;\r
209                 for(const openxc::can_message& m : message_set.messages())\r
210                 {\r
211                         out << "                {\n";\r
212                         std::uint32_t signal_count = m.signals().size();\r
213                         for(const openxc::signal& s : m.signals())\r
214                         {\r
215                                 out << gen(s, index, "                  ");\r
216                                 if (signal_count > 1) out << ',';\r
217                                 --signal_count;\r
218                                 out << '\n';\r
219                         }\r
220                         out << "                }";\r
221                         if (index + 1 < message_count) out << ',';\r
222                         ++index;\r
223                         out << '\n';\r
224                 }\r
225                 out << "        }\n"\r
226                         << "    , diagnostic_messages_\n"\r
227                         << "    {\n"\r
228                         << gen(message_set.diagnostic_messages(), "             ") << "\n"\r
229                         << "    }\n"\r
230                         << "{\n"\r
231                         << "}\n\n"\r
232                         << "const std::string configuration_t::get_diagnostic_bus() const\n"\r
233                         << "{\n";\r
234 \r
235                 std::string active_bus = "";\r
236                 for (const auto& d : message_set.diagnostic_messages())\r
237                 {\r
238                         if (d.bus().size() == 0) std::cerr << "ERROR: The bus name should be set for each diagnostic message." << std::endl;\r
239                         if (active_bus.size() == 0) active_bus = d.bus();\r
240                         if (active_bus != d.bus()) std::cerr << "ERROR: The bus name should be the same for each diagnostic message." << std::endl;\r
241                 }\r
242 \r
243                 out     << "    return " << gen(active_bus) << ";\n"\r
244                         << "}\n\n";\r
245         out << footer << std::endl;\r
246 }\r
247 \r
248 /// @brief Read whole file content to a string.\r
249 /// @param[in] file Path to the file.\r
250 /// @return A std::string which contains the file content. If @c file is an empty string, the return value is also empty.\r
251 /// @exception std::runtime_error Throw this exception if the specified file is not found or cannot be opened.\r
252 std::string read_file(const std::string& file)\r
253 {\r
254         if(file.size() == 0) return std::string();\r
255         \r
256         std::string content;\r
257         std::ifstream stream(file);\r
258         if (stream)\r
259         {\r
260                 stream.seekg(0, std::ios::end);\r
261                 content.reserve(stream.tellg());\r
262                 stream.seekg(0, std::ios::beg);\r
263                 content.assign((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());\r
264                 return content;\r
265         }\r
266         std::stringstream ss;\r
267         ss << "The specified file (" << file << ") is not found!";\r
268         throw std::runtime_error(ss.str());\r
269 }\r
270 \r
271 /// @brief Read whole file content as a json document.\r
272 /// @param[in] file Path to the file.\r
273 /// @return A @c nlohmann::json object.\r
274 /// @exception std::runtime_error Throw this exception if the specified file is not found or cannot be opened.\r
275 nlohmann::json read_json(const std::string& file)\r
276 {\r
277         std::ifstream stream(file);\r
278         if (stream)\r
279         {\r
280                 nlohmann::json result;\r
281                 stream >> result;\r
282                 return result;\r
283         }\r
284         std::stringstream ss;\r
285         ss << "The specified file (" << file << ") is not found!";\r
286         throw std::runtime_error(ss.str());\r
287 }\r
288 \r
289 /// @brief Entry point.\r
290 /// @param[in] argc Argument's count.\r
291 /// @param[in] argv Argument's array.\r
292 /// @return Exit code, zero if success.\r
293 int main(int argc, char** argv)\r
294 {\r
295         //std::ios::sync_with_stdio(false);\r
296         \r
297         try\r
298         {\r
299                 std::string appName = boost::filesystem::basename(argv[0]);\r
300                 std::string message_set_file;\r
301                 std::string output_file;\r
302                 std::string header_file;\r
303                 std::string footer_file;\r
304 \r
305                 namespace bpo = boost::program_options;\r
306                 bpo::options_description desc("Options");\r
307                 desc.add_options()\r
308                         ("help,h", "Display this help.")\r
309                         ("message-set,m", bpo::value<std::string>(&message_set_file)->required(), "The message set definition file.")\r
310                         ("output,o", bpo::value<std::string>(&output_file), "An output file, if not specified stdout is used.")\r
311                         ("header,h", bpo::value<std::string>(&header_file), "A file to copy at the top of the generated output.")\r
312                         ("footer,f", bpo::value<std::string>(&footer_file), "A file to copy at the end of the generated output.");\r
313 \r
314                 bpo::variables_map vm;\r
315                 try\r
316                 {\r
317                         bpo::store(bpo::parse_command_line(argc, argv, desc), vm);\r
318 \r
319                         if (vm.count("help"))\r
320                         {\r
321                                 std::cout << desc << std::endl;\r
322                                 return EXIT_SUCCESS;\r
323                         }\r
324 \r
325                         bpo::notify(vm);\r
326 \r
327                         std::stringstream header;\r
328                         header << read_file(header_file);\r
329                                                 \r
330                         std::string footer = read_file(footer_file);\r
331                         openxc::message_set message_set;\r
332                         message_set.from_json(read_json(message_set_file));\r
333 \r
334                         boost::filesystem::path message_set_path(message_set_file);\r
335                         message_set_path.remove_filename();\r
336                         for(const auto& s : message_set.extra_sources())\r
337                         {\r
338                                 boost::filesystem::path extra_source(s);\r
339                                 if (!extra_source.is_complete()) extra_source = message_set_path / extra_source;\r
340                                 header << "\n// >>>>> " << s << " >>>>>\n" << read_file(extra_source.string()) << "\n// <<<<< " << s << " <<<<<\n";\r
341                         }\r
342 \r
343                         std::ofstream out;\r
344                         if (output_file.size())\r
345                         {\r
346                                 out = std::ofstream(output_file);\r
347                                 if(!out)\r
348                                 {\r
349                                         std::stringstream ss;\r
350                                         ss << "Can't open the ouput file (" << output_file << ") for writing!";\r
351                                         throw std::runtime_error(ss.str());\r
352                                 }\r
353                         }\r
354                         \r
355                         generate(header.str(), footer, message_set, output_file.size() ? out : std::cout); \r
356 \r
357                 }\r
358                 catch (bpo::required_option& e)\r
359                 {\r
360                         std::cerr << "ERROR: Argument required - " << e.what() << std::endl;\r
361                         std::cout << desc << std::endl;\r
362                         return EXIT_COMMAND_LINE_ERROR;\r
363                 }\r
364                 catch (bpo::error& e)\r
365                 {\r
366                         std::cerr << "ERROR: Command line error - " << e.what() << std::endl;\r
367                         std::cout << desc << std::endl;\r
368                         return EXIT_COMMAND_LINE_ERROR;\r
369                 }\r
370                 catch(std::exception& e)\r
371                 {\r
372                         std::cerr << "ERROR: " << e.what() << std::endl;\r
373                         return EXIT_PROGRAM_ERROR;\r
374                 }\r
375         }\r
376         catch (std::exception& e)\r
377         {\r
378                 std::cerr << "ERROR: Unhandled exception - " << e.what() << std::endl;\r
379                 return EXIT_UNKNOWN_ERROR;\r
380         }\r
381 \r
382         return EXIT_SUCCESS;\r
383 }\r