Package ais :: Module aisxmlbinmsg2py
[hide private]
[frames] | no frames]

Source Code for Module ais.aisxmlbinmsg2py

   1  #!/usr/bin/env python 
   2   
   3  __version__ = '$Revision: 4791 $'.split()[1] 
   4  __date__ = '$Date: 2006-09-24 14:01:41 -0400 (Sun, 24 Sep 2006) $'.split()[1] 
   5  __author__ = 'Kurt Schwehr' 
   6   
   7  __doc__=''' 
   8   
   9  Tools to generate python code to serialize/deserialize messages 
  10  between python and ais binary.  Trying to be as inline as possible, so 
  11  no XML on the fly like in ais-py.   
  12   
  13  serialize: python to ais binary 
  14  deserialize: ais binary to python 
  15   
  16  The generated code uses translators.py, binary.py, and aisstring.py 
  17  which should be packaged with the resulting files. 
  18   
  19  @requires: U{lxml<http://codespeak.net/lxml/>} 
  20  @requires: U{epydoc<http://epydoc.sourceforge.net/>} >= 3.0alpha3 
  21  @requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>} >= 1.2 
  22   
  23  @author: U{'''+__author__+'''<http://schwehr.org/>} 
  24  @version: ''' + __version__ +''' 
  25  @copyright: 2006 
  26  @var __date__: Date of last svn commit 
  27  @undocumented: __version__ __author__ __doc__ parser 
  28  @since: 2006-Sep-24 
  29  @status: under development 
  30  @organization: U{CCOM<http://ccom.unh.edu/>} 
  31  @license: GPL v2 
  32   
  33  @todo: add a link to generated doc string to bring up the html for the pretty version 
  34  @todo: write a separate validation script that distinguishes standard messages and bin messages 
  35  @todo: make sure binary is only used in AIS ITU messages and not within the binary messages! 
  36  @todo: arrays 
  37  @bug: NOT complete 
  38  ''' 
  39   
  40  import sys, os 
  41  from decimal import Decimal 
  42  from lxml import etree  
  43   
44 -def suggestType(name,curType,printout=True):
45 ''' 46 Try to suggest a type name if one did not work. 47 48 @param printout: if true, write a suggestion to stdout. 49 50 >>> suggestType('myFieldName','unsigned int') 51 Recommend switching "unsigned int" to "uint" for field "myFieldName" 52 'uint' 53 54 >>> suggestType('JohnWarfon','yoyodyne') 55 Sorry! No recommendation available for bad type "yoyodyne" for field "JohnWarfon" 56 ''' 57 newType = None 58 if curType.lower()=='unsigned int': 59 newType = 'uint' 60 elif curType.lower()=='unsigned decimal': 61 newType = 'udecimal' 62 63 if printout: 64 if None != newType: 65 print 'Recommend switching "'+curType+'" to "'+newType+'" for field "'+name+'"' 66 else: 67 print 'Sorry! No recommendation available for bad type "'+curType+'" for field "'+name+'"' 68 return newType
69 70
71 -def hasSubTag(et,subtag):
72 ''' 73 @return: true if the tag a sub tag with name subtag 74 ''' 75 if 0<len(et.xpath(subtag)): return True 76 return False
77 78 79 #def writeBeginning(o,aisBinMsgET):
80 -def writeBeginning(o):
81 ''' 82 Write the doc string header for the message file 83 84 @param o: Open output file to write code to. 85 @param msgET: element tree for the ais message definition. 86 Must be pre-expanded with the expandais.py command. 87 ''' 88 import datetime 89 d = datetime.datetime.utcnow() 90 dateStr = str(d.year)+'-'+("%02d" %d.month)+'-'+("%02d"%d.day) 91 92 # FIX: what to do for __version__, @since, etc? 93 # Need to pass in info about the source file, etc. 94 95 # Minor trickery to get svn to ignore the keywords in the next few lines 96 o.write('''#!/usr/bin/env python 97 98 __version__ = '$Revision: 4791 $'.split()[1] 99 __date__ = '$Da'''+'''te: '''+dateStr+''' $'.split()[1] 100 __author__ = 'xmlbinmsg' 101 102 __doc__=\'\'\' 103 104 Autogenerated python functions to serialize/deserialize binary messages. 105 106 Generated by: '''+__file__+''' 107 108 Need to then wrap these functions with the outer AIS packet and then 109 convert the whole binary blob to a NMEA string. Those functions are 110 not currently provided in this file. 111 112 serialize: python to ais binary 113 deserialize: ais binary to python 114 115 The generated code uses translators.py, binary.py, and aisstring.py 116 which should be packaged with the resulting files. 117 118 ''') 119 120 o.write(''' 121 @requires: U{epydoc<http://epydoc.sourceforge.net/>} > 3.0alpha3 122 @requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>} 123 124 @author: \'\'\'+__author__+\'\'\' 125 @version: \'\'\' + __version__ +\'\'\' 126 @var __date__: Date of last svn commit 127 @undocumented: __version__ __author__ __doc__ parser 128 @status: under development 129 @license: Generated code has no license 130 \'\'\' 131 132 import sys 133 from decimal import Decimal 134 from BitVector import BitVector 135 136 import binary, aisstring 137 138 # FIX: check to see if these will be needed 139 TrueBV = BitVector(bitstring="1") 140 "Why always rebuild the True bit? This should speed things up a bunch" 141 FalseBV = BitVector(bitstring="0") 142 "Why always rebuild the False bit? This should speed things up a bunch" 143 144 145 ''') 146 return
147
148 -def generatePython(infile,outfile, prefixName=False,verbose=False):
149 ''' 150 @param infile: xml ais binary message definition file 151 @param outfile: where to dump the python code 152 ''' 153 154 aisMsgsET = etree.parse(infile).getroot() 155 156 o = file(outfile,'w') 157 os.chmod(outfile,0755) 158 159 writeBeginning(o) 160 161 for msgET in aisMsgsET: 162 if msgET.tag != 'message': continue 163 print msgET.tag, msgET.attrib['name'] 164 165 if len(msgET.xpath('include-struct')) > 0: 166 sys.exit("ERROR: cannot handle xml that still has include-struct tags.\n Please use expandais.py.") 167 buildHelpers(o,msgET,prefixName=prefixName,verbose=verbose) 168 buildEncode(o,msgET,prefixName=prefixName,verbose=verbose) 169 buildDecode(o,msgET,prefixName=prefixName) 170 buildDecodeParts(o,msgET,prefixName=prefixName) # functions that only decode one field 171 buildPrint(o,msgET,prefixName=prefixName) 172 buildLUT(o,msgET,prefixName=prefixName) 173 buildSQL(o,msgET,prefixName=prefixName) 174 175 o.write('\n\n######################################################################\n') 176 o.write('# UNIT TESTING\n') 177 o.write('######################################################################\n') 178 o.write('import unittest\n') 179 180 for msgET in aisMsgsET: 181 if msgET.tag != 'message': continue 182 print 'Building unit tests for message ...', msgET.attrib['name'] 183 184 buildUnitTest(o,msgET, prefixName=prefixName) 185 186 for msgET in aisMsgsET: 187 if msgET.tag != 'message': continue 188 buildMain(o,msgET, prefixName=prefixName) 189 190 return
191 192 ###################################################################### 193 # Build Helpers 194 ###################################################################### 195
196 -def buildHelpers(o,msgET, verbose=False, prefixName=False):
197 ''' 198 emit the fieldList and other things??? 199 200 @param o: open file where resulting code will be written 201 @param msgET: Element Tree starting at a message node 202 203 @todo: for lookuptable/entry values, make it also print the decoded value. 204 @todo: use a different name for message and field 205 ''' 206 207 if verbose: 208 msgname = msgET.attrib['name'] 209 print 'Building helpers for',msgname 210 211 if prefixName: 212 o.write(prefixName,'FieldList = [\n') 213 else: 214 o.write('fieldList = [\n') 215 216 for field in msgET.xpath('field'): 217 name = field.attrib['name'] 218 o.write('\t\''+name+'\',\n') 219 220 o.write(']\n\n')
221 # FIX: add some documentation to the fieldList 222 # FIX: like that it is used as the default csv list 223 224 ###################################################################### 225 # SIMPLE PRINT 226 ###################################################################### 227
228 -def getMaxFieldNameLen(msgET):
229 '''Get the maximum string length of any field name''' 230 maxStrLen=0 231 for field in msgET.xpath('field'): 232 fieldLen = len(field.attrib['name']) 233 if fieldLen>maxStrLen: maxStrLen = fieldLen 234 return maxStrLen
235
236 -def padStrRight(aStr,strlen):
237 '''Pad a string out to the length requested with spaces out to the right''' 238 return aStr + ' '*(strlen-len(aStr))
239
240 -def haveLocatableMessage(msgET):
241 '''Make sure this message has both long/x and lat/y fields. 242 @rtype: bool 243 ''' 244 #if getLongitudeFieldName(msgET) and getLatitudeFieldName(msgET): return True 245 246 if 0==len(msgET.xpath('field[contains(@name, "longitude")]')): return False 247 if 0==len(msgET.xpath('field[contains(@name, "latitude")]')): return False 248 return True
249
250 -def getLongitudeFieldName(msgET):
251 ''' 252 Dig up the first field name that include longitude and return it 253 @todo: might want to allow a search for a special tag to mark this 254 ''' 255 return msgET.xpath('field[contains(@name, "longitude")]')[0].attrib['name']
256
257 -def getLatitudeFieldName(msgET):
258 ''' 259 Dig up the first field name that include longitude and return it 260 @todo: might want to allow a search for a special tag to mark this 261 ''' 262 return msgET.xpath('field[contains(@name, "latitude")]')[0].attrib['name']
263
264 -def buildPrint(o,msgET, verbose=False, prefixName=False):
265 ''' 266 Write a simple in order print for the resulting dictionary. 267 268 @param o: open file where resulting code will be written 269 @param msgET: Element Tree starting at a message node 270 271 @todo: for lookuptable/entry values, make it also print the decoded value. 272 @todo: use a different name for message and field 273 ''' 274 assert(msgET.tag=='message') 275 name = msgET.attrib['name'] 276 msgName = msgname = name # FIX: make these all msgName 277 278 print 'Generating print for',name # FIX: verbose? 279 # +1 for the ':' 280 maxFieldLen = 1 + getMaxFieldNameLen(msgET) 281 282 283 ############################## 284 # Html builder 285 ############################## 286 287 printHtmlName = 'printHtml' 288 if prefixName: printHtmlName = name+'printHtml' 289 290 #################### 291 ####### HTML format 292 #################### 293 #o.write('\telif \'html\'==format:\n') 294 o.write('\n') 295 o.write('def '+printHtmlName+'(params, out=sys.stdout):\n') 296 o.write('\t\tout.write("<h3>'+msgname+'<h3>\\n")\n') 297 o.write('\t\tout.write("<table border=\\"1\\">\\n")\n') 298 #o.write('\t\tout.write("<tr bgcolor=\\"#9acd32\\">\\n")\n') 299 o.write('\t\tout.write("<tr bgcolor=\\"orange\\">\\n")\n') 300 o.write('\t\tout.write("<th align=\\"left\\">Field Name</th>\\n")\n') 301 o.write('\t\tout.write("<th align=\\"left\\">Type</th>\\n")\n') 302 o.write('\t\tout.write("<th align=\\"left\\">Value</th>\\n")\n') 303 o.write('\t\tout.write("<th align=\\"left\\">Value in Lookup Table</th>\\n")\n') 304 o.write('\t\tout.write("<th align=\\"left\\">Units</th>\\n")\n') 305 306 307 for field in msgET.xpath('field'): 308 o.write('\t\tout.write("\\n")\n') 309 o.write('\t\tout.write("<tr>\\n")\n') 310 name = field.attrib['name'] 311 type = field.attrib['type'] 312 o.write('\t\tout.write("<td>'+name+'</td>\\n")\n') 313 o.write('\t\tout.write("<td>'+type+'</td>\\n")\n') 314 315 numbits = int(field.attrib['numberofbits']) 316 required = None; 317 if hasSubTag(field,'required'): 318 required = field.xpath('required')[0].text 319 unavailable=None; 320 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 321 arraylen=1 322 if 'arraylength' in field.attrib: arraylen=int(field.attrib['arraylength']) 323 324 if 1==arraylen or type=='aisstr6': 325 o.write('\t\tif \''+name+'\' in params:\n\t\t\tout.write("\t<td>"+str(params[\''+name+'\'])+"</td>\\n")\n') 326 if not hasSubTag(field,'lookuptable'): 327 # Just duplicate the value through 328 o.write('\t\t\tout.write("\t<td>"+str(params[\''+name+'\'])+"</td>\\n")\n') 329 else: 330 lutName = name+'DecodeLut' 331 if prefixName: lutName = msgname+name.capitalize()+'DecodeLut' 332 333 o.write('\t\t\tif str(params[\''+name+'\']) in '+lutName+':\n') 334 o.write('\t\t\t\tout.write("<td>"+'+lutName+'[str(params[\''+name+'\'])]+"</td>")\n') 335 o.write('\t\t\telse:\n') 336 o.write('\t\t\t\tout.write("<td><i>Missing LUT entry</i></td>")\n') 337 else: sys.exit ('FIX: handle arrays in the buildPrint func') 338 339 if hasSubTag(field,'units'): 340 o.write('\t\tout.write("<td>'+field.xpath('units')[0].text+'</td>\\n")\n') 341 342 o.write('\t\tout.write("</tr>\\n")\n') 343 344 o.write('\t\tout.write("</table>\\n")\n') 345 o.write('\n') 346 347 ############################## 348 ####### KML GoogleEarth 349 ############################## 350 351 printKmlName = 'printKml' 352 if prefixName: printKmlName = name+'printKml' 353 if not haveLocatableMessage(msgET): printKmlName=None 354 else: 355 o.write('\n') 356 o.write('def printKml(params, out=sys.stdout):\n') 357 o.write('\t\'\'\'KML (Keyhole Markup Language) for Google Earth, but without the header/footer\'\'\'\n') 358 o.write('\tout.write("\\\t<Placemark>\\n")\n') 359 o.write('\tout.write("\\t\t<name>"+str(params[\''+msgET.attrib['titlefield']+'\'])+"</name>\\n")\n') 360 o.write('\tout.write("\\t\\t<description>\\n")\n') 361 362 o.write('\timport StringIO\n') 363 o.write('\tbuf = StringIO.StringIO()\n') 364 o.write('\tprintHtml(params,buf)\n') 365 o.write('\timport cgi\n') 366 o.write('\tout.write(cgi.escape(buf.getvalue()))\n') 367 368 o.write('\tout.write("\\t\\t</description>\\n")\n') 369 o.write('\tout.write("\\t\\t<styleUrl>#m_ylw-pushpin_copy0</styleUrl>\\n")\n') 370 o.write('\tout.write("\\t\\t<Point>\\n")\n') 371 o.write('\tout.write("\\t\\t\\t<coordinates>")\n') 372 o.write('\tout.write(str(params[\''+getLongitudeFieldName(msgET)+'\']))\n') 373 o.write('\tout.write(\',\')\n') 374 o.write('\tout.write(str(params[\''+getLatitudeFieldName(msgET)+'\']))\n') 375 o.write('\tout.write(",0</coordinates>\\n")\n') 376 o.write('\tout.write("\\t\\t</Point>\\n")\n') 377 o.write('\tout.write("\\t</Placemark>\\n")\n') 378 o.write('\n') 379 380 381 ############################## 382 ##### Main print dispatch 383 ############################## 384 385 funcName = 'printFields' 386 if prefixName: funcName = name+'PrintFields' 387 388 o.write('def '+funcName+'(params, out=sys.stdout, format=\'std\', fieldList=None):\n') 389 390 ######################################## 391 # doc string 392 o.write("\t'''Print a "+name+" message to stdout.\n\n") 393 o.write('\tFields in params:\n') 394 for field in msgET.xpath('field'): 395 desc = field[0].text.replace('\n',' ') # get ride of new lines 396 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 397 if len(field.xpath("required")) == 1: 398 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 399 400 o.write('\n') 401 o.write('\t@param params: Dictionary of field names/values. \n') 402 o.write('\t@param out: File like object to write to\n') 403 404 o.write("\t@rtype: stdout\n") 405 o.write("\t@return: text to out\n") 406 o.write("\t'''\n\n") 407 408 ######################################## 409 # Actually build the code 410 411 o.write('\tif \'std\'==format:\n') 412 o.write('\t\tout.write("'+name+':\\n")\n') 413 414 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 415 416 417 for field in msgET.xpath('field'): 418 name = field.attrib['name'] 419 type = field.attrib['type'] 420 numbits = int(field.attrib['numberofbits']) 421 required = None; 422 if hasSubTag(field,'required'): 423 required = field.xpath('required')[0].text 424 #print 'required set for',name,'to',required 425 unavailable=None; 426 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 427 arraylen=1 428 if 'arraylength' in field.attrib: 429 arraylen=int(field.attrib['arraylength']) 430 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 431 else: 432 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 433 434 if 1==arraylen or type=='aisstr6': 435 o.write('\t\tif \''+name+'\' in params: out.write("\t'+padStrRight(name+':',maxFieldLen)+' "+str(params[\''+name+'\'])+"\\n")\n') 436 # FIX: elif aisstr6 strip @@@ and then print 437 else: 438 sys.exit ('FIX: handle arrays in the buildPrint func') 439 440 #################### 441 ####### Comma separated values (csv) 442 #################### 443 o.write(''' elif 'csv'==format: 444 if None == options.fieldList: 445 options.fieldList = fieldList 446 needComma = False; 447 for field in fieldList: 448 if needComma: out.write(',') 449 needComma = True 450 if field in params: 451 out.write(str(params[field])) 452 # else: leave it empty 453 out.write("\\n") 454 ''') 455 456 #################### 457 ####### Other dispatchers 458 #################### 459 o.write('\telif \'html\'==format:\n') 460 o.write('\t\t'+printHtmlName+'(params,out)\n') 461 462 o.write(''' elif 'sql'==format: 463 sqlInsertStr(params,out) 464 ''') 465 466 if haveLocatableMessage(msgET): 467 # Has a lon/lat pair somewhere in there. 468 o.write('\telif \'kml\'==format:\n') 469 o.write('\t\t'+printKmlName+'(params,out)\n') 470 471 o.write('\telif \'kml-full\'==format:\n') 472 473 o.write('\t\tout.write(\"<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>\\n")\n') 474 o.write('\t\tout.write("<kml xmlns=\\"http://earth.google.com/kml/2.1\\">\\n")\n') 475 o.write('\t\tout.write("<Document>\\n")\n') 476 # o.write('\t\tout.write("\t<name>Position</name>\\n")\n') 477 o.write('\t\tout.write("\t<name>'+msgName+'</name>\\n")\n') 478 479 o.write('\t\t'+printKmlName+'(params,out)\n') 480 481 o.write('\t\tout.write("</Document>\\n")\n') 482 o.write('\t\tout.write("</kml>\\n")\n') 483 484 485 486 #################### 487 ####### Make safety check 488 #################### 489 o.write('\telse: \n') 490 o.write('\t\tprint "ERROR: unknown format:",format\n') 491 o.write('\t\tassert False\n') 492 493 o.write('\n\treturn # Nothing to return\n\n')
494 495 496 ###################################################################### 497 # Build Lookup tables for enumerated int or uint fields 498 ###################################################################### 499 500
501 -def buildSQL(o,msgET, verbose=False, prefixName=False):
502 ''' 503 Write SQL code 504 505 @param o: open file where resulting code will be written 506 @param msgET: Element Tree starting at a message node 507 @param verbose: talk lots in the process 508 @param prefixName: set to a string to have the commands prefixed by that character. 509 ''' 510 assert(msgET.tag=='message') 511 msgName = msgET.attrib['name'] 512 print 'Generating SQL commands for', msgName 513 514 createFuncName = 'sqlCreate' 515 if prefixName: createFuncName = msgName+'SqlCreate' 516 insertFuncName = 'sqlInsert' 517 if prefixName: insertFuncName = msgName+'SqlInsert' 518 519 o.write('''###################################################################### 520 # SQL SUPPORT 521 ###################################################################### 522 523 ''') 524 525 o.write('def '+createFuncName+'''Str(outfile=sys.stdout, fields=None, extraFields=None): 526 \'\'\' 527 Return the SQL CREATE command for this message type 528 @param outfile: file like object to print to. 529 @param fields: which fields to put in the create. Defaults to all. 530 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 531 @return: sql create string 532 @rtype: str 533 534 @see: sqlCreate 535 \'\'\' 536 outfile.write(str(sqlCreate(fields,extraFields))) 537 538 ''') 539 540 o.write('def '+createFuncName+'''(fields=None, extraFields=None): 541 \'\'\' 542 Return the sqlhelp object to create the table. 543 544 @param fields: which fields to put in the create. Defaults to all. 545 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 546 @return: An object that can be used to generate a return 547 @rtype: sqlhelp.create 548 \'\'\' 549 if None == fields: fields = fieldList 550 import sqlhelp 551 ''') 552 553 o.write('\tc = sqlhelp.create(\''+msgName+'\')\n') 554 555 for field in msgET.xpath('field'): 556 fieldName = field.attrib['name'] 557 fieldType = field.attrib['type'] 558 559 o.write('\tif \''+fieldName+'\' in fields: c.add') 560 # FIX: add the ADD command here. 561 if fieldType in ('int','uint'): o.write('Int (\''+fieldName+'\')') 562 elif fieldType in ('float' ): o.write('Real(\''+fieldName+'\')') 563 elif fieldType == 'bool': o.write('Bool(\''+fieldName+'\')') 564 elif fieldType in ('decimal','udecimal'): 565 if not hasSubTag(field,'decimalplaces'): 566 print '\n ** ERROR: missing decimalplaces field for ',fieldName,'\n' 567 assert (False) 568 scaleSQL = int(field.xpath('decimalplaces')[0].text) # number of decimal places 569 numBits = int(field.attrib['numberofbits']) 570 scaleVal = float(field.xpath('scale')[0].text) 571 precision = scaleSQL + len(str(int((2**numBits)/scaleVal))) 572 #precision += 1 # FIX: do I really need this safety factor 573 o.write('Decimal(\''+fieldName+'\','+str(precision)+','+str(scaleSQL)+')') 574 elif fieldType == 'binary': 575 numBits = int(field.attrib['numberofbits']) 576 if -1 == numBits: numBits=1024 # FIX: what is a good maximum for AIS? 577 o.write('BitVarying(\''+fieldName+'\','+str(numBits)+')') 578 elif fieldType == 'aisstr6': 579 arrayLength = int(field.attrib['arraylength']) 580 o.write('VarChar('+fieldName+','+str(arrayLength)+')') 581 else: 582 print '\n\n *** Unknown type in SQL code generation for field',fieldName+':',fieldType,'\n\n' 583 assert(False) 584 o.write('\n') 585 586 o.write('\n\treturn c\n\n') 587 588 589 # FIX: function name 590 o.write('''def '''+insertFuncName+'''Str(params, outfile=sys.stdout, extraParams=None): 591 \'\'\' 592 Return the SQL CREATE command for this message type 593 @param params: dictionary of values keyed by field name 594 @param outfile: file like object to print to. 595 @param extraParams: A sequence of tuples containing (name,sql type) for additional fields 596 @return: sql create string 597 @rtype: str 598 599 @see: sqlCreate 600 \'\'\' 601 outfile.write(str(sqlInsert(params,extraParams))) 602 603 604 ''') 605 606 # FIX: function name 607 o.write('''def '''+insertFuncName+'''(params,extraParams=None): 608 \'\'\' 609 Give the SQL insert statement 610 @param params: dict keyed by field name of values 611 @param extraParams: any extra fields that you have created beyond the normal ais message fields 612 @rtype: sqlhelp.insert 613 @return: insert class instance 614 @todo: allow optional type checking of params? 615 @warning: this will take invalid keys happily and do what??? 616 \'\'\' 617 import sqlhelp 618 i = sqlhelp.insert(\'''' + msgName + '''\') 619 for key in params: i.add(key,params[key]) 620 if None != extraParams: 621 for key in extraParams: 622 i.add(key,extraParams[key]) 623 624 return i 625 ''')
626 627 628 629 ###################################################################### 630 # Build Lookup tables for enumerated int or uint fields 631 ###################################################################### 632 633
634 -def buildLUT(o,msgET, verbose=False, prefixName=False):
635 ''' 636 Write lookup tables for enumerated types (uint or int, maybe bool too). 637 638 @todo: FIX: what to do about multiple entries with the same text? Need to ban that kind of thing 639 @todo: Make doc strings for each LUT. 640 641 @param o: open file where resulting code will be written 642 @param msgET: Element Tree starting at a message node 643 ''' 644 assert(msgET.tag=='message') 645 msgname = msgET.attrib['name'] 646 647 print 'Generating lookup tables for',msgname # FIX: verbose? 648 649 for field in msgET.xpath('field'): 650 name = field.attrib['name'] 651 if not hasSubTag(field,'lookuptable'): continue 652 lut = field.xpath('lookuptable')[0] 653 654 lutName = name 655 if prefixName: lutName = msgname+name.capitalize() 656 657 o.write(lutName+'EncodeLut = {\n') 658 for entry in lut.xpath('entry'): 659 o.write('\t\''+entry.text+'\':\''+entry.attrib['key']+'\',\n') 660 o.write('\t} #'+lutName+'EncodeLut\n') 661 o.write('\n') 662 663 # FIX: make doc string for LUT here 664 665 o.write(lutName+'DecodeLut = {\n') 666 for entry in lut.xpath('entry'): 667 o.write('\t\''+entry.attrib['key']+'\':\''+entry.text+'\',\n') 668 o.write('\t} # '+lutName+'EncodeLut\n') 669 o.write('\n')
670 671 # FIX: make doc string for LUT here 672 673 674 675 676 ###################################################################### 677 # ENCODERS 678 ######################################################################
679 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
680 ''' 681 Build the encoder for boolean variables 682 @type o: file like obj 683 @param o: where write the code 684 @type name: str 685 @param name: field name 686 @type type: str 687 @param type: bool, etc. 688 @type numbits: int = 1 689 @param numbits: How many bits per unit datum (must be 1 for bools) 690 @type required: bool or None 691 @param required: If not None, then the value must be set to this. 692 @type arraylen: int >= 1 693 @param arraylen: many bools will there be? FIX: handle variable 694 @type unavailable: bool or None 695 @param unavailable: the default value to use if none given (if not None) 696 @return: None 697 ''' 698 699 if verbose: print 'bool encode',name,': unvail=',unavailable 700 701 assert type.lower()=='bool' 702 assert numbits==1 703 if arraylen != 1: assert False # FIX... handle arrays 704 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n') 705 if None != required: 706 assert type(required)==bool 707 if required: o.write('\t\tbvList.append(TrueBV)\n') 708 else: o.write('\t\tbvList.append(FalseBV)\n') 709 if verbose: o.write('\n') 710 return 711 712 if None==unavailable: 713 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n') 714 o.write('\telse: bvList.append(FalseBV)\n') 715 else: # Have a default value that can be filled in 716 assert type(unavailable)==bool 717 o.write("\tif '"+name+"' in params:\n") 718 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n') 719 o.write('\t\telse: bvList.append(FalseBV)\n') 720 o.write('\telse:\n') 721 if unavailable: o.write('\t\tbvList.append(TrueBV)\n') 722 else: o.write('\t\tbvList.append(FalseBV)\n') 723 if verbose: o.write('\n')
724
725 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
726 ''' 727 Build the encoder for unsigned integer variables 728 729 @type o: file like obj 730 @param o: where write the code 731 @type name: str 732 @param name: field name 733 @type type: str 734 @param type: uint, bool, etc. 735 @type numbits: int >= 1 736 @param numbits: How many bits per unit datum (must be 1..32) 737 @type required: bool or None 738 @param required: If not None, then the value must be set to this. 739 @type arraylen: int >= 1 740 @param arraylen: many unsigned ints will there be? FIX: handle variable 741 @type unavailable: bool or None 742 @param unavailable: the default value to use if none given (if not None) 743 @return: None 744 ''' 745 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 746 747 assert type=='uint' 748 assert numbits>=1 and numbits<=32 749 if arraylen != 1: assert False # FIX... handle arrays 750 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 751 752 if None != required: 753 if verbose: print ' required:',required 754 required=int(required) 755 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n') 756 if verbose: o.write('\n') 757 return 758 759 if None==unavailable: 760 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n') 761 else: # Have a default value that can be filled in 762 #assert type(unavailable)== 763 int(unavailable) # Make sure unavailable is a number object 764 o.write("\tif '"+name+"' in params:\n") 765 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n') 766 o.write('\telse:\n') 767 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n') 768 769 if verbose: o.write('\n')
770 771
772 -def encodeFloat(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
773 ''' 774 Build the encoder for IEEE float variables 775 776 @type o: file like obj 777 @param o: where write the code 778 @type name: str 779 @param name: field name 780 @type fieldType: str 781 @param fieldType: uint, bool, etc. 782 @type numbits: int >= 1 783 @param numbits: How many bits per unit datum (must be 1..32) 784 @type required: bool or None 785 @param required: If not None, then the value must be set to this. 786 @type arraylen: int >= 1 787 @param arraylen: many unsigned ints will there be? FIX: handle variable 788 @type unavailable: bool or None 789 @param unavailable: the default value to use if none given (if not None) 790 @return: None 791 ''' 792 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 793 794 assert numbits==32 # Force by the IEEE spec 795 if arraylen != 1: assert False # FIX... handle arrays 796 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 797 798 if None != required: 799 if verbose: print ' required:',required 800 required=int(required) 801 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n') 802 if verbose: o.write('\n') 803 return 804 805 if None==unavailable: 806 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n') 807 else: # Have a default value that can be filled in 808 int(unavailable) # Make sure unavailable is a number object 809 o.write("\tif '"+name+"' in params:\n") 810 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n') 811 o.write('\telse:\n') 812 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n') 813 814 if verbose: o.write('\n')
815 816
817 -def encodeAisstr6(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
818 ''' 819 Build the encoder for aisstr6 variables. Generally are arrays. 820 @bug: do we need to optionally check for a valid string? 821 822 @type o: file like obj 823 @param o: where write the code 824 @type name: str 825 @param name: field name 826 @type fieldType: str 827 @param fieldType: uint, bool, etc. 828 @type numbits: int >= 1 829 @param numbits: How many bits per unit datum (must be 1..32) 830 @type required: bool or None 831 @param required: If not None, then the value must be set to this. 832 @type arraylen: int >= 1 833 @param arraylen: many unsigned ints will there be? FIX: handle variable 834 @type unavailable: bool or None 835 @param unavailable: the default value to use if none given (if not None) 836 @return: None 837 ''' 838 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 839 totLen = str(numbits*arraylen) 840 assert numbits==6 # Each character must be 6 bits 841 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 842 843 if None != required: 844 if verbose: print ' required:',required 845 required=int(required) 846 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+totLen+'))\n') 847 if verbose: o.write('\n') 848 return 849 850 if None==unavailable: 851 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 852 else: # Have a default value that can be filled in 853 o.write("\tif '"+name+"' in params:\n") 854 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 855 o.write('\telse:\n') 856 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+totLen+'))\n') 857 858 if verbose: o.write('\n')
859 860
861 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
862 ''' 863 Build the encoder for signed integer variables 864 865 @type o: file like obj 866 @param o: where write the code 867 @type name: str 868 @param name: field name 869 @type type: str 870 @param type: uint, bool, etc. 871 @type numbits: int >= 1 872 @param numbits: How many bits per unit datum (must be 1..32) 873 @type required: bool or None 874 @param required: If not None, then the value must be set to this. 875 @type arraylen: int >= 1 876 @param arraylen: many signed ints will there be? FIX: handle variable 877 @type unavailable: number or None 878 @param unavailable: the default value to use if none given (if not None) 879 @return: None 880 ''' 881 if verbose: print ' encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 882 883 assert numbits>=1 and numbits<=32 884 if arraylen != 1: assert False # FIX... handle arrays 885 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 886 887 if None != required: 888 if verbose: print ' required:',required 889 required=int(required) 890 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n') 891 if verbose: o.write('\n') 892 return 893 894 895 if None==unavailable: 896 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n') 897 else: # Have a default value that can be filled in 898 #assert type(unavailable)== 899 int(unavailable) # Make sure unavailable is a number object 900 o.write("\tif '"+name+"' in params:\n") 901 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n') 902 o.write('\telse:\n') 903 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n') 904 905 if verbose: o.write('\n')
906 907 908 909 # FIX: Ummm... why am I passing the type? I guess it makes the one print statement easier
910 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
911 ''' 912 Build the encoder for signed decimal variables 913 914 @type o: file like obj 915 @param o: where write the code 916 @type name: str 917 @param name: field name 918 @type type: str 919 @param type: decimal 920 @type numbits: int >= 1 921 @param numbits: How many bits per unit datum (must be 1..32) 922 @type required: bool or None 923 @param required: If not None, then the value must be set to this. 924 @type arraylen: int >= 1 925 @param arraylen: many decimals will there be? FIX: handle variable 926 @type unavailable: Decimal or None 927 @param unavailable: the default value to use if none given (if not None) 928 @return: None 929 ''' 930 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 931 932 assert numbits>=1 and numbits<=32 933 if arraylen != 1: assert False # FIX... handle arrays 934 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 935 936 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 937 if None == scale: 938 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 939 print 'Beware canadians bearing travel videos' 940 scale='1' 941 942 if None != required: 943 if verbose: print ' required:',required 944 required=int(required) 945 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n') 946 if verbose: o.write('\n') 947 return 948 949 # FIX: can I get rid of the Decimal around params? 950 if None==unavailable: 951 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 952 else: # Have a default value that can be filled in 953 o.write("\tif '"+name+"' in params:\n") 954 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 955 o.write('\telse:\n') 956 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n') 957 958 if verbose: o.write('\n')
959 960 961
962 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
963 ''' 964 Build the encoder for signed decimal variables 965 966 @type o: file like obj 967 @param o: where write the code 968 @type name: str 969 @param name: field name 970 @type type: str 971 @param type: decimal 972 @type numbits: int >= 1 973 @param numbits: How many bits per unit datum (must be 1..32) 974 @type required: bool or None 975 @param required: If not None, then the value must be set to this. 976 @type arraylen: int >= 1 977 @param arraylen: many decimals will there be? FIX: handle variable 978 @type unavailable: Decimal or None 979 @param unavailable: the default value to use if none given (if not None) 980 @return: None 981 ''' 982 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 983 assert type=='udecimal' 984 assert numbits>=1 and numbits<=32 985 if arraylen != 1: assert False # FIX... handle arrays 986 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 987 988 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 989 if None == scale: 990 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 991 print 'Beware canadians bearing travel videos' 992 scale='1' 993 994 if None != required: 995 if verbose: print ' required:',required 996 required=int(required) 997 assert(0<=required) 998 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n') 999 if verbose: o.write('\n') 1000 return 1001 1002 # FIX: can I get rid of the Decimal around params? 1003 if None==unavailable: 1004 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1005 else: # Have a default value that can be filled in 1006 o.write("\tif '"+name+"' in params:\n") 1007 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1008 o.write('\telse:\n') 1009 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n') 1010 1011 if verbose: o.write('\n')
1012 1013 1014
1015 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1016 ''' 1017 Build the encoder for binary variables. This is just a pass through. 1018 This is used for the ais binary message wrapper (e.g. msg 8). Do 1019 not use it within binary messages. 1020 1021 @type o: file like obj 1022 @param o: where write the code 1023 @type name: str 1024 @param name: field name 1025 @type type: str 1026 @param type: binary 1027 @type numbits: int >= 1 1028 @param numbits: How many bits per unit datum (must be 1..1024 or so) 1029 @type required: bool or None 1030 @param required: If not None, then the value must be set to this. 1031 @type arraylen: int >= 1 1032 @param arraylen: many decimals will there be? FIX: handle variable 1033 @type unavailable: Decimal or None 1034 @param unavailable: the default value to use if none given (if not None) 1035 @return: None 1036 ''' 1037 if verbose: print ' encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable 1038 assert type=='binary' 1039 assert (numbits>=1 and numbits<=1024) or numbits==-1 1040 assert (None == required) # don't allow this 1041 assert (None == unavailable) # don't allow this 1042 1043 if arraylen != 1: assert False # Do not handle arrays. Arrays of bits is just not necessary. 1044 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1045 1046 # FIX: can I get rid of the Decimal around params? 1047 o.write('\tbvList.append(params[\''+name+'\'])\n') # Just pass it through 1048 1049 if verbose: o.write('\n')
1050 1051 1052 ###################################################################### 1053 # DECODERS 1054 ###################################################################### 1055
1056 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1057 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1058 ''' 1059 Build the decoder for boolean variables 1060 1061 @type o: file like obj 1062 @param o: where write the code 1063 @type name: str 1064 @param name: field name 1065 @type type: str 1066 @param type: uint, bool, etc. 1067 @type startindex: int 1068 @param startindex: bit that begins the bool(s) 1069 @type numbits: int = 1 1070 @param numbits: How many bits per unit datum (must be 1 for bools) 1071 @type required: bool or None 1072 @param required: If not None, then the value must be set to this. 1073 @type arraylen: int >= 1 1074 @param arraylen: many bools will there be? FIX: handle variable 1075 @type unavailable: bool or None 1076 @param unavailable: the default value to use if none given (if not None) 1077 @type bv: str 1078 @param bv: BitVector containing the incoming data 1079 @type dataDict: str 1080 @param dataDict: dictionary in which to place the results 1081 @type decodeOnly: bool 1082 @param decodeOnly: Set to true to only get the code for decoding 1083 @rtype: int 1084 @return: index one past the end of where this read 1085 ''' 1086 assert(type=='bool') 1087 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1088 #int(startindex); int(numbits) # Make sure it is a number 1089 assert numbits==1 1090 assert arraylen == 1 # FIX... handle arrays 1091 1092 if None != required: 1093 assert type(required)==bool 1094 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1095 if required: o.write('True\n') 1096 else: o.write('False\n') 1097 if not decodeOnly: o.write('\n') 1098 return int(startindex)+int(numbits) 1099 1100 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1101 o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))') 1102 if not decodeOnly: o.write('\n') 1103 1104 return int(startindex)+int(numbits)
1105 1106
1107 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1108 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1109 ''' 1110 Build the decoder for unsigned integer variables 1111 1112 @type o: file like obj 1113 @param o: where write the code 1114 @type name: str 1115 @param name: field name 1116 @type type: str 1117 @param type: uint, etc. 1118 @type startindex: int 1119 @param startindex: bit that begins the uint(s) 1120 @type numbits: int >= 1 1121 @param numbits: How many bits per unit datum 1122 @type required: int or None 1123 @param required: If not None, then the value must be set to this. 1124 @type arraylen: int >= 1 1125 @param arraylen: many ints will there be? FIX: handle variable 1126 @type unavailable: int or None 1127 @param unavailable: the default value to use if none given (if not None) 1128 @type bv: str 1129 @param bv: BitVector containing the incoming data 1130 @type dataDict: str 1131 @param dataDict: dictionary in which to place the results 1132 @type decodeOnly: bool 1133 @param decodeOnly: Set to true to only get the code for decoding 1134 @rtype: int 1135 @return: index one past the end of where this read 1136 ''' 1137 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1138 if None==arraylen: arraylen=1 1139 assert arraylen == 1 # FIX... handle arrays 1140 assert numbits>=1 1141 if not decodeOnly: verbose=False 1142 1143 if None != required: 1144 int(required) # Make sure required is a number 1145 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1146 o.write(str(required)) 1147 if not decodeOnly: o.write('\n') 1148 return startindex+numbits 1149 1150 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1151 o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])') 1152 if not decodeOnly: o.write('\n') 1153 if verbose: o.write('\n') 1154 1155 return startindex+numbits
1156 1157
1158 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1159 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1160 ''' 1161 Build the decoder for unsigned integer variables 1162 1163 @type o: file like obj 1164 @param o: where write the code 1165 @type name: str 1166 @param name: field name 1167 @type type: str 1168 @param type: int 1169 @type startindex: int 1170 @param startindex: bit that begins the int(s) 1171 @type numbits: int >= 1 1172 @param numbits: How many bits per unit datum 1173 @type required: int or None 1174 @param required: If not None, then the value must be set to this. 1175 @type arraylen: int >= 1 1176 @param arraylen: many ints will there be? FIX: handle variable 1177 @type unavailable: int or None 1178 @param unavailable: the default value to use if none given (if not None) 1179 @type bv: str 1180 @param bv: BitVector containing the incoming data 1181 @type dataDict: str 1182 @param dataDict: dictionary in which to place the results 1183 @type decodeOnly: bool 1184 @param decodeOnly: Set to true to only get the code for decoding 1185 @rtype: int 1186 @return: index one past the end of where this read 1187 ''' 1188 assert type=='int' 1189 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1190 if None==arraylen: arraylen=1 1191 end = startindex+int(numbits)*int(arraylen) 1192 assert arraylen == 1 # FIX... handle arrays 1193 assert numbits>=1 1194 1195 if None != required: 1196 int(required) # Make sure required is a number 1197 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1198 o.write(str(required)) 1199 if not decodeOnly: o.write('\n') 1200 return end 1201 1202 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1203 o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])') 1204 if not decodeOnly: o.write('\n') 1205 if verbose: o.write('\n') 1206 1207 return end
1208
1209 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1210 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1211 ''' 1212 Build the decoder for IEEE float variables 1213 1214 @type o: file like obj 1215 @param o: where write the code 1216 @type name: str 1217 @param name: field name 1218 @type type: str 1219 @param type: int 1220 @type startindex: int 1221 @param startindex: bit that begins the int(s) 1222 @type numbits: int >= 1 1223 @param numbits: How many bits per unit datum 1224 @type required: float or None 1225 @param required: If not None, then the value must be set to this. 1226 @type arraylen: int >= 1 1227 @param arraylen: many ints will there be? FIX: handle variable 1228 @type unavailable: float or None 1229 @param unavailable: the default value to use if none given (if not None) 1230 @type bv: str 1231 @param bv: BitVector containing the incoming data 1232 @type dataDict: str 1233 @param dataDict: dictionary in which to place the results 1234 @type decodeOnly: bool 1235 @param decodeOnly: Set to true to only get the code for decoding 1236 @rtype: int 1237 @return: index one past the end of where this read 1238 ''' 1239 assert type=='float' 1240 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1241 if None==arraylen: arraylen=1 1242 end = startindex+int(numbits)*int(arraylen) 1243 assert arraylen == 1 # FIX... handle arrays 1244 assert numbits>=1 1245 1246 if None != required: 1247 float(required) # Make sure required is a number 1248 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1249 o.write(str(required)) 1250 if not decodeOnly: o.write('\n') 1251 if verbose: o.write('\n') 1252 return end 1253 1254 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1255 o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])') 1256 if not decodeOnly: o.write('\n') 1257 1258 return end
1259 1260
1261 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1262 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1263 ''' 1264 Build the decoder for aisstr6 variables. Generally arrays. 1265 @bug: FIX: validate strings?? 1266 @type o: file like obj 1267 @param o: where write the code 1268 @type name: str 1269 @param name: field name 1270 @type type: str 1271 @param type: 'aisstr6' 1272 @type startindex: int 1273 @param startindex: bit that begins the int(s) 1274 @type numbits: int >= 1 1275 @param numbits: How many bits per unit datum 1276 @type required: restricted str or None 1277 @param required: If not None, then the value must be set to this. 1278 @type arraylen: int >= 1 1279 @param arraylen: many ints will there be? FIX: handle variable 1280 @type unavailable: restricted str or None 1281 @param unavailable: the default value to use if none given (if not None) 1282 @type bv: str 1283 @param bv: BitVector containing the incoming data 1284 @type dataDict: str 1285 @param dataDict: dictionary in which to place the results 1286 @type decodeOnly: bool 1287 @param decodeOnly: Set to true to only get the code for decoding 1288 @rtype: int 1289 @return: index one past the end of where this read 1290 ''' 1291 assert type=='aisstr6' 1292 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1293 if None==arraylen: arraylen=1 1294 end = startindex+int(numbits)*int(arraylen) 1295 assert arraylen >= 1 # FIX... handle arrays 1296 assert numbits>=1 1297 1298 if None != required: 1299 float(required) # Make sure required is a number 1300 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1301 o.write(required) 1302 if not decodeOnly: o.write('\n') 1303 return end 1304 1305 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1306 o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])') 1307 if not decodeOnly: o.write('\n') 1308 1309 return end
1310 1311
1312 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1313 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1314 ''' 1315 Build the decoder for signed decimal variables 1316 1317 @type o: file like obj 1318 @param o: where write the code 1319 @type name: str 1320 @param name: field name 1321 @type type: str 1322 @param type: 'decimal' 1323 @type startindex: int 1324 @param startindex: bit that begins the int(s) 1325 @type numbits: int >= 1 1326 @param numbits: How many bits per unit datum 1327 @type required: Decimal or None 1328 @param required: If not None, then the value must be set to this. 1329 @type arraylen: int >= 1 1330 @param arraylen: many ints will there be? FIX: handle variable 1331 @type unavailable: Decimal or None 1332 @param unavailable: the default value to use if none given (if not None) 1333 @type bv: str 1334 @param bv: BitVector containing the incoming data 1335 @type dataDict: str 1336 @param dataDict: dictionary in which to place the results 1337 @type decodeOnly: bool 1338 @param decodeOnly: Set to true to only get the code for decoding 1339 @rtype: int 1340 @return: index one past the end of where this read 1341 ''' 1342 assert type=='decimal' 1343 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1344 if None==arraylen: arraylen=1 1345 end = startindex+int(numbits)*int(arraylen) 1346 assert arraylen == 1 # FIX... handle arrays 1347 assert numbits>=1 and numbits <= 32 1348 1349 if None == scale: scale='1' # Warning about this was in the encode section 1350 1351 if None != required: 1352 Decimal(required) # Make sure required is a number 1353 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1354 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1355 if not decodeOnly: o.write('\n') 1356 return end 1357 1358 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1359 o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')') 1360 if not decodeOnly: o.write('\n') 1361 1362 return end
1363 1364 1365 1366
1367 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1368 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1369 ''' 1370 Build the decoder for unsigned decimal variables 1371 1372 @type o: file like obj 1373 @param o: where write the code 1374 @type name: str 1375 @param name: field name 1376 @type type: str 1377 @param type: 'udecimal' 1378 @type startindex: int 1379 @param startindex: bit that begins the int(s) 1380 @type numbits: int >= 1 1381 @param numbits: How many bits per unit datum 1382 @type required: Decimal or None 1383 @param required: If not None, then the value must be set to this. 1384 @type arraylen: int >= 1 1385 @param arraylen: many ints will there be? FIX: handle variable 1386 @type unavailable: Decimal or None 1387 @param unavailable: the default value to use if none given (if not None) 1388 @type bv: str 1389 @param bv: BitVector containing the incoming data 1390 @type dataDict: str 1391 @param dataDict: dictionary in which to place the results 1392 @type decodeOnly: bool 1393 @param decodeOnly: Set to true to only get the code for decoding 1394 @rtype: int 1395 @return: index one past the end of where this read 1396 ''' 1397 assert type=='udecimal' 1398 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1399 if None==arraylen: arraylen=1 1400 end = startindex+int(numbits)*int(arraylen) 1401 assert arraylen == 1 # FIX... handle arrays 1402 assert numbits>=1 and numbits <= 32 1403 1404 if None == scale: scale='1' # Warning about this was in the encode section 1405 1406 if None != required: 1407 assert (Decimal(required)>=0.) # Make sure required is a number and not negative 1408 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1409 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1410 if not decodeOnly: o.write('\n') 1411 return end 1412 1413 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1414 o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')') 1415 if not decodeOnly: o.write('\n') 1416 1417 return end
1418 1419
1420 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1421 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1422 ''' 1423 Build the decoder for unsigned decimal variables 1424 1425 @type o: file like obj 1426 @param o: where write the code 1427 @type name: str 1428 @param name: field name 1429 @type type: str 1430 @param type: 'udecimal' 1431 @type startindex: int 1432 @param startindex: bit that begins the int(s) 1433 @type numbits: int >= 1 1434 @param numbits: How many bits per unit datum. If -1, then read to the end of the message 1435 @type required: Decimal or None 1436 @param required: If not None, then the value must be set to this. 1437 @type arraylen: int >= 1 1438 @param arraylen: many ints will there be? FIX: handle variable 1439 @type unavailable: Decimal or None 1440 @param unavailable: the default value to use if none given (if not None) 1441 @type bv: str 1442 @param bv: BitVector containing the incoming data 1443 @type dataDict: str 1444 @param dataDict: dictionary in which to place the results 1445 @type decodeOnly: bool 1446 @param decodeOnly: Set to true to only get the code for decoding 1447 @rtype: int 1448 @return: index one past the end of where this read 1449 ''' 1450 assert type=='binary' 1451 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1452 if None==arraylen: arraylen=1 1453 end = startindex+int(numbits)*int(arraylen) 1454 assert arraylen == 1 # FIX... handle arrays 1455 assert (numbits>=1 and numbits <= 1024) or -1==numbits # What is good max? 1456 # FIX: assert not required and not an array an not unavailable 1457 1458 1459 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1460 o.write(bv+'['+str(startindex)+':') 1461 if int(numbits) != -1: o.write(str(end)) # -1 means go to the end of the message 1462 o.write(']') 1463 if not decodeOnly: o.write('\n') 1464 1465 return end
1466 1467 1468 1469 ###################################################################### 1470 # THE REST 1471 ###################################################################### 1472 1473 1474
1475 -def buildTestParamFunc(o,msgET, verbose=False, prefixName=False):
1476 '''Scrape the testvalues to make a basic param 1477 1478 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies? 1479 ''' 1480 name = msgET.attrib['name'] 1481 1482 if prefixName: o.write('def '+name+'TestParams():\n') 1483 else: o.write('def testParams():\n') 1484 o.write("\t'''Return a params file base on the testvalue tags.\n\t@rtype: dict\n\t@return: params based on testvalue tags\n\t'''\n") 1485 o.write('\tparams = {}\n') 1486 for field in msgET.xpath('field'): 1487 name = field.attrib['name'] 1488 type = field.attrib['type'] 1489 if verbose: print 'buildTestParamFunc ...',name,type 1490 val = None 1491 if hasSubTag(field,'testvalue') and hasSubTag(field,'required'): 1492 print 'ERROR: can not have both test value and required tags in the same field' 1493 assert(False) 1494 if hasSubTag(field,'testvalue'): 1495 val = field.xpath('testvalue')[0].text 1496 else: 1497 if not hasSubTag(field,'required'): 1498 sys.exit("ERROR: missing required or testvalue for field: "+name) 1499 val = field.xpath('required')[0].text 1500 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val 1501 o.write('\tparams[\''+name+'\'] = ') 1502 if type=='bool': 1503 if val=='1' or val.lower=='true': val = 'True' 1504 else: val = 'False' 1505 o.write(val) 1506 elif type in ('uint','int','float'): 1507 o.write(val) 1508 elif type in ('decimal','udecimal'): 1509 o.write('Decimal(\''+val+'\')') 1510 1511 elif type in ('aisstr6'): 1512 o.write('\''+val+'\'') 1513 elif type in ('binary'): 1514 o.write('BitVector(bitstring=\''+val+'\')') 1515 else: 1516 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee' 1517 suggestType(name,type) 1518 assert(False) 1519 1520 o.write('\n') 1521 1522 1523 o.write('\n\treturn params\n\n')
1524
1525 -def buildUnitTest(o,msgET, verbose=False, prefixName=False):
1526 ''' 1527 Write the unittests for a message 1528 1529 @param o: open file where resulting code will be written 1530 @param msgET: Element Tree starting at a message node 1531 ''' 1532 assert(msgET.tag=='message') 1533 name = msgET.attrib['name'] 1534 1535 buildTestParamFunc(o,msgET, prefixName=prefixName) 1536 1537 o.write('class Test'+name+'(unittest.TestCase):\n') 1538 o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n") 1539 o.write('\tdef testEncodeDecode(self):\n\n') 1540 if prefixName: 1541 o.write('\t\tparams = '+name+'TestParams()\n') 1542 o.write('\t\tbits = '+name+'Encode(params)\n') 1543 o.write('\t\tr = '+name+'Decode(bits)\n\n') 1544 else: 1545 o.write('\t\tparams = testParams()\n') 1546 o.write('\t\tbits = encode(params)\n') 1547 o.write('\t\tr = decode(bits)\n\n') 1548 1549 o.write('\t\t# Check that each parameter came through ok.\n') 1550 for field in msgET.xpath('field'): 1551 name = field.attrib['name'] 1552 type = field.attrib['type'] 1553 if type in ('bool','uint','int','aisstr6','binary'): 1554 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n') 1555 else: 1556 # float, decimal, udecimal 1557 # FIX: look up the decimal places if decimal 1558 places = '3' 1559 if hasSubTag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text 1560 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
1561 1562 1563
1564 -def buildEncode(o,msgET, verbose=False, prefixName=False):
1565 ''' 1566 Write the encoder/decoder for a message 1567 1568 http://jaynes.colorado.edu/PythonIdioms.html 1569 1570 @param o: open file where resulting code will be written 1571 @param msgET: Element Tree starting at a message node 1572 ''' 1573 assert(msgET.tag=='message') 1574 name = msgET.attrib['name'] # fix rename this variable to avoid hiding later on by field name 1575 1576 print 'Generating encoder for',name # FIX: verbose? 1577 funcName = 'encode' 1578 if prefixName: funcName = name+'Encode' 1579 o.write('def '+funcName+'(params, validate=False):\n') 1580 1581 ######################################## 1582 # doc string 1583 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n") 1584 o.write('\tFields in params:\n') 1585 for field in msgET.xpath('field'): 1586 if verbose: print field.tag,field.attrib['name'] 1587 desc = field[0].text.replace('\n',' ') # get ride of new lines 1588 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1589 if len(field.xpath("required")) == 1: 1590 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1591 1592 o.write('\n') 1593 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n') 1594 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1595 1596 o.write("\t@rtype: BitVector\n") 1597 o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n") 1598 o.write("\t@note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.\n") 1599 o.write("\t'''\n\n") 1600 1601 ######################################## 1602 # Actually build the code 1603 1604 o.write('\tbvList = []\n') 1605 1606 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1607 1608 dynamicArrays = False # Set to true when positioning must be calculated 1609 1610 for field in msgET.xpath('field'): 1611 name = field.attrib['name'] 1612 type = field.attrib['type'] 1613 numbits = int(field.attrib['numberofbits']) 1614 required = None; 1615 if hasSubTag(field,'required'): 1616 required = field.xpath('required')[0].text 1617 #print 'required set for',name,'to',required 1618 unavailable=None; 1619 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1620 arraylen=1 1621 if 'arraylength' in field.attrib: 1622 arraylen=int(field.attrib['arraylength']) 1623 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1624 else: 1625 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 1626 1627 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable) 1628 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable) 1629 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable) 1630 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable) 1631 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable) 1632 elif type=='decimal': 1633 scale = None 1634 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1635 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale) 1636 elif type=='udecimal': 1637 scale = None 1638 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1639 encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale) 1640 elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable) 1641 else: 1642 print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type 1643 suggestType (name,type) 1644 assert False 1645 1646 # o.write('\n\tbv=binary.joinBV(bvList)\n') 1647 # o.write('\n\tbvLen=len(bv)\n') 1648 # o.write('\n\tif bvLen%6!=0:\n') 1649 # o.write('\n\t bv = bv + BitVector(size=bvLen%6) # \n') 1650 # o.write('\n\treturn bv\n\n') 1651 o.write('\n\treturn binary.joinBV(bvList)\n\n')
1652 1653 1654 ###################################################################### 1655 # DECODER ONE PART AT A TIME - if only using a small part, save some cycles! 1656 1657 1658 #, msgDict=None):
1659 -def buildDecodeParts(o,msgET, verbose=False, prefixName=False):
1660 1661 ''' 1662 Write the decoder for a message 1663 1664 @param o: open file where resulting code will be written 1665 @type msgET: elementtree 1666 @param prefixName: if True, put the name of the message on the functions. 1667 @param msgET: Element Tree starting at a message node 1668 @return: None 1669 1670 @todo: FIX: doc strings for each decode! 1671 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 1672 @todo: May want to take a dictionary of already decoded fields to speed things that need prior info 1673 for things like variable length arrays 1674 ''' 1675 1676 assert(msgET.tag=='message') 1677 name = msgET.attrib['name'] 1678 1679 print 'Generating partial decode functions for',name 1680 1681 baseName = name+'Decode' 1682 if not prefixName: baseName = 'decode' 1683 1684 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 1685 1686 for field in msgET.xpath('field'): 1687 name = field.attrib['name'] 1688 type = field.attrib['type'] 1689 1690 o.write('def '+baseName+name+'(bv, validate=False):\n') 1691 # Follow the same convention of decoding into a dict so that code is the same 1692 #o.write('\tr={};') 1693 o.write('\treturn ') 1694 1695 numbits = int(field.attrib['numberofbits']) 1696 required = None; 1697 if hasSubTag(field,'required'): 1698 required = field.xpath('required')[0].text 1699 unavailable=None; 1700 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1701 arraylen=1 1702 if 'arraylength' in field.attrib: 1703 arraylen=int(field.attrib['arraylength']) 1704 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1705 1706 assert None!=startindex 1707 if verbose: print 'startindex',startindex 1708 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1709 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1710 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1711 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1712 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1713 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1714 1715 elif type=='decimal': 1716 scale = None 1717 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1718 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True) 1719 elif type=='udecimal': 1720 scale = None 1721 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1722 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True) 1723 1724 else: 1725 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 1726 suggestType (name,type) 1727 assert False 1728 1729 #o.write('\treturn(r[\''+name+'\'])\n\n') 1730 o.write('\n\n')
1731 1732 1733 ###################################################################### 1734 # DECODER RING 1735
1736 -def buildDecode(o,msgET, verbose=False, prefixName=False):
1737 ''' 1738 Write the decoder for a message 1739 1740 @param o: open file where resulting code will be written 1741 @type msgET: elementtree 1742 @param msgET: Element Tree starting at a message node 1743 @return: None 1744 1745 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 1746 ''' 1747 1748 assert(msgET.tag=='message') 1749 name = msgET.attrib['name'] 1750 1751 print 'Generating decoder for',name 1752 funcName = 'decode' 1753 if prefixName: funcName = name+'Decode' 1754 o.write('def '+funcName+'(bv, validate=False):\n') 1755 1756 ######################################## 1757 # doc string 1758 o.write("\t'''Unpack a "+name+" message \n\n") 1759 o.write('\tFields in params:\n') 1760 for field in msgET.xpath('field'): 1761 desc = field[0].text.replace('\n',' ') # get ride of new lines 1762 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1763 if len(field.xpath("required")) == 1: 1764 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1765 1766 o.write('\n') 1767 o.write('\t@type bv: BitVector\n') 1768 o.write('\t@param bv: Bits defining a message\n') 1769 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1770 1771 o.write("\t@rtype: dict\n") 1772 o.write("\t@return: params\n") 1773 o.write("\t'''\n\n") 1774 1775 1776 o.write('\t#Would be nice to check the bit count here..\n') 1777 o.write('\t#if validate:\n') 1778 o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n') 1779 1780 1781 ######################################## 1782 # Actually build the code 1783 1784 o.write('\tr = {}\n') 1785 1786 1787 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1788 1789 dynamicArrays = False # Set to true when positioning must be calculated 1790 1791 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 1792 1793 for field in msgET.xpath('field'): 1794 name = field.attrib['name'] 1795 type = field.attrib['type'] 1796 numbits = int(field.attrib['numberofbits']) 1797 required = None; 1798 if hasSubTag(field,'required'): 1799 required = field.xpath('required')[0].text 1800 #print 'required set for',name,'to',required 1801 unavailable=None; 1802 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1803 arraylen=1 1804 if 'arraylength' in field.attrib: 1805 arraylen=int(field.attrib['arraylength']) 1806 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1807 else: 1808 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 1809 1810 assert None!=startindex 1811 if verbose: print 'startindex',startindex 1812 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable) 1813 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 1814 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 1815 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable) 1816 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable) 1817 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable) 1818 1819 elif type=='decimal': 1820 scale = None 1821 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1822 #print 'pre call...' 1823 #print ' required: "'+str(required)+'" scale: "'+str(scale)+'" unavail:', unavailable 1824 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale) 1825 elif type=='udecimal': 1826 scale = None 1827 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1828 #print 'pre call...' 1829 #print ' required: "'+str(required)+'" scale: "'+str(scale)+'" unavail:', unavailable 1830 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale) 1831 1832 else: 1833 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 1834 suggestType (name,type) 1835 assert False 1836 1837 1838 if None==startindex: print 'FIX: here. drat. treat me right' 1839 assert None!=startindex 1840 1841 1842 o.write('\treturn r\n\n')
1843 1844 1845 1846 #def getPythonType(aType): 1847 # ''' 1848 # Translate a 1849 # @rtype: str 1850 # @return: name of the python type 1851 # ''' 1852 1853 aisType2pythonType={ 1854 'bool':'Bool', 1855 'uint':'int', 1856 'int':'int', 1857 'udecimal':'Decimal', 1858 'decimal':'Decimal', 1859 'aisstr6':'str', 1860 'binary':'str', 1861 'float':'Float', 1862 } 1863 1864 import copy 1865 aisType2optParseType=copy.deepcopy(aisType2pythonType) 1866 aisType2optParseType['bool']='int' # FIX: make these appear as a flag variable 1867 aisType2optParseType['aisstr6']='string' 1868 aisType2optParseType['aisstr6']='string' 1869 aisType2optParseType['binary']='string' 1870 aisType2optParseType['udecimal']='string' # FIX: Is this a good choice? 1871 aisType2optParseType['decimal']='string' 1872 aisType2optParseType['float']='float' 1873 1874
1875 -def buildOptParse(o,msgET, prefixName=False):
1876 '''Create a function that adds the options to a parse object''' 1877 1878 assert None != msgET 1879 assert msgET.tag=='message' 1880 msgName = msgET.attrib['name'] 1881 1882 prefix='' 1883 if prefixName: prefix=msgName 1884 1885 funcName = 'addMsgOptions' 1886 if prefixName: funcName = msgName + 'AddMsgOptions' 1887 o.write('\ndef '+funcName+'(parser):') 1888 1889 # FIX: write out a doc string 1890 1891 o.write(''' 1892 parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true', 1893 help='decode a "'''+msgName+'''" AIS message') 1894 parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true', 1895 help='encode a "'''+msgName+'''" AIS message') 1896 ''') 1897 1898 #print 'here...', msgName, prefixName 1899 for field in msgET.xpath('field'): 1900 name = field.attrib['name'] 1901 fieldType = field.attrib['type'] 1902 if hasSubTag(field,'required'): 1903 print 'skipping required field ...',name,fieldType 1904 continue 1905 #print 'there',name,fieldType 1906 o.write('\tparser.add_option(\'--') 1907 if prefixName: o.write(msgName+'-') 1908 o.write(name+'-field\', dest=\''+name+'Field\'') 1909 if hasSubTag(field,'unavailable'): 1910 val = field.xpath('unavailable')[0].text 1911 o.write(',default=') 1912 if fieldType in ('uint','int','float'): 1913 o.write(val) 1914 elif fieldType in ('decimal','udecimal'): 1915 o.write('Decimal(\''+val+'\')') 1916 elif fieldType in ('aisstr6','bitvector'): 1917 o.write('\''+val+'\'') 1918 o.write(',metavar=\''+fieldType+'\',type=\''+aisType2optParseType[fieldType]+'\'') 1919 o.write('\n\t\t,help=\'Field parameter value [default: %default]\')\n')
1920 1921 1922
1923 -def buildMain(o, msgET, prefixName=False):
1924 assert None != msgET 1925 assert msgET.tag=='message' 1926 msgName = msgET.attrib['name'] 1927 1928 prefix='' 1929 if prefixName: prefix=msgName 1930 1931 buildOptParse(o, msgET, prefixName) 1932 1933 1934 o.write(''' 1935 ############################################################ 1936 if __name__=='__main__': 1937 1938 from optparse import OptionParser 1939 parser = OptionParser(usage="%prog [options]", 1940 version="%prog "+__version__) 1941 1942 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 1943 help='run the documentation tests') 1944 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true', 1945 help='run the unit tests') 1946 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 1947 help='Make the test output verbose') 1948 1949 # FIX: remove nmea from binary messages. No way to build the whole packet? 1950 # FIX: or build the surrounding msg 8 for a broadcast? 1951 typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message? 1952 parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType' 1953 ,default='nmeapayload' 1954 ,help='What kind of string to expect ('+', '.join(typeChoices)+') [default: %default]') 1955 1956 1957 outputChoices = ('std','html','csv','sql' ''') 1958 # 'xml' - FIX: need to add xml output 1959 if haveLocatableMessage(msgET): o.write(', \'kml\',\'kml-full\'') 1960 o.write(''') 1961 parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType' 1962 ,default='std' 1963 ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]') 1964 1965 parser.add_option('-o','--output',dest='outputFileName',default=None, 1966 help='Name of the python file to write [default: stdout]') 1967 1968 parser.add_option('-f','--fields',dest='fieldList',default=None, action='append', 1969 choices=fieldList, 1970 help='Which fields to include in the output. Currently only for csv output [default: all]') 1971 1972 parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true', 1973 help='Print the field name for csv') 1974 1975 parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true', 1976 help='Print out an sql create command for the table.') 1977 1978 ''') 1979 1980 1981 o.write('''\taddMsgOptions(parser)\n''') 1982 1983 o.write(''' 1984 (options,args) = parser.parse_args() 1985 success=True 1986 1987 if options.doctest: 1988 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 1989 sys.argv= [sys.argv[0]] 1990 if options.verbose: sys.argv.append('-v') 1991 import doctest 1992 numfail,numtests=doctest.testmod() 1993 if numfail==0: print 'ok' 1994 else: 1995 print 'FAILED' 1996 success=False 1997 1998 if not success: sys.exit('Something Failed') 1999 del success # Hide success from epydoc 2000 2001 if options.unittest: 2002 sys.argv = [sys.argv[0]] 2003 if options.verbose: sys.argv.append('-v') 2004 unittest.main() 2005 2006 outfile = sys.stdout 2007 if None!=options.outputFileName: 2008 outfile = file(options.outputFileName,'w') 2009 2010 ''') 2011 2012 2013 2014 ############################## 2015 # encode 2016 ############################## 2017 # FIX: make everything tabs. Grr. 2018 o.write('\n\tif options.doEncode:\n') 2019 o.write('\t\t# First make sure all non required options are specified\n') 2020 for field in msgET.xpath('field'): 2021 name = field.attrib['name'] 2022 fieldType = field.attrib['type'] 2023 varName = prefix+name+'Field' 2024 if not hasSubTag(field,'required'): 2025 o.write('\t\tif None==options.'+varName+': parser.error("missing value for '+varName+'")\n') 2026 2027 # Build dict 2028 o.write('\t\tmsgDict={\n') 2029 for field in msgET.xpath('field'): 2030 name = field.attrib['name'] 2031 varName = prefix+name+'Field' 2032 if hasSubTag(field,'required'): 2033 o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n') 2034 else: 2035 o.write('\t\t\t\''+name+'\': options.'+varName+',\n') 2036 o.write('\t\t}\n') 2037 2038 encodeFunction = 'encode' 2039 if prefixName: encodeFunction = msgName+'Encode' 2040 o.write(''' 2041 bits = '''+encodeFunction+'''(msgDict) 2042 if 'binary'==options.ioType: print str(bits) 2043 elif 'nmeapayload'==options.ioType: 2044 # FIX: figure out if this might be necessary at compile time 2045 print "bitLen",len(bits) 2046 bitLen=len(bits) 2047 if bitLen%6!=0: 2048 bits = bits + BitVector(size=(6 - (bitLen%6))) # Pad out to multiple of 6 2049 print "result:",binary.bitvectoais6(bits)[0] 2050 2051 2052 # FIX: Do not emit this option for the binary message payloads. Does not make sense. 2053 elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability") 2054 else: sys.exit('ERROR: unknown ioType. Help!') 2055 ''') 2056 2057 2058 ############################## 2059 # decode all 2060 ############################## 2061 decodeFunction = 'decode' 2062 printFields='printFields' 2063 if prefixName: 2064 decodeFunction = msgName+'Decode' 2065 printFields = msgName+'PrintFields' 2066 o.write(''' 2067 2068 if options.sqlCreate: 2069 sqlCreateStr(outfile,options.fieldList) 2070 2071 if options.printCsvfieldList: 2072 # Make a csv separated list of fields that will be displayed for csv 2073 if None == options.fieldList: options.fieldList = fieldList 2074 import StringIO 2075 buf = StringIO.StringIO() 2076 for field in options.fieldList: 2077 buf.write(field+',') 2078 result = buf.getvalue() 2079 if result[-1] == ',': print result[:-1] 2080 else: print result 2081 2082 if options.doDecode: 2083 for msg in args: 2084 bv = None 2085 if 'binary' == options.ioType: bv = BitVector(bitstring=msg) 2086 elif 'nmeapayload'== options.ioType: bv = binary.ais6tobitvec(msg) 2087 elif 'nmea' == options.ioType: bv = binary.ais6tobitvec(msg.split(',')[5]) 2088 else: sys.exit('ERROR: unknown ioType. Help!') 2089 2090 '''+printFields+'''('''+decodeFunction+'''(bv),out=outfile,format=options.outputType,fieldList=options.fieldList) 2091 ''')
2092 2093 2094 2095 2096 ###################################################################### 2097 if __name__=='__main__': 2098 from optparse import OptionParser 2099 parser = OptionParser(usage="%prog [options]", 2100 version="%prog "+__version__) 2101 2102 parser.add_option('-o','--output',dest='outputFileName',default=None, 2103 help='Name of the python file to write') 2104 # help='Name of the python file to write [default: stdout]') 2105 2106 parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None, 2107 help='XML definition file for the msg to use') 2108 # help='XML definition file for the msg to use [default: stdin]') 2109 2110 # parser.add_option('-m','--message',dest='message',default=None, 2111 # help='Which message to write code for [default: all]') 2112 2113 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 2114 help='run the documentation tests') 2115 2116 parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true', 2117 help='put the field name in front of all function names.' 2118 +' Allows multiple messages in one file') 2119 2120 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 2121 help='run the tests run in verbose mode') 2122 2123 (options,args) = parser.parse_args() 2124 2125 success=True 2126 2127 if options.doctest: 2128 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 2129 argvOrig = sys.argv 2130 sys.argv= [sys.argv[0]] 2131 if options.verbose: sys.argv.append('-v') 2132 import doctest 2133 numfail,numtests=doctest.testmod() 2134 if numfail==0: print 'ok' 2135 else: 2136 print 'FAILED' 2137 success=False 2138 sys.argv = argvOrig # Restore the original args 2139 del argvOrig # hide from epydoc 2140 sys.exit() # FIX: Will this exit success? 2141 2142 #infile=sys.stdin 2143 #if options.xmlFileName: infile = file(options.xmlFileName,'r') 2144 2145 #outfile=sys.stdout 2146 #if options.outputFile: outfile = file(options.xmlDefinition,'w') 2147 2148 # FIX: down the road, it would be good to allow either filenames of std{in/out} 2149 2150 if None==options.xmlFileName: 2151 sys.exit('ERROR: must specify an xml definition file.') 2152 if None==options.outputFileName: 2153 sys.exit('ERROR: must specify an python file to write to.') 2154 generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix, verbose=options.verbose) 2155 2156 print '\nrecommend running pychecker like this:' 2157 print ' pychecker -q',options.outputFileName 2158