mavgen_objc.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. #!/usr/bin/env python
  2. '''
  3. parse a MAVLink protocol XML file and generate an Objective-C implementation
  4. Copyright John Boiles 2013
  5. Released under GNU GPL version 3 or later
  6. '''
  7. from __future__ import print_function
  8. import os
  9. from . import mavparse, mavtemplate
  10. t = mavtemplate.MAVTemplate()
  11. def generate_mavlink(directory, xml):
  12. '''generate MVMavlink header and implementation'''
  13. f = open(os.path.join(directory, "MVMavlink.h"), mode='w')
  14. t.write(f,'''
  15. //
  16. // MVMavlink.h
  17. // MAVLink communications protocol built from ${basename}.xml
  18. //
  19. // Created on ${parse_time} by mavgen_objc.py
  20. // http://qgroundcontrol.org/mavlink
  21. //
  22. #import "MVMessage.h"
  23. ${{message_definition_files:#import "MV${name_camel_case}Messages.h"
  24. }}
  25. @class MVMavlink;
  26. @protocol MVMessage;
  27. @protocol MVMavlinkDelegate <NSObject>
  28. /*!
  29. Method called on the delegate when a full message has been received. Note that this may be called multiple times when parseData: is called, if the data passed to parseData: contains multiple messages.
  30. @param mavlink The MVMavlink object calling this method
  31. @param message The id<MVMessage> class containing the parsed message
  32. */
  33. - (void)mavlink:(MVMavlink *)mavlink didGetMessage:(id<MVMessage>)message;
  34. /*!
  35. Method called on the delegate when data should be sent.
  36. @param mavlink The MVMavlink object calling this method
  37. @param data NSData object containing the bytes to be sent
  38. */
  39. - (BOOL)mavlink:(MVMavlink *)mavlink shouldWriteData:(NSData *)data;
  40. @end
  41. /*!
  42. Class for parsing and sending instances of id<MVMessage>
  43. @discussion MVMavlink receives a stream of bytes via the parseData: method and calls the delegate method mavlink:didGetMessage: each time a message is fully parsed. Users of MVMavlink can call parseData: anytime they get new data, even if that data does not contain a complete message.
  44. */
  45. @interface MVMavlink : NSObject
  46. @property (weak, nonatomic) id<MVMavlinkDelegate> delegate;
  47. /*!
  48. Parse byte data received from a MAVLink byte stream.
  49. @param data NSData containing the received bytes
  50. */
  51. - (void)parseData:(NSData *)data;
  52. /*!
  53. Compile MVMessage object into a bytes and pass to the delegate for sending.
  54. @param message Object conforming to the MVMessage protocol that represents the data to be sent
  55. @return YES if message sending was successful
  56. */
  57. - (BOOL)sendMessage:(id<MVMessage>)message;
  58. @end
  59. ''', xml)
  60. f.close()
  61. f = open(os.path.join(directory, "MVMavlink.m"), mode='w')
  62. t.write(f,'''
  63. //
  64. // MVMavlink.m
  65. // MAVLink communications protocol built from ${basename}.xml
  66. //
  67. // Created by mavgen_objc.py
  68. // http://qgroundcontrol.org/mavlink
  69. //
  70. #import "MVMavlink.h"
  71. @implementation MVMavlink
  72. - (void)parseData:(NSData *)data {
  73. mavlink_message_t msg;
  74. mavlink_status_t status;
  75. char *bytes = (char *)[data bytes];
  76. for (NSInteger i = 0; i < [data length]; ++i) {
  77. if (mavlink_parse_char(MAVLINK_COMM_0, bytes[i], &msg, &status)) {
  78. // Packet received
  79. id<MVMessage> message = [MVMessage messageWithCMessage:msg];
  80. [_delegate mavlink:self didGetMessage:message];
  81. }
  82. }
  83. }
  84. - (BOOL)sendMessage:(id<MVMessage>)message {
  85. return [_delegate mavlink:self shouldWriteData:[message data]];
  86. }
  87. @end
  88. ''', xml)
  89. f.close()
  90. def generate_base_message(directory, xml):
  91. '''Generate base MVMessage header and implementation'''
  92. f = open(os.path.join(directory, 'MVMessage.h'), mode='w')
  93. t.write(f, '''
  94. //
  95. // MVMessage.h
  96. // MAVLink communications protocol built from ${basename}.xml
  97. //
  98. // Created by mavgen_objc.py
  99. // http://qgroundcontrol.org/mavlink
  100. //
  101. #import "mavlink.h"
  102. @protocol MVMessage <NSObject>
  103. - (id)initWithCMessage:(mavlink_message_t)message;
  104. - (NSData *)data;
  105. @property (readonly, nonatomic) mavlink_message_t message;
  106. @end
  107. @interface MVMessage : NSObject <MVMessage> {
  108. mavlink_message_t _message;
  109. }
  110. /*!
  111. Create an MVMessage subclass from a mavlink_message_t.
  112. @param message Struct containing the details of the message
  113. @result MVMessage or subclass representing the message
  114. */
  115. + (id<MVMessage>)messageWithCMessage:(mavlink_message_t)message;
  116. //! System ID of the sender of the message.
  117. - (uint8_t)systemId;
  118. //! Component ID of the sender of the message.
  119. - (uint8_t)componentId;
  120. //! Message ID of this message.
  121. - (uint8_t)messageId;
  122. @end
  123. ''', xml)
  124. f.close()
  125. f = open(os.path.join(directory, 'MVMessage.m'), mode='w')
  126. t.write(f, '''
  127. //
  128. // MVMessage.m
  129. // MAVLink communications protocol built from ${basename}.xml
  130. //
  131. // Created by mavgen_objc.py
  132. // http://qgroundcontrol.org/mavlink
  133. //
  134. #import "MVMessage.h"
  135. ${{message_definition_files:#import "MV${name_camel_case}Messages.h"
  136. }}
  137. @implementation MVMessage
  138. @synthesize message=_message;
  139. + (id)messageWithCMessage:(mavlink_message_t)message {
  140. static NSDictionary *messageIdToClass = nil;
  141. if (!messageIdToClass) {
  142. messageIdToClass = @{
  143. ${{message: @${id} : [MVMessage${name_camel_case} class],
  144. }}
  145. };
  146. }
  147. Class messageClass = messageIdToClass[@(message.msgid)];
  148. // Store unknown messages to MVMessage
  149. if (!messageClass) {
  150. messageClass = [MVMessage class];
  151. }
  152. return [[messageClass alloc] initWithCMessage:message];
  153. }
  154. - (id)initWithCMessage:(mavlink_message_t)message {
  155. if ((self = [super init])) {
  156. self->_message = message;
  157. }
  158. return self;
  159. }
  160. - (NSData *)data {
  161. uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
  162. NSInteger length = mavlink_msg_to_send_buffer(buffer, &self->_message);
  163. return [NSData dataWithBytes:buffer length:length];
  164. }
  165. - (uint8_t)systemId {
  166. return self->_message.sysid;
  167. }
  168. - (uint8_t)componentId {
  169. return self->_message.compid;
  170. }
  171. - (uint8_t)messageId {
  172. return self->_message.msgid;
  173. }
  174. - (NSString *)description {
  175. return [NSString stringWithFormat:@"%@, systemId=%d, componentId=%d", [self class], self.systemId, self.componentId];
  176. }
  177. @end
  178. ''', xml)
  179. f.close()
  180. def generate_message_definitions_h(directory, xml):
  181. '''generate headerfile containing includes for all messages'''
  182. f = open(os.path.join(directory, "MV" + camel_case_from_underscores(xml.basename) + "Messages.h"), mode='w')
  183. t.write(f, '''
  184. //
  185. // MV${basename_camel_case}Messages.h
  186. // MAVLink communications protocol built from ${basename}.xml
  187. //
  188. // Created by mavgen_objc.py
  189. // http://qgroundcontrol.org/mavlink
  190. //
  191. ${{message:#import "MVMessage${name_camel_case}.h"
  192. }}
  193. ''', xml)
  194. f.close()
  195. def generate_message(directory, m):
  196. '''generate per-message header and implementation file'''
  197. f = open(os.path.join(directory, 'MVMessage%s.h' % m.name_camel_case), mode='w')
  198. t.write(f, '''
  199. //
  200. // MVMessage${name_camel_case}.h
  201. // MAVLink communications protocol built from ${basename}.xml
  202. //
  203. // Created by mavgen_objc.py
  204. // http://qgroundcontrol.org/mavlink
  205. //
  206. #import "MVMessage.h"
  207. /*!
  208. Class that represents a ${name} Mavlink message.
  209. @discussion ${description}
  210. */
  211. @interface MVMessage${name_camel_case} : MVMessage
  212. - (id)initWithSystemId:(uint8_t)systemId componentId:(uint8_t)componentId${{arg_fields: ${name_lower_camel_case}:(${arg_type}${array_prefix})${name_lower_camel_case}}};
  213. ${{fields://! ${description}
  214. - (${return_type})${name_lower_camel_case}${get_arg_objc};
  215. }}
  216. @end
  217. ''', m)
  218. f.close()
  219. f = open(os.path.join(directory, 'MVMessage%s.m' % m.name_camel_case), mode='w')
  220. t.write(f, '''
  221. //
  222. // MVMessage${name_camel_case}.m
  223. // MAVLink communications protocol built from ${basename}.xml
  224. //
  225. // Created by mavgen_objc.py
  226. // http://qgroundcontrol.org/mavlink
  227. //
  228. #import "MVMessage${name_camel_case}.h"
  229. @implementation MVMessage${name_camel_case}
  230. - (id)initWithSystemId:(uint8_t)systemId componentId:(uint8_t)componentId${{arg_fields: ${name_lower_camel_case}:(${arg_type}${array_prefix})${name_lower_camel_case}}} {
  231. if ((self = [super init])) {
  232. mavlink_msg_${name_lower}_pack(systemId, componentId, &(self->_message)${{arg_fields:, ${name_lower_camel_case}}});
  233. }
  234. return self;
  235. }
  236. ${{fields:- (${return_type})${name_lower_camel_case}${get_arg_objc} {
  237. ${return_method_implementation}
  238. }
  239. }}
  240. - (NSString *)description {
  241. return [NSString stringWithFormat:@"%@${{fields:, ${name_lower_camel_case}=${print_format}}}", [super description]${{fields:, ${get_message}}}];
  242. }
  243. @end
  244. ''', m)
  245. f.close()
  246. def camel_case_from_underscores(string):
  247. """generate a CamelCase string from an underscore_string."""
  248. components = string.split('_')
  249. string = ''
  250. for component in components:
  251. string += component[0].upper() + component[1:]
  252. return string
  253. def lower_camel_case_from_underscores(string):
  254. """generate a lower-cased camelCase string from an underscore_string.
  255. For example: my_variable_name -> myVariableName"""
  256. components = string.split('_')
  257. string = components[0]
  258. for component in components[1:]:
  259. string += component[0].upper() + component[1:]
  260. return string
  261. def generate_shared(basename, xml_list):
  262. # Create a dictionary to hold all the values we want to use in the templates
  263. template_dict = {}
  264. template_dict['parse_time'] = xml_list[0].parse_time
  265. template_dict['message'] = []
  266. template_dict['message_definition_files'] = []
  267. print("Generating Objective-C implementation in directory %s" % basename)
  268. mavparse.mkdir_p(basename)
  269. for xml in xml_list:
  270. template_dict['message'].extend(xml.message)
  271. basename_camel_case = camel_case_from_underscores(xml.basename)
  272. template_dict['message_definition_files'].append({'name_camel_case': basename_camel_case})
  273. if not template_dict.get('basename', None):
  274. template_dict['basename'] = xml.basename
  275. else:
  276. template_dict['basename'] = template_dict['basename'] + ', ' + xml.basename
  277. # Sort messages by ID
  278. template_dict['message'] = sorted(template_dict['message'], key = lambda message : message.id)
  279. # Add name_camel_case to each message object
  280. for message in template_dict['message']:
  281. message.name_camel_case = camel_case_from_underscores(message.name_lower)
  282. generate_mavlink(basename, template_dict)
  283. generate_base_message(basename, template_dict)
  284. def generate_message_definitions(basename, xml):
  285. '''generate files for one XML file'''
  286. directory = os.path.join(basename, xml.basename)
  287. print("Generating Objective-C implementation in directory %s" % directory)
  288. mavparse.mkdir_p(directory)
  289. xml.basename_camel_case = camel_case_from_underscores(xml.basename)
  290. # Add some extra field attributes for convenience
  291. for m in xml.message:
  292. m.basename = xml.basename
  293. m.parse_time = xml.parse_time
  294. m.name_camel_case = camel_case_from_underscores(m.name_lower)
  295. for f in m.fields:
  296. f.name_lower_camel_case = lower_camel_case_from_underscores(f.name);
  297. f.get_message = "[self %s]" % f.name_lower_camel_case
  298. f.return_method_implementation = ''
  299. f.array_prefix = ''
  300. f.array_return_arg = ''
  301. f.get_arg = ''
  302. f.get_arg_objc = ''
  303. if f.enum:
  304. f.return_type = f.enum
  305. f.arg_type = f.enum
  306. else:
  307. f.return_type = f.type
  308. f.arg_type = f.type
  309. if f.print_format is None:
  310. if f.array_length != 0:
  311. f.print_format = "%@"
  312. elif f.type.startswith('uint64_t'):
  313. f.print_format = "%lld"
  314. elif f.type.startswith('uint') or f.type.startswith('int'):
  315. f.print_format = "%d"
  316. elif f.type.startswith('float'):
  317. f.print_format = "%f"
  318. elif f.type.startswith('char'):
  319. f.print_format = "%c"
  320. else:
  321. print("print_format unsupported for type %s" % f.type)
  322. if f.array_length != 0:
  323. f.get_message = '@"[array of %s[%d]]"' % (f.type, f.array_length)
  324. f.array_prefix = ' *'
  325. f.array_return_arg = '%s, %u, ' % (f.name, f.array_length)
  326. f.return_type = 'uint16_t'
  327. f.get_arg = ', %s' % (f.name)
  328. f.get_arg_objc = ':(%s *)%s' % (f.type, f.name)
  329. if f.type == 'char':
  330. # Special handling for strings (assumes all char arrays are strings)
  331. f.return_type = 'NSString *'
  332. f.get_arg_objc = ''
  333. f.get_message = "[self %s]" % f.name_lower_camel_case
  334. f.return_method_implementation = \
  335. """char string[%(array_length)d];
  336. mavlink_msg_%(message_name_lower)s_get_%(name)s(&(self->_message), (char *)&string);
  337. return [[NSString alloc] initWithBytes:string length:%(array_length)d encoding:NSASCIIStringEncoding];""" % {'array_length': f.array_length, 'message_name_lower': m.name_lower, 'name': f.name}
  338. if not f.return_method_implementation:
  339. f.return_method_implementation = \
  340. """return mavlink_msg_%(message_name_lower)s_get_%(name)s(&(self->_message)%(get_arg)s);""" % {'message_name_lower': m.name_lower, 'name': f.name, 'get_arg': f.get_arg}
  341. for m in xml.message:
  342. m.arg_fields = []
  343. for f in m.fields:
  344. if not f.omit_arg:
  345. m.arg_fields.append(f)
  346. generate_message_definitions_h(directory, xml)
  347. for m in xml.message:
  348. generate_message(directory, m)
  349. def generate(basename, xml_list):
  350. '''generate complete MAVLink Objective-C implemenation'''
  351. generate_shared(basename, xml_list)
  352. for xml in xml_list:
  353. generate_message_definitions(basename, xml)