00001
00002 '''
00003 parse a MAVLink protocol XML file and generate a python implementation
00004
00005 Copyright Andrew Tridgell 2011
00006 Released under GNU GPL version 3 or later
00007 '''
00008
00009 import sys, textwrap, os
00010 from . import mavparse, mavtemplate
00011
00012 t = mavtemplate.MAVTemplate()
00013
00014 def generate_preamble(outf, msgs, basename, args, xml):
00015 print("Generating preamble")
00016 t.write(outf, """
00017 '''
00018 MAVLink protocol implementation (auto-generated by mavgen.py)
00019
00020 Generated from: ${FILELIST}
00021
00022 Note: this file has been auto-generated. DO NOT EDIT
00023 '''
00024
00025 import struct, array, time, json, os, sys, platform
00026
00027 from ...generator.mavcrc import x25crc
00028
00029 WIRE_PROTOCOL_VERSION = "${WIRE_PROTOCOL_VERSION}"
00030 DIALECT = "${DIALECT}"
00031
00032 native_supported = platform.system() != 'Windows' # Not yet supported on other dialects
00033 native_force = 'MAVNATIVE_FORCE' in os.environ # Will force use of native code regardless of what client app wants
00034 native_testing = 'MAVNATIVE_TESTING' in os.environ # Will force both native and legacy code to be used and their results compared
00035
00036 if native_supported:
00037 try:
00038 import mavnative
00039 except ImportError:
00040 print("ERROR LOADING MAVNATIVE - falling back to python implementation")
00041 native_supported = False
00042
00043 # some base types from mavlink_types.h
00044 MAVLINK_TYPE_CHAR = 0
00045 MAVLINK_TYPE_UINT8_T = 1
00046 MAVLINK_TYPE_INT8_T = 2
00047 MAVLINK_TYPE_UINT16_T = 3
00048 MAVLINK_TYPE_INT16_T = 4
00049 MAVLINK_TYPE_UINT32_T = 5
00050 MAVLINK_TYPE_INT32_T = 6
00051 MAVLINK_TYPE_UINT64_T = 7
00052 MAVLINK_TYPE_INT64_T = 8
00053 MAVLINK_TYPE_FLOAT = 9
00054 MAVLINK_TYPE_DOUBLE = 10
00055
00056
00057 class MAVLink_header(object):
00058 '''MAVLink message header'''
00059 def __init__(self, msgId, mlen=0, seq=0, srcSystem=0, srcComponent=0):
00060 self.mlen = mlen
00061 self.seq = seq
00062 self.srcSystem = srcSystem
00063 self.srcComponent = srcComponent
00064 self.msgId = msgId
00065
00066 def pack(self):
00067 return struct.pack('BBBBBB', ${PROTOCOL_MARKER}, self.mlen, self.seq,
00068 self.srcSystem, self.srcComponent, self.msgId)
00069
00070 class MAVLink_message(object):
00071 '''base MAVLink message class'''
00072 def __init__(self, msgId, name):
00073 self._header = MAVLink_header(msgId)
00074 self._payload = None
00075 self._msgbuf = None
00076 self._crc = None
00077 self._fieldnames = []
00078 self._type = name
00079
00080 def get_msgbuf(self):
00081 if isinstance(self._msgbuf, bytearray):
00082 return self._msgbuf
00083 return bytearray(self._msgbuf)
00084
00085 def get_header(self):
00086 return self._header
00087
00088 def get_payload(self):
00089 return self._payload
00090
00091 def get_crc(self):
00092 return self._crc
00093
00094 def get_fieldnames(self):
00095 return self._fieldnames
00096
00097 def get_type(self):
00098 return self._type
00099
00100 def get_msgId(self):
00101 return self._header.msgId
00102
00103 def get_srcSystem(self):
00104 return self._header.srcSystem
00105
00106 def get_srcComponent(self):
00107 return self._header.srcComponent
00108
00109 def get_seq(self):
00110 return self._header.seq
00111
00112 def __str__(self):
00113 ret = '%s {' % self._type
00114 for a in self._fieldnames:
00115 v = getattr(self, a)
00116 ret += '%s : %s, ' % (a, v)
00117 ret = ret[0:-2] + '}'
00118 return ret
00119
00120 def __ne__(self, other):
00121 return not self.__eq__(other)
00122
00123 def __eq__(self, other):
00124 if other == None:
00125 return False
00126
00127 if self.get_type() != other.get_type():
00128 return False
00129
00130 # We do not compare CRC because native code doesn't provide it
00131 #if self.get_crc() != other.get_crc():
00132 # return False
00133
00134 if self.get_seq() != other.get_seq():
00135 return False
00136
00137 if self.get_srcSystem() != other.get_srcSystem():
00138 return False
00139
00140 if self.get_srcComponent() != other.get_srcComponent():
00141 return False
00142
00143 for a in self._fieldnames:
00144 if getattr(self, a) != getattr(other, a):
00145 return False
00146
00147 return True
00148
00149 def to_dict(self):
00150 d = dict({})
00151 d['mavpackettype'] = self._type
00152 for a in self._fieldnames:
00153 d[a] = getattr(self, a)
00154 return d
00155
00156 def to_json(self):
00157 return json.dumps(self.to_dict())
00158
00159 def pack(self, mav, crc_extra, payload):
00160 self._payload = payload
00161 self._header = MAVLink_header(self._header.msgId, len(payload), mav.seq,
00162 mav.srcSystem, mav.srcComponent)
00163 self._msgbuf = self._header.pack() + payload
00164 crc = x25crc(self._msgbuf[1:])
00165 if ${crc_extra}: # using CRC extra
00166 crc.accumulate_str(struct.pack('B', crc_extra))
00167 self._crc = crc.crc
00168 self._msgbuf += struct.pack('<H', self._crc)
00169 return self._msgbuf
00170
00171 """, {'FILELIST' : ",".join(args),
00172 'PROTOCOL_MARKER' : xml.protocol_marker,
00173 'DIALECT' : os.path.splitext(os.path.basename(basename))[0],
00174 'crc_extra' : xml.crc_extra,
00175 'WIRE_PROTOCOL_VERSION' : xml.wire_protocol_version })
00176
00177 def generate_enums(outf, enums):
00178 print("Generating enums")
00179 outf.write('''
00180 # enums
00181
00182 class EnumEntry(object):
00183 def __init__(self, name, description):
00184 self.name = name
00185 self.description = description
00186 self.param = {}
00187
00188 enums = {}
00189 ''')
00190 wrapper = textwrap.TextWrapper(initial_indent="", subsequent_indent=" # ")
00191 for e in enums:
00192 outf.write("\n# %s\n" % e.name)
00193 outf.write("enums['%s'] = {}\n" % e.name)
00194 for entry in e.entry:
00195 outf.write("%s = %u # %s\n" % (entry.name, entry.value, wrapper.fill(entry.description)))
00196 outf.write("enums['%s'][%d] = EnumEntry('%s', '''%s''')\n" % (e.name,
00197 int(entry.value), entry.name,
00198 entry.description))
00199 for param in entry.param:
00200 outf.write("enums['%s'][%d].param[%d] = '''%s'''\n" % (e.name,
00201 int(entry.value),
00202 int(param.index),
00203 param.description))
00204
00205 def generate_message_ids(outf, msgs):
00206 print("Generating message IDs")
00207 outf.write("\n# message IDs\n")
00208 outf.write("MAVLINK_MSG_ID_BAD_DATA = -1\n")
00209 for m in msgs:
00210 outf.write("MAVLINK_MSG_ID_%s = %u\n" % (m.name.upper(), m.id))
00211
00212 def generate_classes(outf, msgs):
00213 print("Generating class definitions")
00214 wrapper = textwrap.TextWrapper(initial_indent=" ", subsequent_indent=" ")
00215 for m in msgs:
00216 classname = "MAVLink_%s_message" % m.name.lower()
00217 fieldname_str = ", ".join(map(lambda s: "'%s'" % s, m.fieldnames))
00218 ordered_fieldname_str = ", ".join(map(lambda s: "'%s'" % s, m.ordered_fieldnames))
00219
00220 outf.write("""
00221 class %s(MAVLink_message):
00222 '''
00223 %s
00224 '''
00225 id = MAVLINK_MSG_ID_%s
00226 name = '%s'
00227 fieldnames = [%s]
00228 ordered_fieldnames = [ %s ]
00229 format = '%s'
00230 native_format = bytearray('%s', 'ascii')
00231 orders = %s
00232 lengths = %s
00233 array_lengths = %s
00234 crc_extra = %s
00235
00236 def __init__(self""" % (classname, wrapper.fill(m.description.strip()),
00237 m.name.upper(),
00238 m.name.upper(),
00239 fieldname_str,
00240 ordered_fieldname_str,
00241 m.fmtstr,
00242 m.native_fmtstr,
00243 m.order_map,
00244 m.len_map,
00245 m.array_len_map,
00246 m.crc_extra))
00247 if len(m.fields) != 0:
00248 outf.write(", " + ", ".join(m.fieldnames))
00249 outf.write("):\n")
00250 outf.write(" MAVLink_message.__init__(self, %s.id, %s.name)\n" % (classname, classname))
00251 outf.write(" self._fieldnames = %s.fieldnames\n" % (classname))
00252 for f in m.fields:
00253 outf.write(" self.%s = %s\n" % (f.name, f.name))
00254 outf.write("""
00255 def pack(self, mav):
00256 return MAVLink_message.pack(self, mav, %u, struct.pack('%s'""" % (m.crc_extra, m.fmtstr))
00257 for field in m.ordered_fields:
00258 if (field.type != "char" and field.array_length > 1):
00259 for i in range(field.array_length):
00260 outf.write(", self.{0:s}[{1:d}]".format(field.name,i))
00261 else:
00262 outf.write(", self.{0:s}".format(field.name))
00263 outf.write("))\n")
00264
00265
00266 def native_mavfmt(field):
00267 '''work out the struct format for a type (in a form expected by mavnative)'''
00268 map = {
00269 'float' : 'f',
00270 'double' : 'd',
00271 'char' : 'c',
00272 'int8_t' : 'b',
00273 'uint8_t' : 'B',
00274 'uint8_t_mavlink_version' : 'v',
00275 'int16_t' : 'h',
00276 'uint16_t' : 'H',
00277 'int32_t' : 'i',
00278 'uint32_t' : 'I',
00279 'int64_t' : 'q',
00280 'uint64_t' : 'Q',
00281 }
00282 return map[field.type]
00283
00284 def mavfmt(field):
00285 '''work out the struct format for a type'''
00286 map = {
00287 'float' : 'f',
00288 'double' : 'd',
00289 'char' : 'c',
00290 'int8_t' : 'b',
00291 'uint8_t' : 'B',
00292 'uint8_t_mavlink_version' : 'B',
00293 'int16_t' : 'h',
00294 'uint16_t' : 'H',
00295 'int32_t' : 'i',
00296 'uint32_t' : 'I',
00297 'int64_t' : 'q',
00298 'uint64_t' : 'Q',
00299 }
00300
00301 if field.array_length:
00302 if field.type == 'char':
00303 return str(field.array_length)+'s'
00304 return str(field.array_length)+map[field.type]
00305 return map[field.type]
00306
00307 def generate_mavlink_class(outf, msgs, xml):
00308 print("Generating MAVLink class")
00309
00310 outf.write("\n\nmavlink_map = {\n");
00311 for m in msgs:
00312 outf.write(" MAVLINK_MSG_ID_%s : MAVLink_%s_message,\n" % (m.name.upper(), m.name.lower()))
00313 outf.write("}\n\n")
00314
00315 t.write(outf, """
00316 class MAVError(Exception):
00317 '''MAVLink error class'''
00318 def __init__(self, msg):
00319 Exception.__init__(self, msg)
00320 self.message = msg
00321
00322 class MAVString(str):
00323 '''NUL terminated string'''
00324 def __init__(self, s):
00325 str.__init__(self)
00326 def __str__(self):
00327 i = self.find(chr(0))
00328 if i == -1:
00329 return self[:]
00330 return self[0:i]
00331
00332 class MAVLink_bad_data(MAVLink_message):
00333 '''
00334 a piece of bad data in a mavlink stream
00335 '''
00336 def __init__(self, data, reason):
00337 MAVLink_message.__init__(self, MAVLINK_MSG_ID_BAD_DATA, 'BAD_DATA')
00338 self._fieldnames = ['data', 'reason']
00339 self.data = data
00340 self.reason = reason
00341 self._msgbuf = data
00342
00343 def __str__(self):
00344 '''Override the __str__ function from MAVLink_messages because non-printable characters are common in to be the reason for this message to exist.'''
00345 return '%s {%s, data:%s}' % (self._type, self.reason, [('%x' % ord(i) if isinstance(i, str) else '%x' % i) for i in self.data])
00346
00347 class MAVLink(object):
00348 '''MAVLink protocol handling class'''
00349 def __init__(self, file, srcSystem=0, srcComponent=0, use_native=False):
00350 self.seq = 0
00351 self.file = file
00352 self.srcSystem = srcSystem
00353 self.srcComponent = srcComponent
00354 self.callback = None
00355 self.callback_args = None
00356 self.callback_kwargs = None
00357 self.send_callback = None
00358 self.send_callback_args = None
00359 self.send_callback_kwargs = None
00360 self.buf = bytearray()
00361 self.expected_length = 8
00362 self.have_prefix_error = False
00363 self.robust_parsing = False
00364 self.protocol_marker = ${protocol_marker}
00365 self.little_endian = ${little_endian}
00366 self.crc_extra = ${crc_extra}
00367 self.sort_fields = ${sort_fields}
00368 self.total_packets_sent = 0
00369 self.total_bytes_sent = 0
00370 self.total_packets_received = 0
00371 self.total_bytes_received = 0
00372 self.total_receive_errors = 0
00373 self.startup_time = time.time()
00374 if native_supported and (use_native or native_testing or native_force):
00375 print("NOTE: mavnative is currently beta-test code")
00376 self.native = mavnative.NativeConnection(MAVLink_message, mavlink_map)
00377 else:
00378 self.native = None
00379 if native_testing:
00380 self.test_buf = bytearray()
00381
00382 def set_callback(self, callback, *args, **kwargs):
00383 self.callback = callback
00384 self.callback_args = args
00385 self.callback_kwargs = kwargs
00386
00387 def set_send_callback(self, callback, *args, **kwargs):
00388 self.send_callback = callback
00389 self.send_callback_args = args
00390 self.send_callback_kwargs = kwargs
00391
00392 def send(self, mavmsg):
00393 '''send a MAVLink message'''
00394 buf = mavmsg.pack(self)
00395 self.file.write(buf)
00396 self.seq = (self.seq + 1) % 256
00397 self.total_packets_sent += 1
00398 self.total_bytes_sent += len(buf)
00399 if self.send_callback:
00400 self.send_callback(mavmsg, *self.send_callback_args, **self.send_callback_kwargs)
00401
00402 def bytes_needed(self):
00403 '''return number of bytes needed for next parsing stage'''
00404 if self.native:
00405 ret = self.native.expected_length - len(self.buf)
00406 else:
00407 ret = self.expected_length - len(self.buf)
00408
00409 if ret <= 0:
00410 return 1
00411 return ret
00412
00413 def __parse_char_native(self, c):
00414 '''this method exists only to see in profiling results'''
00415 m = self.native.parse_chars(c)
00416 return m
00417
00418 def __callbacks(self, msg):
00419 '''this method exists only to make profiling results easier to read'''
00420 if self.callback:
00421 self.callback(msg, *self.callback_args, **self.callback_kwargs)
00422
00423 def parse_char(self, c):
00424 '''input some data bytes, possibly returning a new message'''
00425 self.buf.extend(c)
00426
00427 self.total_bytes_received += len(c)
00428
00429 if self.native:
00430 if native_testing:
00431 self.test_buf.extend(c)
00432 m = self.__parse_char_native(self.test_buf)
00433 m2 = self.__parse_char_legacy()
00434 if m2 != m:
00435 print("Native: %s\\nLegacy: %s\\n" % (m, m2))
00436 raise Exception('Native vs. Legacy mismatch')
00437 else:
00438 m = self.__parse_char_native(self.buf)
00439 else:
00440 m = self.__parse_char_legacy()
00441
00442 if m != None:
00443 self.total_packets_received += 1
00444 self.__callbacks(m)
00445
00446 return m
00447
00448 def __parse_char_legacy(self):
00449 '''input some data bytes, possibly returning a new message (uses no native code)'''
00450 if len(self.buf) >= 1 and self.buf[0] != ${protocol_marker}:
00451 magic = self.buf[0]
00452 self.buf = self.buf[1:]
00453 if self.robust_parsing:
00454 m = MAVLink_bad_data(chr(magic), "Bad prefix")
00455 self.expected_length = 8
00456 self.total_receive_errors += 1
00457 return m
00458 if self.have_prefix_error:
00459 return None
00460 self.have_prefix_error = True
00461 self.total_receive_errors += 1
00462 raise MAVError("invalid MAVLink prefix '%s'" % magic)
00463 self.have_prefix_error = False
00464 if len(self.buf) >= 2:
00465 if sys.version_info[0] < 3:
00466 (magic, self.expected_length) = struct.unpack('BB', str(self.buf[0:2])) # bytearrays are not supported in py 2.7.3
00467 else:
00468 (magic, self.expected_length) = struct.unpack('BB', self.buf[0:2])
00469 self.expected_length += 8
00470 if self.expected_length >= 8 and len(self.buf) >= self.expected_length:
00471 mbuf = array.array('B', self.buf[0:self.expected_length])
00472 self.buf = self.buf[self.expected_length:]
00473 self.expected_length = 8
00474 if self.robust_parsing:
00475 try:
00476 m = self.decode(mbuf)
00477 except MAVError as reason:
00478 m = MAVLink_bad_data(mbuf, reason.message)
00479 self.total_receive_errors += 1
00480 else:
00481 m = self.decode(mbuf)
00482 return m
00483 return None
00484
00485 def parse_buffer(self, s):
00486 '''input some data bytes, possibly returning a list of new messages'''
00487 m = self.parse_char(s)
00488 if m is None:
00489 return None
00490 ret = [m]
00491 while True:
00492 m = self.parse_char("")
00493 if m is None:
00494 return ret
00495 ret.append(m)
00496 return ret
00497
00498 def decode(self, msgbuf):
00499 '''decode a buffer as a MAVLink message'''
00500 # decode the header
00501 try:
00502 magic, mlen, seq, srcSystem, srcComponent, msgId = struct.unpack('cBBBBB', msgbuf[:6])
00503 except struct.error as emsg:
00504 raise MAVError('Unable to unpack MAVLink header: %s' % emsg)
00505 if ord(magic) != ${protocol_marker}:
00506 raise MAVError("invalid MAVLink prefix '%s'" % magic)
00507 if mlen != len(msgbuf)-8:
00508 raise MAVError('invalid MAVLink message length. Got %u expected %u, msgId=%u' % (len(msgbuf)-8, mlen, msgId))
00509
00510 if not msgId in mavlink_map:
00511 raise MAVError('unknown MAVLink message ID %u' % msgId)
00512
00513 # decode the payload
00514 type = mavlink_map[msgId]
00515 fmt = type.format
00516 order_map = type.orders
00517 len_map = type.lengths
00518 crc_extra = type.crc_extra
00519
00520 # decode the checksum
00521 try:
00522 crc, = struct.unpack('<H', msgbuf[-2:])
00523 except struct.error as emsg:
00524 raise MAVError('Unable to unpack MAVLink CRC: %s' % emsg)
00525 crcbuf = msgbuf[1:-2]
00526 if ${crc_extra}: # using CRC extra
00527 crcbuf.append(crc_extra)
00528 crc2 = x25crc(crcbuf)
00529 if crc != crc2.crc:
00530 raise MAVError('invalid MAVLink CRC in msgID %u 0x%04x should be 0x%04x' % (msgId, crc, crc2.crc))
00531
00532 try:
00533 t = struct.unpack(fmt, msgbuf[6:-2])
00534 except struct.error as emsg:
00535 raise MAVError('Unable to unpack MAVLink payload type=%s fmt=%s payloadLength=%u: %s' % (
00536 type, fmt, len(msgbuf[6:-2]), emsg))
00537
00538 tlist = list(t)
00539 # handle sorted fields
00540 if ${sort_fields}:
00541 t = tlist[:]
00542 if sum(len_map) == len(len_map):
00543 # message has no arrays in it
00544 for i in range(0, len(tlist)):
00545 tlist[i] = t[order_map[i]]
00546 else:
00547 # message has some arrays
00548 tlist = []
00549 for i in range(0, len(order_map)):
00550 order = order_map[i]
00551 L = len_map[order]
00552 tip = sum(len_map[:order])
00553 field = t[tip]
00554 if L == 1 or isinstance(field, str):
00555 tlist.append(field)
00556 else:
00557 tlist.append(t[tip:(tip + L)])
00558
00559 # terminate any strings
00560 for i in range(0, len(tlist)):
00561 if isinstance(tlist[i], str):
00562 tlist[i] = str(MAVString(tlist[i]))
00563 t = tuple(tlist)
00564 # construct the message object
00565 try:
00566 m = type(*t)
00567 except Exception as emsg:
00568 raise MAVError('Unable to instantiate MAVLink message of type %s : %s' % (type, emsg))
00569 m._msgbuf = msgbuf
00570 m._payload = msgbuf[6:-2]
00571 m._crc = crc
00572 m._header = MAVLink_header(msgId, mlen, seq, srcSystem, srcComponent)
00573 return m
00574 """, xml)
00575
00576 def generate_methods(outf, msgs):
00577 print("Generating methods")
00578
00579 def field_descriptions(fields):
00580 ret = ""
00581 for f in fields:
00582 ret += " %-18s : %s (%s)\n" % (f.name, f.description.strip(), f.type)
00583 return ret
00584
00585 wrapper = textwrap.TextWrapper(initial_indent="", subsequent_indent=" ")
00586
00587 for m in msgs:
00588 comment = "%s\n\n%s" % (wrapper.fill(m.description.strip()), field_descriptions(m.fields))
00589
00590 selffieldnames = 'self, '
00591 for f in m.fields:
00592 if f.omit_arg:
00593 selffieldnames += '%s=%s, ' % (f.name, f.const_value)
00594 else:
00595 selffieldnames += '%s, ' % f.name
00596 selffieldnames = selffieldnames[:-2]
00597
00598 sub = {'NAMELOWER' : m.name.lower(),
00599 'SELFFIELDNAMES' : selffieldnames,
00600 'COMMENT' : comment,
00601 'FIELDNAMES' : ", ".join(m.fieldnames)}
00602
00603 t.write(outf, """
00604 def ${NAMELOWER}_encode(${SELFFIELDNAMES}):
00605 '''
00606 ${COMMENT}
00607 '''
00608 return MAVLink_${NAMELOWER}_message(${FIELDNAMES})
00609
00610 """, sub)
00611
00612 t.write(outf, """
00613 def ${NAMELOWER}_send(${SELFFIELDNAMES}):
00614 '''
00615 ${COMMENT}
00616 '''
00617 return self.send(self.${NAMELOWER}_encode(${FIELDNAMES}))
00618
00619 """, sub)
00620
00621
00622 def generate(basename, xml):
00623 '''generate complete python implemenation'''
00624 if basename.endswith('.py'):
00625 filename = basename
00626 else:
00627 filename = basename + '.py'
00628
00629 msgs = []
00630 enums = []
00631 filelist = []
00632 for x in xml:
00633 msgs.extend(x.message)
00634 enums.extend(x.enum)
00635 filelist.append(os.path.basename(x.filename))
00636
00637 for m in msgs:
00638 if xml[0].little_endian:
00639 m.fmtstr = '<'
00640 else:
00641 m.fmtstr = '>'
00642 m.native_fmtstr = m.fmtstr
00643 for f in m.ordered_fields:
00644 m.fmtstr += mavfmt(f)
00645 m.native_fmtstr += native_mavfmt(f)
00646 m.order_map = [ 0 ] * len(m.fieldnames)
00647 m.len_map = [ 0 ] * len(m.fieldnames)
00648 m.array_len_map = [ 0 ] * len(m.fieldnames)
00649 for i in range(0, len(m.fieldnames)):
00650 m.order_map[i] = m.ordered_fieldnames.index(m.fieldnames[i])
00651 m.array_len_map[i] = m.ordered_fields[i].array_length
00652 for i in range(0, len(m.fieldnames)):
00653 n = m.order_map[i]
00654 m.len_map[n] = m.fieldlengths[i]
00655
00656 print("Generating %s" % filename)
00657 outf = open(filename, "w")
00658 generate_preamble(outf, msgs, basename, filelist, xml[0])
00659 generate_enums(outf, enums)
00660 generate_message_ids(outf, msgs)
00661 generate_classes(outf, msgs)
00662 generate_mavlink_class(outf, msgs, xml[0])
00663 generate_methods(outf, msgs)
00664 outf.close()
00665 print("Generated %s OK" % filename)