438 lines
13 KiB
Python
438 lines
13 KiB
Python
|
#!/usr/bin/env python
|
||
|
'''
|
||
|
parse a MAVLink protocol XML file and generate an Objective-C implementation
|
||
|
|
||
|
Copyright John Boiles 2013
|
||
|
Released under GNU GPL version 3 or later
|
||
|
'''
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import os
|
||
|
from . import mavparse, mavtemplate
|
||
|
|
||
|
t = mavtemplate.MAVTemplate()
|
||
|
|
||
|
def generate_mavlink(directory, xml):
|
||
|
'''generate MVMavlink header and implementation'''
|
||
|
f = open(os.path.join(directory, "MVMavlink.h"), mode='w')
|
||
|
t.write(f,'''
|
||
|
//
|
||
|
// MVMavlink.h
|
||
|
// MAVLink communications protocol built from ${basename}.xml
|
||
|
//
|
||
|
// Created on ${parse_time} by mavgen_objc.py
|
||
|
// https://mavlink.io/en/
|
||
|
//
|
||
|
|
||
|
#import "MVMessage.h"
|
||
|
${{message_definition_files:#import "MV${name_camel_case}Messages.h"
|
||
|
}}
|
||
|
|
||
|
@class MVMavlink;
|
||
|
@protocol MVMessage;
|
||
|
|
||
|
@protocol MVMavlinkDelegate <NSObject>
|
||
|
|
||
|
/*!
|
||
|
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.
|
||
|
|
||
|
@param mavlink The MVMavlink object calling this method
|
||
|
@param message The id<MVMessage> class containing the parsed message
|
||
|
*/
|
||
|
- (void)mavlink:(MVMavlink *)mavlink didGetMessage:(id<MVMessage>)message;
|
||
|
|
||
|
/*!
|
||
|
Method called on the delegate when data should be sent.
|
||
|
|
||
|
@param mavlink The MVMavlink object calling this method
|
||
|
@param data NSData object containing the bytes to be sent
|
||
|
*/
|
||
|
- (BOOL)mavlink:(MVMavlink *)mavlink shouldWriteData:(NSData *)data;
|
||
|
|
||
|
@end
|
||
|
|
||
|
/*!
|
||
|
Class for parsing and sending instances of id<MVMessage>
|
||
|
|
||
|
@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.
|
||
|
*/
|
||
|
@interface MVMavlink : NSObject
|
||
|
@property (weak, nonatomic) id<MVMavlinkDelegate> delegate;
|
||
|
|
||
|
/*!
|
||
|
Parse byte data received from a MAVLink byte stream.
|
||
|
|
||
|
@param data NSData containing the received bytes
|
||
|
*/
|
||
|
- (void)parseData:(NSData *)data;
|
||
|
|
||
|
/*!
|
||
|
Compile MVMessage object into a bytes and pass to the delegate for sending.
|
||
|
|
||
|
@param message Object conforming to the MVMessage protocol that represents the data to be sent
|
||
|
@return YES if message sending was successful
|
||
|
*/
|
||
|
- (BOOL)sendMessage:(id<MVMessage>)message;
|
||
|
|
||
|
@end
|
||
|
''', xml)
|
||
|
f.close()
|
||
|
f = open(os.path.join(directory, "MVMavlink.m"), mode='w')
|
||
|
t.write(f,'''
|
||
|
//
|
||
|
// MVMavlink.m
|
||
|
// MAVLink communications protocol built from ${basename}.xml
|
||
|
//
|
||
|
// Created by mavgen_objc.py
|
||
|
// https://mavlink.io/en/
|
||
|
//
|
||
|
|
||
|
#import "MVMavlink.h"
|
||
|
|
||
|
@implementation MVMavlink
|
||
|
|
||
|
- (void)parseData:(NSData *)data {
|
||
|
mavlink_message_t msg;
|
||
|
mavlink_status_t status;
|
||
|
char *bytes = (char *)[data bytes];
|
||
|
|
||
|
for (NSInteger i = 0; i < [data length]; ++i) {
|
||
|
if (mavlink_parse_char(MAVLINK_COMM_0, bytes[i], &msg, &status)) {
|
||
|
// Packet received
|
||
|
id<MVMessage> message = [MVMessage messageWithCMessage:msg];
|
||
|
[_delegate mavlink:self didGetMessage:message];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (BOOL)sendMessage:(id<MVMessage>)message {
|
||
|
return [_delegate mavlink:self shouldWriteData:[message data]];
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
''', xml)
|
||
|
f.close()
|
||
|
|
||
|
def generate_base_message(directory, xml):
|
||
|
'''Generate base MVMessage header and implementation'''
|
||
|
f = open(os.path.join(directory, 'MVMessage.h'), mode='w')
|
||
|
t.write(f, '''
|
||
|
//
|
||
|
// MVMessage.h
|
||
|
// MAVLink communications protocol built from ${basename}.xml
|
||
|
//
|
||
|
// Created by mavgen_objc.py
|
||
|
// https://mavlink.io/en/
|
||
|
//
|
||
|
|
||
|
#import "mavlink.h"
|
||
|
|
||
|
@protocol MVMessage <NSObject>
|
||
|
- (id)initWithCMessage:(mavlink_message_t)message;
|
||
|
- (NSData *)data;
|
||
|
@property (readonly, nonatomic) mavlink_message_t message;
|
||
|
@end
|
||
|
|
||
|
@interface MVMessage : NSObject <MVMessage> {
|
||
|
mavlink_message_t _message;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
Create an MVMessage subclass from a mavlink_message_t.
|
||
|
|
||
|
@param message Struct containing the details of the message
|
||
|
@result MVMessage or subclass representing the message
|
||
|
*/
|
||
|
+ (id<MVMessage>)messageWithCMessage:(mavlink_message_t)message;
|
||
|
|
||
|
//! System ID of the sender of the message.
|
||
|
- (uint8_t)systemId;
|
||
|
|
||
|
//! Component ID of the sender of the message.
|
||
|
- (uint8_t)componentId;
|
||
|
|
||
|
//! Message ID of this message.
|
||
|
- (uint8_t)messageId;
|
||
|
|
||
|
@end
|
||
|
''', xml)
|
||
|
f.close()
|
||
|
f = open(os.path.join(directory, 'MVMessage.m'), mode='w')
|
||
|
t.write(f, '''
|
||
|
//
|
||
|
// MVMessage.m
|
||
|
// MAVLink communications protocol built from ${basename}.xml
|
||
|
//
|
||
|
// Created by mavgen_objc.py
|
||
|
// https://mavlink.io/en/
|
||
|
//
|
||
|
|
||
|
#import "MVMessage.h"
|
||
|
${{message_definition_files:#import "MV${name_camel_case}Messages.h"
|
||
|
}}
|
||
|
|
||
|
@implementation MVMessage
|
||
|
|
||
|
@synthesize message=_message;
|
||
|
|
||
|
+ (id)messageWithCMessage:(mavlink_message_t)message {
|
||
|
static NSDictionary *messageIdToClass = nil;
|
||
|
if (!messageIdToClass) {
|
||
|
messageIdToClass = @{
|
||
|
${{message: @${id} : [MVMessage${name_camel_case} class],
|
||
|
}}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Class messageClass = messageIdToClass[@(message.msgid)];
|
||
|
// Store unknown messages to MVMessage
|
||
|
if (!messageClass) {
|
||
|
messageClass = [MVMessage class];
|
||
|
}
|
||
|
|
||
|
return [[messageClass alloc] initWithCMessage:message];
|
||
|
}
|
||
|
|
||
|
- (id)initWithCMessage:(mavlink_message_t)message {
|
||
|
if ((self = [super init])) {
|
||
|
self->_message = message;
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
- (NSData *)data {
|
||
|
uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
|
||
|
|
||
|
NSInteger length = mavlink_msg_to_send_buffer(buffer, &self->_message);
|
||
|
|
||
|
return [NSData dataWithBytes:buffer length:length];
|
||
|
}
|
||
|
|
||
|
- (uint8_t)systemId {
|
||
|
return self->_message.sysid;
|
||
|
}
|
||
|
|
||
|
- (uint8_t)componentId {
|
||
|
return self->_message.compid;
|
||
|
}
|
||
|
|
||
|
- (uint8_t)messageId {
|
||
|
return self->_message.msgid;
|
||
|
}
|
||
|
|
||
|
- (NSString *)description {
|
||
|
return [NSString stringWithFormat:@"%@, systemId=%d, componentId=%d", [self class], self.systemId, self.componentId];
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
''', xml)
|
||
|
f.close()
|
||
|
|
||
|
def generate_message_definitions_h(directory, xml):
|
||
|
'''generate headerfile containing includes for all messages'''
|
||
|
f = open(os.path.join(directory, "MV" + camel_case_from_underscores(xml.basename) + "Messages.h"), mode='w')
|
||
|
t.write(f, '''
|
||
|
//
|
||
|
// MV${basename_camel_case}Messages.h
|
||
|
// MAVLink communications protocol built from ${basename}.xml
|
||
|
//
|
||
|
// Created by mavgen_objc.py
|
||
|
// https://mavlink.io/en/
|
||
|
//
|
||
|
|
||
|
${{message:#import "MVMessage${name_camel_case}.h"
|
||
|
}}
|
||
|
''', xml)
|
||
|
f.close()
|
||
|
|
||
|
def generate_message(directory, m):
|
||
|
'''generate per-message header and implementation file'''
|
||
|
f = open(os.path.join(directory, 'MVMessage%s.h' % m.name_camel_case), mode='w')
|
||
|
t.write(f, '''
|
||
|
//
|
||
|
// MVMessage${name_camel_case}.h
|
||
|
// MAVLink communications protocol built from ${basename}.xml
|
||
|
//
|
||
|
// Created by mavgen_objc.py
|
||
|
// https://mavlink.io/en/
|
||
|
//
|
||
|
|
||
|
#import "MVMessage.h"
|
||
|
|
||
|
/*!
|
||
|
Class that represents a ${name} Mavlink message.
|
||
|
|
||
|
@discussion ${description}
|
||
|
*/
|
||
|
@interface MVMessage${name_camel_case} : MVMessage
|
||
|
|
||
|
- (id)initWithSystemId:(uint8_t)systemId componentId:(uint8_t)componentId${{arg_fields: ${name_lower_camel_case}:(${arg_type}${array_prefix})${name_lower_camel_case}}};
|
||
|
|
||
|
${{fields://! ${description}
|
||
|
- (${return_type})${name_lower_camel_case}${get_arg_objc};
|
||
|
|
||
|
}}
|
||
|
@end
|
||
|
''', m)
|
||
|
f.close()
|
||
|
f = open(os.path.join(directory, 'MVMessage%s.m' % m.name_camel_case), mode='w')
|
||
|
t.write(f, '''
|
||
|
//
|
||
|
// MVMessage${name_camel_case}.m
|
||
|
// MAVLink communications protocol built from ${basename}.xml
|
||
|
//
|
||
|
// Created by mavgen_objc.py
|
||
|
// https://mavlink.io/en/
|
||
|
//
|
||
|
|
||
|
#import "MVMessage${name_camel_case}.h"
|
||
|
|
||
|
@implementation MVMessage${name_camel_case}
|
||
|
|
||
|
- (id)initWithSystemId:(uint8_t)systemId componentId:(uint8_t)componentId${{arg_fields: ${name_lower_camel_case}:(${arg_type}${array_prefix})${name_lower_camel_case}}} {
|
||
|
if ((self = [super init])) {
|
||
|
mavlink_msg_${name_lower}_pack(systemId, componentId, &(self->_message)${{arg_fields:, ${name_lower_camel_case}}});
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
${{fields:- (${return_type})${name_lower_camel_case}${get_arg_objc} {
|
||
|
${return_method_implementation}
|
||
|
}
|
||
|
|
||
|
}}
|
||
|
- (NSString *)description {
|
||
|
return [NSString stringWithFormat:@"%@${{fields:, ${name_lower_camel_case}=${print_format}}}", [super description]${{fields:, ${get_message}}}];
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
''', m)
|
||
|
f.close()
|
||
|
|
||
|
def camel_case_from_underscores(string):
|
||
|
"""generate a CamelCase string from an underscore_string."""
|
||
|
components = string.split('_')
|
||
|
string = ''
|
||
|
for component in components:
|
||
|
string += component[0].upper() + component[1:]
|
||
|
return string
|
||
|
|
||
|
def lower_camel_case_from_underscores(string):
|
||
|
"""generate a lower-cased camelCase string from an underscore_string.
|
||
|
For example: my_variable_name -> myVariableName"""
|
||
|
components = string.split('_')
|
||
|
string = components[0]
|
||
|
for component in components[1:]:
|
||
|
string += component[0].upper() + component[1:]
|
||
|
return string
|
||
|
|
||
|
def generate_shared(basename, xml_list):
|
||
|
# Create a dictionary to hold all the values we want to use in the templates
|
||
|
template_dict = {}
|
||
|
template_dict['parse_time'] = xml_list[0].parse_time
|
||
|
template_dict['message'] = []
|
||
|
template_dict['message_definition_files'] = []
|
||
|
|
||
|
print("Generating Objective-C implementation in directory %s" % basename)
|
||
|
mavparse.mkdir_p(basename)
|
||
|
|
||
|
for xml in xml_list:
|
||
|
template_dict['message'].extend(xml.message)
|
||
|
basename_camel_case = camel_case_from_underscores(xml.basename)
|
||
|
template_dict['message_definition_files'].append({'name_camel_case': basename_camel_case})
|
||
|
if not template_dict.get('basename', None):
|
||
|
template_dict['basename'] = xml.basename
|
||
|
else:
|
||
|
template_dict['basename'] = template_dict['basename'] + ', ' + xml.basename
|
||
|
|
||
|
# Sort messages by ID
|
||
|
template_dict['message'] = sorted(template_dict['message'], key = lambda message : message.id)
|
||
|
|
||
|
# Add name_camel_case to each message object
|
||
|
for message in template_dict['message']:
|
||
|
message.name_camel_case = camel_case_from_underscores(message.name_lower)
|
||
|
|
||
|
generate_mavlink(basename, template_dict)
|
||
|
generate_base_message(basename, template_dict)
|
||
|
|
||
|
def generate_message_definitions(basename, xml):
|
||
|
'''generate files for one XML file'''
|
||
|
|
||
|
directory = os.path.join(basename, xml.basename)
|
||
|
|
||
|
print("Generating Objective-C implementation in directory %s" % directory)
|
||
|
mavparse.mkdir_p(directory)
|
||
|
|
||
|
xml.basename_camel_case = camel_case_from_underscores(xml.basename)
|
||
|
|
||
|
# Add some extra field attributes for convenience
|
||
|
for m in xml.message:
|
||
|
m.basename = xml.basename
|
||
|
m.parse_time = xml.parse_time
|
||
|
m.name_camel_case = camel_case_from_underscores(m.name_lower)
|
||
|
for f in m.fields:
|
||
|
f.name_lower_camel_case = lower_camel_case_from_underscores(f.name);
|
||
|
f.get_message = "[self %s]" % f.name_lower_camel_case
|
||
|
f.return_method_implementation = ''
|
||
|
f.array_prefix = ''
|
||
|
f.array_return_arg = ''
|
||
|
f.get_arg = ''
|
||
|
f.get_arg_objc = ''
|
||
|
if f.enum:
|
||
|
f.return_type = f.enum
|
||
|
f.arg_type = f.enum
|
||
|
else:
|
||
|
f.return_type = f.type
|
||
|
f.arg_type = f.type
|
||
|
if f.print_format is None:
|
||
|
if f.array_length != 0:
|
||
|
f.print_format = "%@"
|
||
|
elif f.type.startswith('uint64_t'):
|
||
|
f.print_format = "%lld"
|
||
|
elif f.type.startswith('uint') or f.type.startswith('int'):
|
||
|
f.print_format = "%d"
|
||
|
elif f.type.startswith('float'):
|
||
|
f.print_format = "%f"
|
||
|
elif f.type.startswith('char'):
|
||
|
f.print_format = "%c"
|
||
|
else:
|
||
|
print("print_format unsupported for type %s" % f.type)
|
||
|
if f.array_length != 0:
|
||
|
f.get_message = '@"[array of %s[%d]]"' % (f.type, f.array_length)
|
||
|
f.array_prefix = ' *'
|
||
|
f.array_return_arg = '%s, %u, ' % (f.name, f.array_length)
|
||
|
f.return_type = 'uint16_t'
|
||
|
f.get_arg = ', %s' % (f.name)
|
||
|
f.get_arg_objc = ':(%s *)%s' % (f.type, f.name)
|
||
|
if f.type == 'char':
|
||
|
# Special handling for strings (assumes all char arrays are strings)
|
||
|
f.return_type = 'NSString *'
|
||
|
f.get_arg_objc = ''
|
||
|
f.get_message = "[self %s]" % f.name_lower_camel_case
|
||
|
f.return_method_implementation = \
|
||
|
"""char string[%(array_length)d];
|
||
|
mavlink_msg_%(message_name_lower)s_get_%(name)s(&(self->_message), (char *)&string);
|
||
|
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}
|
||
|
|
||
|
if not f.return_method_implementation:
|
||
|
f.return_method_implementation = \
|
||
|
"""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}
|
||
|
|
||
|
for m in xml.message:
|
||
|
m.arg_fields = []
|
||
|
for f in m.fields:
|
||
|
if not f.omit_arg:
|
||
|
m.arg_fields.append(f)
|
||
|
|
||
|
generate_message_definitions_h(directory, xml)
|
||
|
for m in xml.message:
|
||
|
generate_message(directory, m)
|
||
|
|
||
|
|
||
|
def generate(basename, xml_list):
|
||
|
'''generate complete MAVLink Objective-C implemenation'''
|
||
|
|
||
|
generate_shared(basename, xml_list)
|
||
|
for xml in xml_list:
|
||
|
generate_message_definitions(basename, xml)
|