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