00001
00002
00003 import datetime
00004 from copy import deepcopy
00005 import logging
00006 import xml.etree.ElementTree as et
00007 import os
00008 import pystache
00009 import re
00010 import string
00011 import sys
00012 import subprocess
00013 import urllib2
00014
00015
00016 LIBARCOMMANDS_GIT_OWNER = "Parrot-Developers"
00017 LIBARCOMMANDS_GIT_HASH = "ab28dab91845cd36c4d7002b55f70805deaff3c8"
00018
00019
00020 ROS_TYPE_MAP = {
00021 "bool": "bool",
00022 "u8": "uint8",
00023 "i8": "int8",
00024 "u16": "uint16",
00025 "i16": "int16",
00026 "u32": "uint32",
00027 "i32": "int32",
00028 "u64": "uint64",
00029 "i64": "int64",
00030 "float": "float32",
00031 "double": "float64",
00032 "string": "string",
00033 "enum": "enum"
00034 }
00035
00036
00037 BEBOP_TYPE_MAP = {
00038 "bool": "U8",
00039 "u8": "U8",
00040 "i8": "I8",
00041 "u16": "U16",
00042 "i16": "I16",
00043 "u32": "U32",
00044 "i32": "I32",
00045 "u64": "U64",
00046 "i64": "I64",
00047 "float": "Float",
00048 "double": "Double",
00049 "string": "String",
00050 "enum": "I32"
00051 }
00052
00053
00054 DYN_TYPE_MAP = {
00055 "bool": "bool_t",
00056 "u8": "int_t",
00057 "i8": "int_t",
00058 "u16": "int_t",
00059 "i16": "int_t",
00060 "u32": "int_t",
00061 "i32": "int_t",
00062 "u64": "int_t",
00063 "i64": "int_t",
00064 "float": "double_t",
00065 "double": "double_t",
00066 "string": "str_t",
00067 "enum": "enum"
00068 }
00069
00070 C_TYPE_MAP = {
00071 "bool": "bool",
00072 "u8": "int32_t",
00073 "i8": "int32_t",
00074 "u16": "int32_t",
00075 "i16": "int32_t",
00076 "u32": "int32_t",
00077 "i32": "int32_t",
00078 "u64": "int32_t",
00079 "i64": "int32_t",
00080 "float": "double",
00081 "double": "double",
00082 "string": "std::string",
00083 "enum": "int32_t"
00084 }
00085
00086 blacklist_settings_keys = set(["wifiSecurity"])
00087
00088 min_max_regex = re.compile('\[([0-9\.\-]+)\:([0-9\.\-]+)\]')
00089 rend = pystache.Renderer()
00090
00091 def get_xml_url(filename):
00092 return rend.render_path("templates/url.mustache",
00093 {"repo_owner": LIBARCOMMANDS_GIT_OWNER, "hash": LIBARCOMMANDS_GIT_HASH, "filename": filename})
00094
00095 def load_from_url(url):
00096 f = urllib2.urlopen(url)
00097 data = f.read()
00098 f.close()
00099 return data
00100
00101 def is_state_tag(name):
00102 return (not name.find("State") == -1) and (name.find("Settings") == -1)
00103
00104 def is_settings_tag(name):
00105 return (not name.find("Settings") == -1) and (name.find("State") == -1)
00106
00107 def strip_text(text):
00108 return re.sub("\s\s+", " ", text.strip().replace('\n', '').replace('\r', '')).replace('"', '').replace("'", "")
00109
00110 def cap_word(text):
00111 return text.lower().title()
00112
00113 def guess_min_max(arg_comment):
00114 m = min_max_regex.search(arg_comment)
00115 if m:
00116 logging.info(" ... [min:max]")
00117 return [float(m.group(1)), float(m.group(2))]
00118 elif (arg_comment.lower().find("m/s2") != -1):
00119 logging.info(" ... acc (m/s2)")
00120 return [0.0, 5.0]
00121 elif (arg_comment.lower().find("m/s") != -1):
00122 logging.info(" ... speed (m/s)")
00123 return [0.0, 10.0]
00124 elif (arg_comment.lower().find("in meters") != -1) or (arg_comment.lower().find("in m") != -1):
00125 logging.info(" ... meters")
00126 return [0, 160]
00127 elif (arg_comment.lower().find("in degree/s") != -1):
00128 logging.info(" ... rotations speed degrees/s")
00129 return [0, 900.0]
00130 elif (arg_comment.lower().find("in degree") != -1):
00131 logging.info(" ... degrees")
00132 return [-180.0, 180.0]
00133 elif (arg_comment.lower().find("1") != -1) and (arg_comment.lower().find("0") != -1):
00134 logging.info(" ... bool")
00135 return [0, 1]
00136 elif (arg_comment.lower().find("latitude") != -1):
00137 logging.info(" ... latitude")
00138 return [-90.0, 90.0]
00139 elif (arg_comment.lower().find("longitude") != -1):
00140 logging.info(" ... longitude")
00141 return [-180.0, 180.0]
00142 elif (arg_comment.lower().find("[rad/s]") != -1):
00143 logging.info(" ... angular speed (rad/s)")
00144 return [0.0, 5.0]
00145 elif (arg_comment.lower().find("channel") != -1):
00146 logging.info(" ... unknown int")
00147 return [0, 50]
00148 elif (arg_comment.lower().find("second") != -1):
00149 logging.info(" ... time (s)")
00150 return [0, 120]
00151
00152 return []
00153
00154 def today():
00155 return datetime.datetime.now().strftime("%Y-%m-%d")
00156
00157 def generate_states(xml_filename):
00158 xml_url = get_xml_url(xml_filename)
00159 project = xml_filename.split(".")[0]
00160
00161 logging.info("XML Filename: %s" % (xml_filename, ))
00162 logging.info("Fetching source XML file for project %s " % (project, ))
00163 logging.info("URL: %s" % (xml_url, ))
00164 xml = load_from_url(xml_url)
00165 xml_root = et.fromstring(xml)
00166
00167
00168 logging.info("Iterating all State <class> tags ...")
00169
00170 generator = os.path.basename(__file__)
00171 generator_git_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).strip()
00172
00173 d_cpp = dict({
00174 "url": xml_url,
00175 "project": project,
00176 "date": today(),
00177 "generator": generator,
00178 "generator_git_hash": generator_git_hash,
00179 "queue_size": 10,
00180 "frame_id": "base_link",
00181 "cpp_class": list()
00182 })
00183 d_msg = dict()
00184
00185 for cl in xml_root.iter("class"):
00186 if not is_state_tag(cl.attrib["name"]):
00187 continue
00188
00189
00190
00191 for cmd in cl.iter("cmd"):
00192
00193 msg_name = cap_word(project) + cl.attrib["name"] + cmd.attrib["name"]
00194
00195 comment_el = cmd.find("comment")
00196 msg_file_comment = ""
00197 if not comment_el is None:
00198 msg_file_comment = comment_el.attrib["desc"]
00199
00200 d = dict({
00201 "url": xml_url,
00202 "msg_filename": msg_name,
00203 "date": today(),
00204 "generator": generator,
00205 "generator_git_hash": generator_git_hash,
00206 "msg_file_comment": strip_text(msg_file_comment),
00207 "msg_field": list()
00208 })
00209
00210
00211 cpp_class_dict_key = rend.render_path("templates/dictionary_key.mustache",
00212 {"project": project.upper(), "class": cl.attrib["name"].upper(), "cmd": cmd.attrib["name"].upper()})
00213
00214 cpp_class_name = msg_name
00215 cpp_class_instance_name = project.lower() + "_" + cl.attrib["name"].lower() + "_" + cmd.attrib["name"].lower() + "_ptr";
00216 cpp_class_param_name = "states/enable_" + cl.attrib["name"].lower() + "_" + cmd.attrib["name"].lower()
00217 topic_name = "states/" + project + "/" + cl.attrib["name"] + "/" + cmd.attrib["name"]
00218
00219 arg_list = []
00220 for arg in cmd.iter("arg"):
00221
00222 f_name = arg.attrib["name"]
00223 f_type = ROS_TYPE_MAP[arg.attrib.get("type", "bool")]
00224 f_comment = strip_text(arg.text)
00225 f_enum_list = list()
00226 if (f_type == "enum"):
00227 f_type = "uint8"
00228 counter = 0
00229 for enum in arg.iter("enum"):
00230 f_enum_list.append({
00231 "constant_name": f_name + "_" + enum.attrib["name"],
00232 "constant_value": counter,
00233 "constant_comment": strip_text(enum.text)
00234 })
00235 counter += 1
00236
00237 d["msg_field"].append({
00238 "msg_field_type": f_type,
00239 "msg_field_name": f_name,
00240 "msg_field_comment": f_comment,
00241 "msg_field_enum": deepcopy(f_enum_list)
00242 })
00243
00244
00245 arg_list.append({
00246 "cpp_class_arg_key": cpp_class_dict_key + "_" + arg.attrib["name"].upper(),
00247 "cpp_class_arg_name": f_name,
00248 "cpp_class_arg_sdk_type": BEBOP_TYPE_MAP[arg.attrib.get("type", "bool")]
00249 })
00250
00251 d_msg[msg_name] = deepcopy(d)
00252
00253
00254 d_cpp["cpp_class"].append({
00255 "cpp_class_name": cpp_class_name,
00256 "cpp_class_comment": strip_text(msg_file_comment),
00257 "cpp_class_instance_name": cpp_class_instance_name,
00258 "cpp_class_param_name": cpp_class_param_name,
00259 "topic_name": topic_name,
00260 "latched": "true",
00261 "cpp_class_msg_type": msg_name,
00262 "key": cpp_class_dict_key,
00263 "cpp_class_arg": deepcopy(arg_list)
00264 })
00265
00266 logging.info("... Done iterating, writing results to file")
00267
00268 for k, d in d_msg.items():
00269 msg_filename = "%s.msg" % k
00270 logging.info("Writing %s" % (msg_filename, ))
00271 with open(msg_filename, "w") as msg_file:
00272 msg_file.write(rend.render_path("templates/msg.mustache", d))
00273
00274 header_file_name = "%s_state_callbacks.h" % (project.lower(), )
00275 logging.info("Writing %s" % (header_file_name, ))
00276 with open(header_file_name, "w") as header_file:
00277 header_file.write(rend.render_path("templates/state_callbacks.h.mustache", d_cpp))
00278
00279 include_file_name = "%s_state_callback_includes.h" % (project.lower(), )
00280 logging.info("Writing %s" % (include_file_name, ))
00281 with open(include_file_name, "w") as include_file:
00282 include_file.write(rend.render_path("templates/state_callback_includes.h.mustache", d_cpp))
00283
00284 with open("callbacks_common.h", "w") as header_file:
00285 header_file.write(rend.render_path("templates/callbacks_common.h.mustache", d_cpp))
00286
00287 rst_file_name = "%s_states_param_topic.rst" % (project.lower(), )
00288 logging.info("Writing %s" % (rst_file_name, ))
00289 with open(rst_file_name, "w") as rst_file:
00290 rst_file.write(rend.render_path("templates/states_param_topic.rst.mustache", d_cpp))
00291
00292 def generate_settings(xml_filename):
00293 xml_url = get_xml_url(xml_filename)
00294 project = xml_filename.split(".")[0]
00295
00296 logging.info("Fetching source XML file for project %s " % (project, ))
00297 logging.info("URL: %s" % (xml_url, ))
00298 xml = load_from_url(xml_url)
00299 xml_root = et.fromstring(xml)
00300
00301 generator = os.path.basename(__file__)
00302 generator_git_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).strip()
00303
00304
00305
00306 d_cfg = dict({
00307 "cfg_filename": "Bebop%s.cfg" % (project.title(), ),
00308 "url": xml_url,
00309 "project": project.title(),
00310 "date": today(),
00311 "generator": generator,
00312 "generator_git_hash": generator_git_hash,
00313 "cfg_class": list(),
00314 "cpp_class": list()
00315 })
00316
00317 for cl in xml_root.iter("class"):
00318 if not is_settings_tag(cl.attrib["name"]):
00319 continue
00320
00321
00322
00323 if not xml_root.findall(".//class[@name='%s']" % (cl.attrib["name"] + "State", )):
00324 logging.warning("No State Class for %s " % (cl.attrib["name"], ))
00325 continue
00326
00327
00328
00329 cfg_class_d = {
00330 "cfg_class_name": cl.attrib["name"].lower(),
00331 "cfg_class_comment": strip_text(cl.text),
00332 "cfg_cmd": list()
00333 }
00334 for cmd in cl.iter("cmd"):
00335
00336 if not xml_root.findall(".//cmd[@name='%s']" % (cmd.attrib["name"] + "Changed", )):
00337 logging.warning("No Changed CMD for %s " % (cmd.attrib["name"], ))
00338 continue
00339
00340
00341 if strip_text(cmd.attrib["name"]) in blacklist_settings_keys:
00342 logging.warning("Key %s is blacklisted!" % (cmd.attrib["name"], ))
00343 continue
00344
00345 comment_el = cmd.find("comment")
00346 cmd_comment = ""
00347 if not comment_el is None:
00348 cmd_comment = comment_el.attrib["desc"]
00349
00350
00351
00352 cfg_cmd_d = {
00353 "cfg_cmd_comment": strip_text(cmd_comment),
00354 "cfg_arg": list()
00355 }
00356
00357
00358
00359
00360
00361
00362 cpp_class_dict_key = rend.render_path("templates/dictionary_key.mustache",
00363 {"project": project.upper(), "class": cl.attrib["name"].upper() + "STATE", "cmd": cmd.attrib["name"].upper() + "CHANGED"} )
00364
00365 cpp_class_name = cl.attrib["name"] + cmd.attrib["name"]
00366 cpp_class_comment = strip_text(cmd_comment)
00367 cpp_class_instance_name = project.lower() + "_" + cl.attrib["name"].lower() + "_" + cmd.attrib["name"].lower() + "_ptr";
00368 cpp_class_params = list()
00369
00370 counter = 0
00371
00372 for arg in cmd.iter("arg"):
00373
00374 arg_name = cl.attrib["name"] + cmd.attrib["name"] + cap_word(arg.attrib["name"])
00375 arg_type = DYN_TYPE_MAP[arg.attrib.get("type", "bool")]
00376 arg_comment = strip_text(arg.text)
00377
00378 arg_enum_list = list()
00379 minmax_list = list()
00380 arg_default = 0
00381 arg_min = 0.0
00382 arg_max = 0.0
00383 counter = 0
00384 need_enum_cast = False
00385 if (arg_type == "enum"):
00386 need_enum_cast = True
00387 arg_type = "int_t"
00388 for enum in arg.iter("enum"):
00389 arg_enum_list.append({
00390 "constant_name": arg_name + "_" + enum.attrib["name"],
00391 "constant_value": counter,
00392 "constant_comment": strip_text(enum.text)
00393 })
00394 counter += 1
00395 elif not arg_type == "str_t":
00396
00397 logging.info("Guessing type of \"%s\"" % (arg_name))
00398 logging.info(" from: %s" % (arg_comment))
00399 minmax_list = guess_min_max(arg_comment)
00400 if (len(minmax_list) == 2):
00401 [arg_min, arg_max] = minmax_list
00402 logging.info(" min: %s max: %s" % (arg_min, arg_max))
00403 else:
00404 logging.warning(" Can not guess [min:max] values for this arg, skipping it")
00405
00406
00407
00408
00409 if arg_type == "int_t" and arg_min == 0 and arg_max == 1:
00410 arg_enum_list.append({
00411 "constant_name": arg_name + "_OFF",
00412 "constant_value": 0,
00413 "constant_comment": "Disabled"
00414 })
00415 arg_enum_list.append({
00416 "constant_name": arg_name + "_ON",
00417 "constant_value": 1,
00418 "constant_comment": "Enabled"
00419 })
00420 counter = 2
00421
00422
00423 if len(minmax_list) or need_enum_cast or arg_type == "str_t":
00424
00425 if arg_type == "str_t":
00426 arg_min = "''"
00427 arg_max = "''"
00428 arg_default = "''"
00429
00430 cfg_cmd_d["cfg_arg"].append({
00431 "cfg_arg_type": arg_type,
00432 "cfg_arg_name": arg_name,
00433 "cfg_arg_comment": arg_comment,
00434 "cfg_arg_default": arg_default,
00435 "cfg_arg_min": arg_min,
00436 "cfg_arg_max": arg_max,
00437
00438 "cfg_arg_enum": {'items' : deepcopy(arg_enum_list)} if len(arg_enum_list) else [],
00439 "enum_max": counter - 1
00440 })
00441
00442
00443 if (need_enum_cast):
00444 enum_cast = "static_cast<eARCOMMANDS_%s_%s_%s_%s>" % (project.upper(), cl.attrib["name"].upper(), cmd.attrib["name"].upper(), arg.attrib["name"].upper())
00445 else:
00446 enum_cast = ""
00447
00448 cpp_class_params.append({
00449 "cpp_class_arg_key": cpp_class_dict_key + "_" + arg.attrib["name"].upper(),
00450 "cpp_class_param_name": arg_name,
00451 "cpp_class_comment": cpp_class_comment,
00452 "cpp_class_param_enum_cast": enum_cast,
00453 "cpp_class_param_type": C_TYPE_MAP[arg.attrib.get("type", "bool")],
00454 "cpp_class_param_sdk_type": BEBOP_TYPE_MAP[arg.attrib.get("type", "bool")]
00455 })
00456
00457
00458 if len(cfg_cmd_d["cfg_arg"]):
00459 cfg_class_d["cfg_cmd"].append(deepcopy(cfg_cmd_d))
00460 d_cfg["cpp_class"].append({
00461 "cpp_class_dict_key": cpp_class_dict_key,
00462 "cpp_class_name": cpp_class_name,
00463 "cpp_class_instance_name": cpp_class_instance_name,
00464 "cpp_class_params": deepcopy(cpp_class_params)
00465 })
00466
00467 d_cfg["cfg_class"].append(deepcopy(cfg_class_d))
00468
00469
00470 logging.info("... Done iterating, writing results to file")
00471
00472
00473 cfg_file_name = d_cfg["cfg_filename"]
00474 logging.info("Writing %s" % (cfg_file_name, ))
00475 with open(cfg_file_name, "w") as cfg_file:
00476 cfg_file.write(rend.render_path("templates/cfg.mustache", d_cfg))
00477
00478 header_file_name = "%s_setting_callbacks.h" % (project.lower(), )
00479 logging.info("Writing %s" % (header_file_name, ))
00480 with open(header_file_name, "w") as header_file:
00481 header_file.write(rend.render_path("templates/setting_callbacks.h.mustache", d_cfg))
00482
00483 include_file_name = "%s_setting_callback_includes.h" % (project.lower(), )
00484 logging.info("Writing %s" % (include_file_name, ))
00485 with open(include_file_name, "w") as include_file:
00486 include_file.write(rend.render_path("templates/setting_callback_includes.h.mustache", d_cfg))
00487
00488 rst_file_name = "%s_settings_param.rst" % (project.lower(), )
00489 logging.info("Writing %s" % (rst_file_name, ))
00490 with open(rst_file_name, "w") as rst_file:
00491 rst_file.write(rend.render_path("templates/settings_param.rst.mustache", d_cfg))
00492
00493 def main():
00494
00495 logging.basicConfig(level="INFO")
00496
00497 generate_states("common.xml")
00498 generate_states("ardrone3.xml")
00499
00500 generate_settings("ardrone3.xml")
00501
00502 generator = os.path.basename(__file__)
00503 generator_git_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).strip()
00504 with open("last_build_info", "w") as last_build_file:
00505 last_build_file.write(rend.render_path(
00506 "templates/last_build_info.mustache",
00507 {
00508 "source_hash": LIBARCOMMANDS_GIT_HASH,
00509 "date": datetime.datetime.now(),
00510 "generator": generator,
00511 "generator_git_hash": generator_git_hash
00512 }))
00513
00514 if __name__ == "__main__":
00515 main()
00516