Update to match current binding expectations
[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  * Taken from include/low-can/can-message.hpp in agl-service-can-low-level.\r
42  * The flags values in the generated message_definition_t definitions needs to\r
43  * match usage of these flags in the binding, e.g. in the various member\r
44  * functions in low-can-binding/can/message-definition.cpp.\r
45  *\r
46  * ATM the only flags known to be used are:\r
47  *\r
48  * CAN_PROTOCOL_WITH_FD_FRAME\r
49  * J1939_PROTOCOL\r
50  * ISOTP_PROTOCOL\r
51  * BYTE_FRAME_IS_BIG_ENDIAN\r
52  *\r
53  * Note that for BYTE_FRAME_IS_BIG_ENDIAN, even though it is referenced in\r
54  * message-definition.cpp, the member function that does so seems currently\r
55  * unused, so it is not clear what the intended usage actually is.\r
56  * The JSON parser for CAN messages would likely need an additional field\r
57  * added to allow setting it, for now that is being left for a future update.\r
58  */\r
59 \r
60 #define CAN_PROTOCOL               0x0001\r
61 #define CAN_PROTOCOL_WITH_FD_FRAME 0x0002\r
62 #define J1939_ADDR_CLAIM_PROTOCOL  0x0004\r
63 #define J1939_PROTOCOL             0x0008\r
64 #define ISOTP_PROTOCOL             0x0010\r
65 #define ISOTP_SEND                 0x0020\r
66 #define ISOTP_RECEIVE              0x0040\r
67 #define BYTE_FRAME_IS_BIG_ENDIAN   0x0080\r
68 #define BIT_POSITION_REVERSED      0x0100\r
69 #define CONTINENTAL_BIT_POSITION   0x0200\r
70 #define INVALID_FLAG               0x8000\r
71 \r
72 \r
73 #define VERSION_LOW_CAN "2.0"\r
74 \r
75 \r
76 std::string VERSION_FILE = "1.0";\r
77 \r
78 template <typename T>\r
79 struct generator\r
80 {\r
81         T v_;\r
82         std::string line_prefix_;\r
83         generator(T v, std::string line_prefix = "") : v_{v}, line_prefix_{line_prefix} {}\r
84 };\r
85 \r
86 template <>\r
87 struct generator<openxc::signal>\r
88 {\r
89         const openxc::signal& v_;\r
90         std::uint32_t index_;\r
91         std::string line_prefix_;\r
92         generator(const openxc::signal& v, std::uint32_t index, std::string line_prefix = "")\r
93                 : v_{v}, index_{index}, line_prefix_{line_prefix}\r
94         {\r
95         }\r
96 };\r
97 \r
98 template <typename T>\r
99 generator<T> gen(const T& v, std::string line_prefix = "") { return generator<T>(v, line_prefix); }\r
100 \r
101 generator<openxc::signal> gen(const openxc::signal& v, std::uint32_t index, std::string line_prefix = "")\r
102 {\r
103         return generator<openxc::signal>(v, index, line_prefix);\r
104 }\r
105 \r
106 template <typename T>\r
107 std::ostream& operator<<(std::ostream& o, const generator<T>& v)\r
108 {\r
109         o << v.line_prefix_ << v.v_;\r
110         return o;\r
111 }\r
112 \r
113 template <>\r
114 std::ostream& operator<<(std::ostream& o, const generator<bool>& v)\r
115 {\r
116         o << v.line_prefix_ << (v.v_ ? "true" : "false");\r
117         return o;\r
118 }\r
119 \r
120 template <>\r
121 std::ostream& operator<<(std::ostream& o, const generator<float>& v)\r
122 {\r
123         o << v.line_prefix_ << std::showpoint << v.v_ << "f";\r
124         return o;\r
125 }\r
126 \r
127 template <>\r
128 std::ostream& operator<<(std::ostream& o, const generator<std::string>& v)\r
129 {\r
130         o << v.line_prefix_ << '\"' << v.v_ << '\"';\r
131         return o;\r
132 }\r
133 \r
134 template <typename T>\r
135 std::ostream& operator<<(std::ostream& o, const generator<std::vector<T>>& v)\r
136 {\r
137 //      o << v.line_prefix_;\r
138         auto sz = v.v_.size();\r
139         for(const T& i : v.v_)\r
140         {\r
141                 o << gen(i, v.line_prefix_ + '\t');\r
142                 if (sz > 1) o << ",";\r
143                 --sz;\r
144 //              o << '\n';\r
145         }\r
146 //      o << v.line_prefix_;\r
147         return o;\r
148 }\r
149 \r
150 template <>\r
151 std::ostream& operator<<(std::ostream& o, const generator<openxc::message_set>& v)\r
152 {\r
153         o       << "std::shared_ptr<message_set_t> cms = "\r
154                 << "std::make_shared<message_set_t>(message_set_t{"\r
155                 << "0,"\r
156                 << gen(v.v_.name()) << ",\n"\r
157                 << "\t{ // beginning message_definition_ vector\n"\r
158                 << gen(v.v_.messages(), "\t")\r
159                 << "\t}, // end message_definition vector\n"\r
160                 << "\t{ // beginning diagnostic_messages_ vector\n"\r
161                 << gen(v.v_.diagnostic_messages(),"\t") << "\n"\r
162                 << "\t} // end diagnostic_messages_ vector\n"\r
163                 << "}); // end message_set entry\n";\r
164         return o;\r
165 }\r
166 \r
167 template <>\r
168 std::ostream& operator<<(std::ostream& o, const generator<std::map<std::string, std::vector<std::uint32_t>>>& v)\r
169 {\r
170         o << v.line_prefix_ << "{\n";\r
171         std::uint32_t c1 = (uint32_t)v.v_.size();\r
172         for(const auto& state : v.v_)\r
173         {\r
174                 std::uint32_t c2 = (uint32_t)state.second.size();\r
175                 for(const auto& i : state.second)\r
176                 {\r
177                         o << v.line_prefix_ << "\t" << "{" << i << "," << gen(state.first) << "}";\r
178                         if (c1 > 1 || c2 > 1) o << ',';\r
179                         o << '\n';\r
180                         --c2;\r
181                 }\r
182                 --c1;\r
183         }\r
184         o << v.line_prefix_ << "}";\r
185         return o;\r
186 }\r
187 \r
188 template <>\r
189 std::ostream& operator<<(std::ostream& o, const generator<openxc::signal>& v)\r
190 {\r
191         o       << v.line_prefix_ << "{std::make_shared<signal_t> (signal_t{\n"\r
192                 << v.line_prefix_ << "\t" << gen(v.v_.generic_name()) << ",// generic_name\n"\r
193                 << v.line_prefix_ << "\t" << v.v_.bit_position() << ",// bit_position\n"\r
194                 << v.line_prefix_ << "\t" << v.v_.bit_size() << ",// bit_size\n"\r
195                 << v.line_prefix_ << "\t" << gen(v.v_.factor()) << ",// factor\n"\r
196                 << v.line_prefix_ << "\t" << gen(v.v_.offset()) << ",// offset\n"\r
197                 << v.line_prefix_ << "\t" << "0,// min_value\n"\r
198                 << v.line_prefix_ << "\t" << "0,// max_value\n"\r
199                 << v.line_prefix_ << "\tfrequency_clock_t(" << gen(v.v_.max_frequency()) << "),// frequency\n"\r
200                 << v.line_prefix_ << "\t" << gen(v.v_.send_same()) << ",// send_same\n"\r
201                 << v.line_prefix_ << "\t" << gen(v.v_.force_send_changed()) << ",// force_send_changed\n"\r
202                 << gen(v.v_.states(), v.line_prefix_ + '\t') << ",// states\n"\r
203                 << v.line_prefix_ << '\t' << gen(v.v_.writable()) << ",// writable\n"\r
204                 << 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
205                         , VERSION_FILE\r
206                         , VERSION_LOW_CAN) << ",// decoder\n"\r
207                 << v.line_prefix_ << '\t' << (v.v_.encoder().size() ? v.v_.encoder() : "nullptr") << ",// encoder\n"\r
208                 << v.line_prefix_ << '\t' << "false,// received\n";\r
209                 std::string multi_first = "";\r
210                 if(v.v_.multiplex().first){\r
211                         multi_first = "true";\r
212                 }else{\r
213                         multi_first = "false";\r
214                 }\r
215                 std::string multi = "std::make_pair<bool, int>(" + multi_first + ", " + std::to_string(v.v_.multiplex().second) + ")";\r
216         o       << v.line_prefix_ << '\t' << multi << ",// multiplex\n"\r
217                 << v.line_prefix_ << "\tstatic_cast<sign_t>(" << gen(v.v_.sign()) << "),// signed\n"\r
218                 << v.line_prefix_ << '\t' << gen(v.v_.bit_sign_position()) << ",// bit_sign_position\n"\r
219                 << v.line_prefix_ << "\t" << gen(v.v_.unit()) << "// unit\n"\r
220                 << v.line_prefix_ << "})}";\r
221         return o;\r
222 }\r
223 \r
224 template <>\r
225 std::ostream& operator<<(std::ostream& o, const generator<openxc::can_message>& v)\r
226 {\r
227         o       << v.line_prefix_\r
228                 << "{std::make_shared<message_definition_t>(message_definition_t{"\r
229                 << gen(v.v_.bus()) << ","\r
230                 << v.v_.id() << ","\r
231                 << "\"" << v.v_.name() << "\","\r
232                 << (v.v_.length() != 0 ? v.v_.length() : 8) << ",";\r
233                 uint32_t flags = 0;\r
234                 if(v.v_.is_fd())\r
235                 {\r
236                         flags = flags|CAN_PROTOCOL_WITH_FD_FRAME;\r
237                 }\r
238 \r
239                 if(v.v_.is_j1939())\r
240                 {\r
241                         flags = flags|J1939_PROTOCOL;\r
242                 }\r
243 \r
244                 if(v.v_.is_isotp())\r
245                 {\r
246                         flags = flags|ISOTP_PROTOCOL;\r
247                 }\r
248 \r
249                 o << gen(flags) << ",";\r
250 \r
251         o       << "frequency_clock_t(" << gen(v.v_.max_frequency()) << "),"\r
252                 << gen(v.v_.force_send_changed()) << ",";\r
253                 std::uint32_t index = 0;\r
254         o       << "\n\t\t\t{ // beginning signals vector\n";\r
255                         std::uint32_t signal_count = (uint32_t)v.v_.signals().size();\r
256                         for(const openxc::signal& s : v.v_.signals())\r
257                         {\r
258         o                       << gen(s, index,"\t\t\t\t");\r
259                                 if (signal_count > 1) o << ',';\r
260                                 --signal_count;\r
261         o                       << '\n';\r
262                         }\r
263         o       << "\t\t\t} // end signals vector\n"\r
264                 << "\t\t})} // end message_definition entry\n";\r
265         return o;\r
266 }\r
267 \r
268 template <>\r
269 std::ostream& operator<<(std::ostream& o, const generator<openxc::diagnostic_message>& v)\r
270 {\r
271         o       << v.line_prefix_ << "{std::make_shared<diagnostic_message_t>(diagnostic_message_t{\n"\r
272                 << v.line_prefix_ << "\t" << v.v_.pid() << ",\n"\r
273                 << v.line_prefix_ << "\t" << gen(v.v_.name()) << ",\n"\r
274                 << v.line_prefix_ << "\t" << 0 << ",\n"\r
275                 << v.line_prefix_ << "\t" << 0 << ",\n"\r
276                 << v.line_prefix_ << "\t" << "UNIT::INVALID" << ",\n"\r
277                 << v.line_prefix_ << "\t" << gen(v.v_.frequency()) << ",\n"\r
278                 << v.line_prefix_ << "\t" << decoder_t::add_decoder((v.v_.decoder().size() ? v.v_.decoder() : "nullptr"),VERSION_FILE,VERSION_LOW_CAN) << ",\n"\r
279                 << v.line_prefix_ << "\t" << (v.v_.callback().size() ? v.v_.callback() : "nullptr") << ",\n"\r
280                 << v.line_prefix_ << "\t" << "true" << ",\n"\r
281                 << v.line_prefix_ << "\t" << "false" << "\n"\r
282                 << v.line_prefix_ << "})}\n";\r
283         return o;\r
284 }\r
285 \r
286 /// @brief Generate the application code.\r
287 /// @param[in] header Content to be inserted as a header.\r
288 /// @param[in] footer Content to be inserted as a footer.\r
289 /// @param[in] message_set application read from the json file.\r
290 /// @param[in] out Stream to write on.\r
291 void generate(const std::string& header, const std::string& footer, const openxc::message_set& message_set, std::ostream& out)\r
292 {\r
293         // Derive CAPI name from message set name\r
294         // (the name is lowercased and spaces are converted to dashes ('-')\r
295         std::string capi_name(message_set.name());\r
296         std::transform(capi_name.begin(), capi_name.end(), capi_name.begin(),\r
297                        [](unsigned char c){ return (c == ' ' ? '-' : std::tolower(c)); });\r
298 \r
299         out << "#include <binding/application.hpp>\n"\r
300                 << "#include <can/can-decoder.hpp>\n"\r
301                 << "#include <can/can-encoder.hpp>\n\n";\r
302 \r
303         if (header.size()) out << header << "\n";\r
304 \r
305         out     << "extern \"C\" {\n"\r
306                 << "CTLP_CAPI_REGISTER(\"" << capi_name << "\");\n"\r
307                 << "\n"\r
308                 << gen(message_set, "\t\t")\r
309                 << "\n"\r
310                 << "CTLP_ONLOAD(plugin, handle) {\n"\r
311                 << "\tafb_api_t api = (afb_api_t) plugin->api;\n"\r
312                 << "\tCtlConfigT* CtlConfig = (CtlConfigT*) afb_api_get_userdata(api);\n"\r
313                 << "\tapplication_t* app = (application_t*) getExternalData(CtlConfig);\n"\r
314                 << "\n"\r
315                 << "\treturn app->add_message_set(cms);\n"\r
316                 << "}\n\n\n}\n";\r
317 \r
318         out << decoder_t::apply_patch();\r
319 \r
320         out     << footer << std::endl;\r
321 }\r
322 \r
323 /// @brief Read whole file content to a string.\r
324 /// @param[in] file Path to the file.\r
325 /// @return A std::string which contains the file content. If @c file is an empty string, the return value is also empty.\r
326 /// @exception std::runtime_error Throw this exception if the specified file is not found or cannot be opened.\r
327 std::string read_file(const std::string& file)\r
328 {\r
329         if(file.size() == 0) return std::string();\r
330 \r
331         std::string content;\r
332         std::ifstream stream(file);\r
333         if (stream)\r
334         {\r
335                 stream.seekg(0, std::ios::end);\r
336                 content.reserve(stream.tellg());\r
337                 stream.seekg(0, std::ios::beg);\r
338                 content.assign((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());\r
339                 return content;\r
340         }\r
341         std::stringstream ss;\r
342         ss << "The specified file (" << file << ") is not found!";\r
343         throw std::runtime_error(ss.str());\r
344 }\r
345 \r
346 /// @brief Read whole file content as a json document.\r
347 /// @param[in] file Path to the file.\r
348 /// @return A @c nlohmann::json object.\r
349 /// @exception std::runtime_error Throw this exception if the specified file is not found or cannot be opened.\r
350 nlohmann::json read_json(const std::string& file)\r
351 {\r
352         std::ifstream stream(file);\r
353         if (stream)\r
354         {\r
355                 nlohmann::json result;\r
356                 stream >> result;\r
357                 return result;\r
358         }\r
359         std::stringstream ss;\r
360         ss << "The specified file (" << file << ") is not found!";\r
361         throw std::runtime_error(ss.str());\r
362 }\r
363 \r
364 // function that show the help information\r
365 void showhelpinfo(char *s)\r
366 {\r
367 std::cout<<"Usage:   "<<s<<" <-m inpout.json> [-o application-generated.cpp]"<< std::endl;\r
368 std::cout<<"option:  "<<"-m  input.json : JSON file describing CAN messages and signals"<< std::endl;\r
369 std::cout<<"         "<<"-h header.cpp : header source file insert at the beginning of generated file"<< std::endl;\r
370 std::cout<<"         "<<"-f footer.cpp : footer source file append to generated file."<< std::endl;\r
371 std::cout<<"         "<<"-o application-generated.cpp : output source file. Name has to be application-generated.cpp"<< std::endl;\r
372 }\r
373 \r
374 /// @brief Entry point.\r
375 /// @param[in] argc Argument's count.\r
376 /// @param[in] argv Argument's array.\r
377 /// @return Exit code, zero if success.\r
378 int main(int argc, char *argv[])\r
379 {\r
380 \r
381         decoder_t::init_decoder();\r
382         try\r
383         {\r
384                 std::string appName = argv[0];\r
385                 std::string message_set_file;\r
386                 std::string output_file;\r
387                 std::string header_file;\r
388                 std::string footer_file;\r
389 \r
390                 char tmp;\r
391                 /*if the program is ran witout options ,it will show the usgage and exit*/\r
392                 if(argc == 1)\r
393                 {\r
394                         showhelpinfo(argv[0]);\r
395                         exit(1);\r
396                 }\r
397                 /*use function getopt to get the arguments with option."hu:p:s:v" indicate\r
398                 that option h,v are the options without arguments while u,p,s are the\r
399                 options with arguments*/\r
400                 while((tmp=(char)getopt(argc,argv,"m:h:f:o:"))!=-1)\r
401                 {\r
402                         switch(tmp)\r
403                         {\r
404                         case 'h':\r
405                                 header_file = optarg;\r
406                                 break;\r
407                         case 'f':\r
408                                 footer_file = optarg;\r
409                                 break;\r
410                         case 'm':\r
411                                 message_set_file = optarg;\r
412                                 break;\r
413                         case 'o':\r
414                                 output_file = optarg;\r
415                                 break;\r
416                         default:\r
417                                 showhelpinfo(argv[0]);\r
418                         break;\r
419                         }\r
420                 }\r
421 \r
422                 std::stringstream header;\r
423                 header << read_file(header_file);\r
424 \r
425                 std::string footer = read_file(footer_file);\r
426                 openxc::message_set message_set;\r
427                 message_set.from_json(read_json(message_set_file));\r
428 \r
429                 std::string message_set_path = dirname(strdup(message_set_file.c_str()));\r
430                 for(const auto& s : message_set.extra_sources())\r
431                 {\r
432                         std::string extra_source = s;\r
433                         extra_source = message_set_path + "/" + extra_source;\r
434                         header << "\n// >>>>> " << s << " >>>>>\n" << read_file(extra_source) << "\n// <<<<< " << s << " <<<<<\n";\r
435                 }\r
436 \r
437                 std::ofstream out;\r
438                 if (output_file.size())\r
439                 {\r
440                         out.open(output_file);\r
441                         if(!out)\r
442                         {\r
443                                 std::stringstream ss;\r
444                                 ss << "Can't open the ouput file (" << output_file << ") for writing!";\r
445                                 throw std::runtime_error(ss.str());\r
446                         }\r
447                 }\r
448                 VERSION_FILE = message_set.version();\r
449                 generate(header.str(), footer, message_set, output_file.size() ? out : std::cout);\r
450         }\r
451         catch (std::exception& e)\r
452         {\r
453                 std::cerr << "ERROR: Unhandled exception - " << e.what() << std::endl;\r
454                 return EXIT_UNKNOWN_ERROR;\r
455         }\r
456         return EXIT_SUCCESS;\r
457 }\r