c001df3b6e9e36bc14c9f2289f8e6c4c2502204b
[src/low-level-can-generator.git] / src / main.cpp
1 /*\r
2  * Copyright (C) 2015, 2016 "IoT.bzh"\r
3  * Author "Loïc Collignon" <loic.collignon@iot.bzh>\r
4  * Author "Romain Forlot" <romain.forlot@iot.bzh>\r
5  *\r
6  * Licensed under the Apache License, Version 2.0 (the "License");\r
7  * you may not use this file except in compliance with the License.\r
8  * You may obtain a copy of the License at\r
9  *\r
10  *       http://www.apache.org/licenses/LICENSE-2.0\r
11  *\r
12  * Unless required by applicable law or agreed to in writing, software\r
13  * distributed under the License is distributed on an "AS IS" BASIS,\r
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
15  * See the License for the specific language governing permissions and\r
16  * limitations under the License.\r
17  */\r
18 \r
19 #include <unistd.h>\r
20 #include <stdlib.h>\r
21 #include <libgen.h>\r
22 #include <exception>\r
23 #include <fstream>\r
24 #include <iostream>\r
25 #include <string>\r
26 #include <numeric>\r
27 #include <iterator>\r
28 #include <json.hpp>\r
29 #include "openxc/message_set.hpp"\r
30 #include "openxc/decoder.hpp"\r
31 \r
32 #define EXIT_SUCCESS                            0\r
33 #define EXIT_UNKNOWN_ERROR                      1\r
34 #define EXIT_COMMAND_LINE_ERROR         2\r
35 #define EXIT_PROGRAM_ERROR                      3\r
36 \r
37 \r
38 /**\r
39  * FLAGS\r
40  */\r
41 \r
42 #define INVALID_FLAG 0x0001\r
43 #define STANDARD_ID 0x0002\r
44 #define EXTENDED_ID 0x0004\r
45 #define BCM_PROTOCOL 0x0008\r
46 #define J1939_PROTOCOL 0x0010\r
47 #define J1939_ADDR_CLAIM_PROTOCOL 0x0020\r
48 #define ISOTP_PROTOCOL 0x0040\r
49 #define FD_FRAME 0x0800\r
50 \r
51 #define VERSION_LOW_CAN "2.0"\r
52 \r
53 \r
54 std::string VERSION_FILE = "1.0";\r
55 \r
56 template <typename T>\r
57 struct generator\r
58 {\r
59         T v_;\r
60         std::string line_prefix_;\r
61         generator(T v, std::string line_prefix = "") : v_{v}, line_prefix_{line_prefix} {}\r
62 };\r
63 \r
64 template <>\r
65 struct generator<openxc::signal>\r
66 {\r
67         const openxc::signal& v_;\r
68         std::uint32_t index_;\r
69         std::string line_prefix_;\r
70         generator(const openxc::signal& v, std::uint32_t index, std::string line_prefix = "")\r
71                 : v_{v}, index_{index}, line_prefix_{line_prefix}\r
72         {\r
73         }\r
74 };\r
75 \r
76 template <typename T>\r
77 generator<T> gen(const T& v, std::string line_prefix = "") { return generator<T>(v, line_prefix); }\r
78 \r
79 generator<openxc::signal> gen(const openxc::signal& v, std::uint32_t index, std::string line_prefix = "")\r
80 {\r
81         return generator<openxc::signal>(v, index, line_prefix);\r
82 }\r
83 \r
84 template <typename T>\r
85 std::ostream& operator<<(std::ostream& o, const generator<T>& v)\r
86 {\r
87         o << v.line_prefix_ << v.v_;\r
88         return o;\r
89 }\r
90 \r
91 template <>\r
92 std::ostream& operator<<(std::ostream& o, const generator<bool>& v)\r
93 {\r
94         o << v.line_prefix_ << (v.v_ ? "true" : "false");\r
95         return o;\r
96 }\r
97 \r
98 template <>\r
99 std::ostream& operator<<(std::ostream& o, const generator<float>& v)\r
100 {\r
101         o << v.line_prefix_ << std::showpoint << v.v_ << "f";\r
102         return o;\r
103 }\r
104 \r
105 template <>\r
106 std::ostream& operator<<(std::ostream& o, const generator<std::string>& v)\r
107 {\r
108         o << v.line_prefix_ << '\"' << v.v_ << '\"';\r
109         return o;\r
110 }\r
111 \r
112 template <typename T>\r
113 std::ostream& operator<<(std::ostream& o, const generator<std::vector<T>>& v)\r
114 {\r
115 //      o << v.line_prefix_;\r
116         auto sz = v.v_.size();\r
117         for(const T& i : v.v_)\r
118         {\r
119                 o << gen(i, v.line_prefix_ + '\t');\r
120                 if (sz > 1) o << ",";\r
121                 --sz;\r
122 //              o << '\n';\r
123         }\r
124 //      o << v.line_prefix_;\r
125         return o;\r
126 }\r
127 \r
128 template <>\r
129 std::ostream& operator<<(std::ostream& o, const generator<openxc::message_set>& v)\r
130 {\r
131         o       << v.line_prefix_\r
132                 << "{std::make_shared<message_set_t>(message_set_t{"\r
133                 << "0,"\r
134                 << gen(v.v_.name()) << ",\n"\r
135                 << "\t\t\t{ // beginning message_definition_ vector\n"\r
136                 << gen(v.v_.messages(), "\t\t\t")\r
137                 << "\n\t\t}, // end message_definition vector\n"\r
138                 << "\t\t\t{ // beginning diagnostic_messages_ vector\n"\r
139                 << gen(v.v_.diagnostic_messages(),"\t\t\t") << "\n"\r
140                 << "\t\t\t} // end diagnostic_messages_ vector\n"\r
141                 << "\t\t})} // end message_set entry\n";\r
142         return o;\r
143 }\r
144 \r
145 template <>\r
146 std::ostream& operator<<(std::ostream& o, const generator<std::map<std::string, std::vector<std::uint32_t>>>& v)\r
147 {\r
148         o << v.line_prefix_ << "{\n";\r
149         std::uint32_t c1 = (uint32_t)v.v_.size();\r
150         for(const auto& state : v.v_)\r
151         {\r
152                 std::uint32_t c2 = (uint32_t)state.second.size();\r
153                 for(const auto& i : state.second)\r
154                 {\r
155                         o << v.line_prefix_ << "\t" << "{" << i << "," << gen(state.first) << "}";\r
156                         if (c1 > 1 || c2 > 1) o << ',';\r
157                         o << '\n';\r
158                         --c2;\r
159                 }\r
160                 --c1;\r
161         }\r
162         o << v.line_prefix_ << "}";\r
163         return o;\r
164 }\r
165 \r
166 template <>\r
167 std::ostream& operator<<(std::ostream& o, const generator<openxc::signal>& v)\r
168 {\r
169         o       << v.line_prefix_ << "{std::make_shared<signal_t> (signal_t{\n"\r
170                 << v.line_prefix_ << "\t" << gen(v.v_.generic_name()) << ",// generic_name\n"\r
171                 << v.line_prefix_ << "\t" << v.v_.bit_position() << ",// bit_position\n"\r
172                 << v.line_prefix_ << "\t" << v.v_.bit_size() << ",// bit_size\n"\r
173                 << v.line_prefix_ << "\t" << gen(v.v_.factor()) << ",// factor\n"\r
174                 << v.line_prefix_ << "\t" << gen(v.v_.offset()) << ",// offset\n"\r
175                 << v.line_prefix_ << "\t" << "0,// min_value\n"\r
176                 << v.line_prefix_ << "\t" << "0,// max_value\n"\r
177                 << v.line_prefix_ << "\tfrequency_clock_t(" << gen(v.v_.max_frequency()) << "),// frequency\n"\r
178                 << v.line_prefix_ << "\t" << gen(v.v_.send_same()) << ",// send_same\n"\r
179                 << v.line_prefix_ << "\t" << gen(v.v_.force_send_changed()) << ",// force_send_changed\n"\r
180                 << gen(v.v_.states(), v.line_prefix_ + '\t') << ",// states\n"\r
181                 << v.line_prefix_ << '\t' << gen(v.v_.writable()) << ",// writable\n"\r
182                 << v.line_prefix_ << '\t' << decoder_t::add_decoder(v.v_.decoder().size() ? v.v_.decoder() : v.v_.states().size() ? "decoder_t::decode_state" : "nullptr"\r
183                         , VERSION_FILE\r
184                         , VERSION_LOW_CAN) << ",// decoder\n"\r
185                 << v.line_prefix_ << '\t' << (v.v_.encoder().size() ? v.v_.encoder() : "nullptr") << ",// encoder\n"\r
186                 << v.line_prefix_ << '\t' << "false,// received\n";\r
187                 std::string multi_first = "";\r
188                 if(v.v_.multiplex().first){\r
189                         multi_first = "true";\r
190                 }else{\r
191                         multi_first = "false";\r
192                 }\r
193                 std::string multi = "std::make_pair<bool, int>(" + multi_first + ", " + std::to_string(v.v_.multiplex().second) + ")";\r
194         o       << v.line_prefix_ << '\t' << multi << ",// multiplex\n"\r
195                 << v.line_prefix_ << '\t' << gen(v.v_.is_big_endian()) << ",// is_big_endian\n"\r
196                 << v.line_prefix_ << '\t' << gen(v.v_.is_signed()) << ",// is_signed\n"\r
197                 << v.line_prefix_ << "\t" << gen(v.v_.unit()) << "// unit\n"\r
198                 << v.line_prefix_ << "})}";\r
199         return o;\r
200 }\r
201 \r
202 template <>\r
203 std::ostream& operator<<(std::ostream& o, const generator<openxc::can_message>& v)\r
204 {\r
205         o       << v.line_prefix_\r
206                 << "{std::make_shared<message_definition_t>(message_definition_t{"\r
207                 << gen(v.v_.bus()) << ","\r
208                 << v.v_.id() << ","\r
209                 << "\"" << v.v_.name() << "\","\r
210                 << v.v_.length() << ",";\r
211                 uint32_t flags = 0;\r
212                 if(v.v_.is_fd())\r
213                 {\r
214                         flags = flags|FD_FRAME;\r
215                 }\r
216 \r
217                 if(v.v_.is_j1939())\r
218                 {\r
219                         flags = flags|J1939_PROTOCOL;\r
220                 }\r
221 \r
222                 if(v.v_.is_isotp())\r
223                 {\r
224                         flags = flags|ISOTP_PROTOCOL;\r
225                 }\r
226 \r
227                 if(v.v_.is_extended())\r
228                 {\r
229                         flags = flags|EXTENDED_ID;\r
230                 }\r
231                 else\r
232                 {\r
233                         flags = flags|STANDARD_ID;\r
234                 }\r
235 \r
236 \r
237                 if(v.v_.is_fd())\r
238                 {\r
239                         flags = flags|FD_FRAME;\r
240                 }\r
241 \r
242                 o << gen(flags) << ",";\r
243 \r
244         o       << "frequency_clock_t(" << gen(v.v_.max_frequency()) << "),"\r
245                 << gen(v.v_.force_send_changed()) << ",";\r
246                 std::uint32_t index = 0;\r
247         o       << "\n\t\t\t\t\t{ // beginning signals vector\n";\r
248                         std::uint32_t signal_count = (uint32_t)v.v_.signals().size();\r
249                         for(const openxc::signal& s : v.v_.signals())\r
250                         {\r
251         o                       << gen(s, index,"\t\t\t\t\t\t");\r
252                                 if (signal_count > 1) o << ',';\r
253                                 --signal_count;\r
254         o                       << '\n';\r
255                         }\r
256         o       << "\t\t\t\t\t} // end signals vector\n"\r
257                 << "\t\t\t\t})} // end message_definition entry\n";\r
258         return o;\r
259 }\r
260 \r
261 template <>\r
262 std::ostream& operator<<(std::ostream& o, const generator<openxc::diagnostic_message>& v)\r
263 {\r
264         o       << v.line_prefix_ << "{std::make_shared<diagnostic_message_t>(diagnostic_message_t{\n"\r
265                 << v.line_prefix_ << "\t" << v.v_.pid() << ",\n"\r
266                 << v.line_prefix_ << "\t" << gen(v.v_.name()) << ",\n"\r
267                 << v.line_prefix_ << "\t" << 0 << ",\n"\r
268                 << v.line_prefix_ << "\t" << 0 << ",\n"\r
269                 << v.line_prefix_ << "\t" << "UNIT::INVALID" << ",\n"\r
270                 << v.line_prefix_ << "\t" << gen(v.v_.frequency()) << ",\n"\r
271                 << v.line_prefix_ << "\t" << decoder_t::add_decoder((v.v_.decoder().size() ? v.v_.decoder() : "nullptr"),VERSION_FILE,VERSION_LOW_CAN) << ",\n"\r
272                 << v.line_prefix_ << "\t" << (v.v_.callback().size() ? v.v_.callback() : "nullptr") << ",\n"\r
273                 << v.line_prefix_ << "\t" << "true" << ",\n"\r
274                 << v.line_prefix_ << "\t" << "false" << "\n"\r
275                 << v.line_prefix_ << "})}\n";\r
276         return o;\r
277 }\r
278 \r
279 /// @brief Generate the application code.\r
280 /// @param[in] header Content to be inserted as a header.\r
281 /// @param[in] footer Content to be inserted as a footer.\r
282 /// @param[in] message_set application read from the json file.\r
283 /// @param[in] out Stream to write on.\r
284 void generate(const std::string& header, const std::string& footer, const openxc::message_set& message_set, std::ostream& out)\r
285 {\r
286         out << "#include \"application.hpp\"\n"\r
287                 << "#include \"../can/can-decoder.hpp\"\n"\r
288                 << "#include \"../can/can-encoder.hpp\"\n\n";\r
289 \r
290         if (header.size()) out << header << "\n";\r
291 \r
292         out     << "application_t::application_t()\n"\r
293                 << "    : can_bus_manager_{utils::config_parser_t{\"/etc/dev-mapping.conf\"}}\n"\r
294                 << "    , message_set_{\n"\r
295                 << gen(message_set, "\t\t")\r
296                 << "\t} // end message_set vector\n"\r
297                 << "{\n"\r
298                 << "    for(std::shared_ptr<message_set_t> cms: message_set_)\n"\r
299                 << "    {\n"\r
300                 << "            std::vector<std::shared_ptr<message_definition_t>> messages_definition = cms->get_messages_definition();\n"\r
301                 << "            for(std::shared_ptr<message_definition_t> cmd : messages_definition)\n"\r
302                 << "            {\n"\r
303                 << "                    cmd->set_parent(cms);\n"\r
304                 << "                    std::vector<std::shared_ptr<signal_t>> signals = cmd->get_signals();\n"\r
305                 << "                    for(std::shared_ptr<signal_t> sig: signals)\n"\r
306                 << "                    {\n"\r
307                 << "                            sig->set_parent(cmd);\n"\r
308                 << "                    }\n"\r
309                 << "            }\n\n"\r
310                 << "            std::vector<std::shared_ptr<diagnostic_message_t>> diagnostic_messages = cms->get_diagnostic_messages();\n"\r
311                 << "            for(std::shared_ptr<diagnostic_message_t> dm : diagnostic_messages)\n"\r
312                 << "            {\n"\r
313                 << "                    dm->set_parent(cms);\n"\r
314                 << "            }\n"\r
315                 << "    }\n"\r
316                 << "            }\n\n"\r
317                 << "const std::string application_t::get_diagnostic_bus() const\n"\r
318                 << "{\n";\r
319 \r
320                 std::string active_bus = "";\r
321                 for (const auto& d : message_set.diagnostic_messages())\r
322                 {\r
323                         if (d.bus().size() == 0) std::cerr << "ERROR: The bus name should be set for each diagnostic message." << std::endl;\r
324                         if (active_bus.size() == 0) active_bus = d.bus();\r
325                         if (active_bus != d.bus()) std::cerr << "ERROR: The bus name should be the same for each diagnostic message." << std::endl;\r
326                 }\r
327 \r
328         out     << "\treturn " << gen(active_bus) << ";\n"\r
329                 << "}\n\n";\r
330 \r
331 \r
332         out << decoder_t::apply_patch();\r
333 \r
334 \r
335 \r
336         out     << footer << std::endl;\r
337 }\r
338 \r
339 /// @brief Read whole file content to a string.\r
340 /// @param[in] file Path to the file.\r
341 /// @return A std::string which contains the file content. If @c file is an empty string, the return value is also empty.\r
342 /// @exception std::runtime_error Throw this exception if the specified file is not found or cannot be opened.\r
343 std::string read_file(const std::string& file)\r
344 {\r
345         if(file.size() == 0) return std::string();\r
346 \r
347         std::string content;\r
348         std::ifstream stream(file);\r
349         if (stream)\r
350         {\r
351                 stream.seekg(0, std::ios::end);\r
352                 content.reserve(stream.tellg());\r
353                 stream.seekg(0, std::ios::beg);\r
354                 content.assign((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());\r
355                 return content;\r
356         }\r
357         std::stringstream ss;\r
358         ss << "The specified file (" << file << ") is not found!";\r
359         throw std::runtime_error(ss.str());\r
360 }\r
361 \r
362 /// @brief Read whole file content as a json document.\r
363 /// @param[in] file Path to the file.\r
364 /// @return A @c nlohmann::json object.\r
365 /// @exception std::runtime_error Throw this exception if the specified file is not found or cannot be opened.\r
366 nlohmann::json read_json(const std::string& file)\r
367 {\r
368         std::ifstream stream(file);\r
369         if (stream)\r
370         {\r
371                 nlohmann::json result;\r
372                 stream >> result;\r
373                 return result;\r
374         }\r
375         std::stringstream ss;\r
376         ss << "The specified file (" << file << ") is not found!";\r
377         throw std::runtime_error(ss.str());\r
378 }\r
379 \r
380 // function that show the help information\r
381 void showhelpinfo(char *s)\r
382 {\r
383 std::cout<<"Usage:   "<<s<<" <-m inpout.json> [-o application-generated.cpp]"<< std::endl;\r
384 std::cout<<"option:  "<<"-m  input.json : JSON file describing CAN messages and signals"<< std::endl;\r
385 std::cout<<"         "<<"-h header.cpp : header source file insert at the beginning of generated file"<< std::endl;\r
386 std::cout<<"         "<<"-f footer.cpp : footer source file append to generated file."<< std::endl;\r
387 std::cout<<"         "<<"-o application-generated.cpp : output source file. Name has to be application-generated.cpp"<< std::endl;\r
388 }\r
389 \r
390 /// @brief Entry point.\r
391 /// @param[in] argc Argument's count.\r
392 /// @param[in] argv Argument's array.\r
393 /// @return Exit code, zero if success.\r
394 int main(int argc, char *argv[])\r
395 {\r
396 \r
397         decoder_t::init_decoder();\r
398         try\r
399         {\r
400                 std::string appName = argv[0];\r
401                 std::string message_set_file;\r
402                 std::string output_file;\r
403                 std::string header_file;\r
404                 std::string footer_file;\r
405 \r
406                 char tmp;\r
407                 /*if the program is ran witout options ,it will show the usgage and exit*/\r
408                 if(argc == 1)\r
409                 {\r
410                         showhelpinfo(argv[0]);\r
411                         exit(1);\r
412                 }\r
413                 /*use function getopt to get the arguments with option."hu:p:s:v" indicate\r
414                 that option h,v are the options without arguments while u,p,s are the\r
415                 options with arguments*/\r
416                 while((tmp=(char)getopt(argc,argv,"m:h:f:o:"))!=-1)\r
417                 {\r
418                         switch(tmp)\r
419                         {\r
420                         case 'h':\r
421                                 header_file = optarg;\r
422                                 break;\r
423                         case 'f':\r
424                                 footer_file = optarg;\r
425                                 break;\r
426                         case 'm':\r
427                                 message_set_file = optarg;\r
428                                 break;\r
429                         case 'o':\r
430                                 output_file = optarg;\r
431                                 break;\r
432                         default:\r
433                                 showhelpinfo(argv[0]);\r
434                         break;\r
435                         }\r
436                 }\r
437 \r
438                 std::stringstream header;\r
439                 header << read_file(header_file);\r
440 \r
441                 std::string footer = read_file(footer_file);\r
442                 openxc::message_set message_set;\r
443                 message_set.from_json(read_json(message_set_file));\r
444 \r
445                 std::string message_set_path = dirname(strdup(message_set_file.c_str()));\r
446                 for(const auto& s : message_set.extra_sources())\r
447                 {\r
448                         std::string extra_source = s;\r
449                         extra_source = message_set_path + "/" + extra_source;\r
450                         header << "\n// >>>>> " << s << " >>>>>\n" << read_file(extra_source) << "\n// <<<<< " << s << " <<<<<\n";\r
451                 }\r
452 \r
453                 std::ofstream out;\r
454                 if (output_file.size())\r
455                 {\r
456                         out.open(output_file);\r
457                         if(!out)\r
458                         {\r
459                                 std::stringstream ss;\r
460                                 ss << "Can't open the ouput file (" << output_file << ") for writing!";\r
461                                 throw std::runtime_error(ss.str());\r
462                         }\r
463                 }\r
464                 VERSION_FILE = message_set.version();\r
465                 generate(header.str(), footer, message_set, output_file.size() ? out : std::cout);\r
466         }\r
467         catch (std::exception& e)\r
468         {\r
469                 std::cerr << "ERROR: Unhandled exception - " << e.what() << std::endl;\r
470                 return EXIT_UNKNOWN_ERROR;\r
471         }\r
472         return EXIT_SUCCESS;\r
473 }\r