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 # Should this really default to true for the uscg fields? 526 o.write('def '+createFuncName+'''Str(outfile=sys.stdout, fields=None, extraFields=None, addCoastGuardFields=True): 527 \'\'\' 528 Return the SQL CREATE command for this message type 529 @param outfile: file like object to print to. 530 @param fields: which fields to put in the create. Defaults to all. 531 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 532 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 533 @type addCoastGuardFields: bool 534 @return: sql create string 535 @rtype: str 536 537 @see: sqlCreate 538 \'\'\' 539 outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields))) 540 541 ''') 542 543 o.write('def '+createFuncName+'''(fields=None, extraFields=None, addCoastGuardFields=True): 544 \'\'\' 545 Return the sqlhelp object to create the table. 546 547 @param fields: which fields to put in the create. Defaults to all. 548 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 549 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 550 @type addCoastGuardFields: bool 551 @return: An object that can be used to generate a return 552 @rtype: sqlhelp.create 553 \'\'\' 554 if None == fields: fields = fieldList 555 import sqlhelp 556 ''') 557 558 o.write('\tc = sqlhelp.create(\''+msgName+'\')\n') 559 560 for field in msgET.xpath('field'): 561 fieldName = field.attrib['name'] 562 fieldType = field.attrib['type'] 563 564 o.write('\tif \''+fieldName+'\' in fields: c.add') 565 # FIX: add the ADD command here. 566 if fieldType in ('int','uint'): o.write('Int (\''+fieldName+'\')') 567 elif fieldType in ('float' ): o.write('Real(\''+fieldName+'\')') 568 elif fieldType == 'bool': o.write('Bool(\''+fieldName+'\')') 569 elif fieldType in ('decimal','udecimal'): 570 if not hasSubTag(field,'decimalplaces'): 571 print '\n ** ERROR: missing decimalplaces field for ',fieldName,'\n' 572 assert (False) 573 scaleSQL = int(field.xpath('decimalplaces')[0].text) # number of decimal places 574 numBits = int(field.attrib['numberofbits']) 575 scaleVal = float(field.xpath('scale')[0].text) 576 precision = scaleSQL + len(str(int((2**numBits)/scaleVal))) 577 #precision += 1 # FIX: do I really need this safety factor 578 o.write('Decimal(\''+fieldName+'\','+str(precision)+','+str(scaleSQL)+')') 579 elif fieldType == 'binary': 580 numBits = int(field.attrib['numberofbits']) 581 if -1 == numBits: numBits=1024 # FIX: what is a good maximum for AIS? 582 o.write('BitVarying(\''+fieldName+'\','+str(numBits)+')') 583 elif fieldType == 'aisstr6': 584 arrayLength = int(field.attrib['arraylength']) 585 o.write('VarChar(\''+fieldName+'\','+str(arrayLength)+')') 586 else: 587 print '\n\n *** Unknown type in SQL code generation for field',fieldName+':',fieldType,'\n\n' 588 assert(False) 589 o.write('\n') 590 591 o.write(''' 592 if addCoastGuardFields: 593 # c.addInt('cg_rssi') # Relative signal strength indicator 594 # c.addInt('cg_d') # dBm receive strength 595 # c.addInt('cg_T') # Receive timestamp from the AIS equipment 596 # c.addInt('cg_S') # Slot received in 597 # c.addVarChar('cg_x',10) # Idonno 598 c.addVarChar('cg_r',15) # Receiver station ID - should usually be an MMSI, but sometimes is a string 599 c.addInt('cg_timestamp') # UTC seconds since the epoch 600 # FIX: maybe an actually time field? 601 ''') 602 603 o.write('\n\treturn c\n\n') 604 605 606 # FIX: function name 607 o.write('''def '''+insertFuncName+'''Str(params, outfile=sys.stdout, extraParams=None): 608 \'\'\' 609 Return the SQL CREATE command for this message type 610 @param params: dictionary of values keyed by field name 611 @param outfile: file like object to print to. 612 @param extraParams: A sequence of tuples containing (name,sql type) for additional fields 613 @return: sql create string 614 @rtype: str 615 616 @see: sqlCreate 617 \'\'\' 618 outfile.write(str(sqlInsert(params,extraParams))) 619 620 621 ''') 622 623 # FIX: function name 624 o.write('''def '''+insertFuncName+'''(params,extraParams=None): 625 \'\'\' 626 Give the SQL insert statement 627 @param params: dict keyed by field name of values 628 @param extraParams: any extra fields that you have created beyond the normal ais message fields 629 @rtype: sqlhelp.insert 630 @return: insert class instance 631 @todo: allow optional type checking of params? 632 @warning: this will take invalid keys happily and do what??? 633 \'\'\' 634 import sqlhelp 635 i = sqlhelp.insert(\'''' + msgName + '''\') 636 for key in params: 637 #i.add(key,params[key]) 638 if type(params[key])==Decimal: i.add(key,float(params[key])) 639 else: i.add(key,params[key]) 640 if None != extraParams: 641 for key in extraParams: 642 i.add(key,extraParams[key]) 643 644 return i 645 ''')
646 647 648 649 ###################################################################### 650 # Build Lookup tables for enumerated int or uint fields 651 ###################################################################### 652 653
654 -def buildLUT(o,msgET, verbose=False, prefixName=False):
655 ''' 656 Write lookup tables for enumerated types (uint or int, maybe bool too). 657 658 @todo: FIX: what to do about multiple entries with the same text? Need to ban that kind of thing 659 @todo: Make doc strings for each LUT. 660 661 @param o: open file where resulting code will be written 662 @param msgET: Element Tree starting at a message node 663 ''' 664 assert(msgET.tag=='message') 665 msgname = msgET.attrib['name'] 666 667 print 'Generating lookup tables for',msgname # FIX: verbose? 668 669 for field in msgET.xpath('field'): 670 name = field.attrib['name'] 671 if not hasSubTag(field,'lookuptable'): continue 672 lut = field.xpath('lookuptable')[0] 673 674 lutName = name 675 if prefixName: lutName = msgname+name.capitalize() 676 677 o.write(lutName+'EncodeLut = {\n') 678 for entry in lut.xpath('entry'): 679 o.write('\t\''+entry.text+'\':\''+entry.attrib['key']+'\',\n') 680 o.write('\t} #'+lutName+'EncodeLut\n') 681 o.write('\n') 682 683 # FIX: make doc string for LUT here 684 685 o.write(lutName+'DecodeLut = {\n') 686 for entry in lut.xpath('entry'): 687 o.write('\t\''+entry.attrib['key']+'\':\''+entry.text+'\',\n') 688 o.write('\t} # '+lutName+'EncodeLut\n') 689 o.write('\n')
690 691 # FIX: make doc string for LUT here 692 693 694 695 696 ###################################################################### 697 # ENCODERS 698 ######################################################################
699 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
700 ''' 701 Build the encoder for boolean variables 702 @type o: file like obj 703 @param o: where write the code 704 @type name: str 705 @param name: field name 706 @type type: str 707 @param type: bool, etc. 708 @type numbits: int = 1 709 @param numbits: How many bits per unit datum (must be 1 for bools) 710 @type required: bool or None 711 @param required: If not None, then the value must be set to this. 712 @type arraylen: int >= 1 713 @param arraylen: many bools will there be? FIX: handle variable 714 @type unavailable: bool or None 715 @param unavailable: the default value to use if none given (if not None) 716 @return: None 717 ''' 718 719 if verbose: print 'bool encode',name,': unvail=',unavailable 720 721 assert type.lower()=='bool' 722 assert numbits==1 723 if arraylen != 1: assert False # FIX... handle arrays 724 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n') 725 if None != required: 726 assert type(required)==bool 727 if required: o.write('\t\tbvList.append(TrueBV)\n') 728 else: o.write('\t\tbvList.append(FalseBV)\n') 729 if verbose: o.write('\n') 730 return 731 732 if None==unavailable: 733 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n') 734 o.write('\telse: bvList.append(FalseBV)\n') 735 else: # Have a default value that can be filled in 736 assert type(unavailable)==bool 737 o.write("\tif '"+name+"' in params:\n") 738 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n') 739 o.write('\t\telse: bvList.append(FalseBV)\n') 740 o.write('\telse:\n') 741 if unavailable: o.write('\t\tbvList.append(TrueBV)\n') 742 else: o.write('\t\tbvList.append(FalseBV)\n') 743 if verbose: o.write('\n')
744
745 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
746 ''' 747 Build the encoder for unsigned integer variables 748 749 @type o: file like obj 750 @param o: where write the code 751 @type name: str 752 @param name: field name 753 @type type: str 754 @param type: uint, bool, etc. 755 @type numbits: int >= 1 756 @param numbits: How many bits per unit datum (must be 1..32) 757 @type required: bool or None 758 @param required: If not None, then the value must be set to this. 759 @type arraylen: int >= 1 760 @param arraylen: many unsigned ints will there be? FIX: handle variable 761 @type unavailable: bool or None 762 @param unavailable: the default value to use if none given (if not None) 763 @return: None 764 ''' 765 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 766 767 assert type=='uint' 768 assert numbits>=1 and numbits<=32 769 if arraylen != 1: assert False # FIX... handle arrays 770 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 771 772 if None != required: 773 if verbose: print ' required:',required 774 required=int(required) 775 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n') 776 if verbose: o.write('\n') 777 return 778 779 if None==unavailable: 780 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n') 781 else: # Have a default value that can be filled in 782 #assert type(unavailable)== 783 int(unavailable) # Make sure unavailable is a number object 784 o.write("\tif '"+name+"' in params:\n") 785 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n') 786 o.write('\telse:\n') 787 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n') 788 789 if verbose: o.write('\n')
790 791
792 -def encodeFloat(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
793 ''' 794 Build the encoder for IEEE float variables 795 796 @type o: file like obj 797 @param o: where write the code 798 @type name: str 799 @param name: field name 800 @type fieldType: str 801 @param fieldType: uint, bool, etc. 802 @type numbits: int >= 1 803 @param numbits: How many bits per unit datum (must be 1..32) 804 @type required: bool or None 805 @param required: If not None, then the value must be set to this. 806 @type arraylen: int >= 1 807 @param arraylen: many unsigned ints will there be? FIX: handle variable 808 @type unavailable: bool or None 809 @param unavailable: the default value to use if none given (if not None) 810 @return: None 811 ''' 812 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 813 814 assert numbits==32 # Force by the IEEE spec 815 if arraylen != 1: assert False # FIX... handle arrays 816 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 817 818 if None != required: 819 if verbose: print ' required:',required 820 required=int(required) 821 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n') 822 if verbose: o.write('\n') 823 return 824 825 if None==unavailable: 826 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n') 827 else: # Have a default value that can be filled in 828 int(unavailable) # Make sure unavailable is a number object 829 o.write("\tif '"+name+"' in params:\n") 830 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n') 831 o.write('\telse:\n') 832 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n') 833 834 if verbose: o.write('\n')
835 836
837 -def encodeAisstr6(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
838 ''' 839 Build the encoder for aisstr6 variables. Generally are arrays. 840 @bug: do we need to optionally check for a valid string? 841 842 @type o: file like obj 843 @param o: where write the code 844 @type name: str 845 @param name: field name 846 @type fieldType: str 847 @param fieldType: uint, bool, etc. 848 @type numbits: int >= 1 849 @param numbits: How many bits per unit datum (must be 1..32) 850 @type required: bool or None 851 @param required: If not None, then the value must be set to this. 852 @type arraylen: int >= 1 853 @param arraylen: many unsigned ints will there be? FIX: handle variable 854 @type unavailable: bool or None 855 @param unavailable: the default value to use if none given (if not None) 856 @return: None 857 ''' 858 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 859 totLen = str(numbits*arraylen) 860 assert numbits==6 # Each character must be 6 bits 861 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 862 863 if None != required: 864 if verbose: print ' required:',required 865 required=int(required) 866 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+totLen+'))\n') 867 if verbose: o.write('\n') 868 return 869 870 if None==unavailable: 871 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 872 else: # Have a default value that can be filled in 873 o.write("\tif '"+name+"' in params:\n") 874 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 875 o.write('\telse:\n') 876 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+totLen+'))\n') 877 878 if verbose: o.write('\n')
879 880
881 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
882 ''' 883 Build the encoder for signed integer variables 884 885 @type o: file like obj 886 @param o: where write the code 887 @type name: str 888 @param name: field name 889 @type type: str 890 @param type: uint, bool, etc. 891 @type numbits: int >= 1 892 @param numbits: How many bits per unit datum (must be 1..32) 893 @type required: bool or None 894 @param required: If not None, then the value must be set to this. 895 @type arraylen: int >= 1 896 @param arraylen: many signed ints will there be? FIX: handle variable 897 @type unavailable: number or None 898 @param unavailable: the default value to use if none given (if not None) 899 @return: None 900 ''' 901 if verbose: print ' encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 902 903 assert numbits>=1 and numbits<=32 904 if arraylen != 1: assert False # FIX... handle arrays 905 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 906 907 if None != required: 908 if verbose: print ' required:',required 909 required=int(required) 910 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n') 911 if verbose: o.write('\n') 912 return 913 914 915 if None==unavailable: 916 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n') 917 else: # Have a default value that can be filled in 918 #assert type(unavailable)== 919 int(unavailable) # Make sure unavailable is a number object 920 o.write("\tif '"+name+"' in params:\n") 921 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n') 922 o.write('\telse:\n') 923 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n') 924 925 if verbose: o.write('\n')
926 927 928 929 # FIX: Ummm... why am I passing the type? I guess it makes the one print statement easier
930 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
931 ''' 932 Build the encoder for signed decimal variables 933 934 @type o: file like obj 935 @param o: where write the code 936 @type name: str 937 @param name: field name 938 @type type: str 939 @param type: decimal 940 @type numbits: int >= 1 941 @param numbits: How many bits per unit datum (must be 1..32) 942 @type required: bool or None 943 @param required: If not None, then the value must be set to this. 944 @type arraylen: int >= 1 945 @param arraylen: many decimals will there be? FIX: handle variable 946 @type unavailable: Decimal or None 947 @param unavailable: the default value to use if none given (if not None) 948 @return: None 949 ''' 950 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 951 952 assert numbits>=1 and numbits<=32 953 if arraylen != 1: assert False # FIX... handle arrays 954 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 955 956 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 957 if None == scale: 958 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 959 print 'Beware canadians bearing travel videos' 960 scale='1' 961 962 if None != required: 963 if verbose: print ' required:',required 964 required=int(required) 965 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n') 966 if verbose: o.write('\n') 967 return 968 969 # FIX: can I get rid of the Decimal around params? 970 if None==unavailable: 971 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 972 else: # Have a default value that can be filled in 973 o.write("\tif '"+name+"' in params:\n") 974 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 975 o.write('\telse:\n') 976 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n') 977 978 if verbose: o.write('\n')
979 980 981
982 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
983 ''' 984 Build the encoder for signed decimal variables 985 986 @type o: file like obj 987 @param o: where write the code 988 @type name: str 989 @param name: field name 990 @type type: str 991 @param type: decimal 992 @type numbits: int >= 1 993 @param numbits: How many bits per unit datum (must be 1..32) 994 @type required: bool or None 995 @param required: If not None, then the value must be set to this. 996 @type arraylen: int >= 1 997 @param arraylen: many decimals will there be? FIX: handle variable 998 @type unavailable: Decimal or None 999 @param unavailable: the default value to use if none given (if not None) 1000 @return: None 1001 ''' 1002 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1003 assert type=='udecimal' 1004 assert numbits>=1 and numbits<=32 1005 if arraylen != 1: assert False # FIX... handle arrays 1006 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1007 1008 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 1009 if None == scale: 1010 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 1011 print 'Beware canadians bearing travel videos' 1012 scale='1' 1013 1014 if None != required: 1015 if verbose: print ' required:',required 1016 required=int(required) 1017 assert(0<=required) 1018 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n') 1019 if verbose: o.write('\n') 1020 return 1021 1022 # FIX: can I get rid of the Decimal around params? 1023 if None==unavailable: 1024 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1025 else: # Have a default value that can be filled in 1026 o.write("\tif '"+name+"' in params:\n") 1027 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1028 o.write('\telse:\n') 1029 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n') 1030 1031 if verbose: o.write('\n')
1032 1033 1034
1035 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1036 ''' 1037 Build the encoder for binary variables. This is just a pass through. 1038 This is used for the ais binary message wrapper (e.g. msg 8). Do 1039 not use it within binary messages. 1040 1041 @type o: file like obj 1042 @param o: where write the code 1043 @type name: str 1044 @param name: field name 1045 @type type: str 1046 @param type: binary 1047 @type numbits: int >= 1 1048 @param numbits: How many bits per unit datum (must be 1..1024 or so) 1049 @type required: bool or None 1050 @param required: If not None, then the value must be set to this. 1051 @type arraylen: int >= 1 1052 @param arraylen: many decimals will there be? FIX: handle variable 1053 @type unavailable: Decimal or None 1054 @param unavailable: the default value to use if none given (if not None) 1055 @return: None 1056 ''' 1057 if verbose: print ' encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable 1058 assert type=='binary' 1059 assert (numbits>=1 and numbits<=1024) or numbits==-1 1060 assert (None == required) # don't allow this 1061 assert (None == unavailable) # don't allow this 1062 1063 if arraylen != 1: assert False # Do not handle arrays. Arrays of bits is just not necessary. 1064 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1065 1066 # FIX: can I get rid of the Decimal around params? 1067 o.write('\tbvList.append(params[\''+name+'\'])\n') # Just pass it through 1068 1069 if verbose: o.write('\n')
1070 1071 1072 ###################################################################### 1073 # DECODERS 1074 ###################################################################### 1075
1076 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1077 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1078 ''' 1079 Build the decoder for boolean variables 1080 1081 @type o: file like obj 1082 @param o: where write the code 1083 @type name: str 1084 @param name: field name 1085 @type type: str 1086 @param type: uint, bool, etc. 1087 @type startindex: int 1088 @param startindex: bit that begins the bool(s) 1089 @type numbits: int = 1 1090 @param numbits: How many bits per unit datum (must be 1 for bools) 1091 @type required: bool or None 1092 @param required: If not None, then the value must be set to this. 1093 @type arraylen: int >= 1 1094 @param arraylen: many bools will there be? FIX: handle variable 1095 @type unavailable: bool or None 1096 @param unavailable: the default value to use if none given (if not None) 1097 @type bv: str 1098 @param bv: BitVector containing the incoming data 1099 @type dataDict: str 1100 @param dataDict: dictionary in which to place the results 1101 @type decodeOnly: bool 1102 @param decodeOnly: Set to true to only get the code for decoding 1103 @rtype: int 1104 @return: index one past the end of where this read 1105 ''' 1106 assert(type=='bool') 1107 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1108 #int(startindex); int(numbits) # Make sure it is a number 1109 assert numbits==1 1110 assert arraylen == 1 # FIX... handle arrays 1111 1112 if None != required: 1113 assert type(required)==bool 1114 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1115 if required: o.write('True\n') 1116 else: o.write('False\n') 1117 if not decodeOnly: o.write('\n') 1118 return int(startindex)+int(numbits) 1119 1120 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1121 o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))') 1122 if not decodeOnly: o.write('\n') 1123 1124 return int(startindex)+int(numbits)
1125 1126
1127 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1128 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1129 ''' 1130 Build the decoder for unsigned integer variables 1131 1132 @type o: file like obj 1133 @param o: where write the code 1134 @type name: str 1135 @param name: field name 1136 @type type: str 1137 @param type: uint, etc. 1138 @type startindex: int 1139 @param startindex: bit that begins the uint(s) 1140 @type numbits: int >= 1 1141 @param numbits: How many bits per unit datum 1142 @type required: int or None 1143 @param required: If not None, then the value must be set to this. 1144 @type arraylen: int >= 1 1145 @param arraylen: many ints will there be? FIX: handle variable 1146 @type unavailable: int or None 1147 @param unavailable: the default value to use if none given (if not None) 1148 @type bv: str 1149 @param bv: BitVector containing the incoming data 1150 @type dataDict: str 1151 @param dataDict: dictionary in which to place the results 1152 @type decodeOnly: bool 1153 @param decodeOnly: Set to true to only get the code for decoding 1154 @rtype: int 1155 @return: index one past the end of where this read 1156 ''' 1157 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1158 if None==arraylen: arraylen=1 1159 assert arraylen == 1 # FIX... handle arrays 1160 assert numbits>=1 1161 if not decodeOnly: verbose=False 1162 1163 if None != required: 1164 int(required) # Make sure required is a number 1165 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1166 o.write(str(required)) 1167 if not decodeOnly: o.write('\n') 1168 return startindex+numbits 1169 1170 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1171 o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])') 1172 if not decodeOnly: o.write('\n') 1173 if verbose: o.write('\n') 1174 1175 return startindex+numbits
1176 1177
1178 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1179 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1180 ''' 1181 Build the decoder for unsigned integer variables 1182 1183 @type o: file like obj 1184 @param o: where write the code 1185 @type name: str 1186 @param name: field name 1187 @type type: str 1188 @param type: int 1189 @type startindex: int 1190 @param startindex: bit that begins the int(s) 1191 @type numbits: int >= 1 1192 @param numbits: How many bits per unit datum 1193 @type required: int or None 1194 @param required: If not None, then the value must be set to this. 1195 @type arraylen: int >= 1 1196 @param arraylen: many ints will there be? FIX: handle variable 1197 @type unavailable: int or None 1198 @param unavailable: the default value to use if none given (if not None) 1199 @type bv: str 1200 @param bv: BitVector containing the incoming data 1201 @type dataDict: str 1202 @param dataDict: dictionary in which to place the results 1203 @type decodeOnly: bool 1204 @param decodeOnly: Set to true to only get the code for decoding 1205 @rtype: int 1206 @return: index one past the end of where this read 1207 ''' 1208 assert type=='int' 1209 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1210 if None==arraylen: arraylen=1 1211 end = startindex+int(numbits)*int(arraylen) 1212 assert arraylen == 1 # FIX... handle arrays 1213 assert numbits>=1 1214 1215 if None != required: 1216 int(required) # Make sure required is a number 1217 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1218 o.write(str(required)) 1219 if not decodeOnly: o.write('\n') 1220 return end 1221 1222 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1223 o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])') 1224 if not decodeOnly: o.write('\n') 1225 if verbose: o.write('\n') 1226 1227 return end
1228
1229 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1230 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1231 ''' 1232 Build the decoder for IEEE float variables 1233 1234 @type o: file like obj 1235 @param o: where write the code 1236 @type name: str 1237 @param name: field name 1238 @type type: str 1239 @param type: int 1240 @type startindex: int 1241 @param startindex: bit that begins the int(s) 1242 @type numbits: int >= 1 1243 @param numbits: How many bits per unit datum 1244 @type required: float or None 1245 @param required: If not None, then the value must be set to this. 1246 @type arraylen: int >= 1 1247 @param arraylen: many ints will there be? FIX: handle variable 1248 @type unavailable: float or None 1249 @param unavailable: the default value to use if none given (if not None) 1250 @type bv: str 1251 @param bv: BitVector containing the incoming data 1252 @type dataDict: str 1253 @param dataDict: dictionary in which to place the results 1254 @type decodeOnly: bool 1255 @param decodeOnly: Set to true to only get the code for decoding 1256 @rtype: int 1257 @return: index one past the end of where this read 1258 ''' 1259 assert type=='float' 1260 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1261 if None==arraylen: arraylen=1 1262 end = startindex+int(numbits)*int(arraylen) 1263 assert arraylen == 1 # FIX... handle arrays 1264 assert numbits>=1 1265 1266 if None != required: 1267 float(required) # Make sure required is a number 1268 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1269 o.write(str(required)) 1270 if not decodeOnly: o.write('\n') 1271 if verbose: o.write('\n') 1272 return end 1273 1274 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1275 o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])') 1276 if not decodeOnly: o.write('\n') 1277 1278 return end
1279 1280
1281 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1282 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1283 ''' 1284 Build the decoder for aisstr6 variables. Generally arrays. 1285 @bug: FIX: validate strings?? 1286 @type o: file like obj 1287 @param o: where write the code 1288 @type name: str 1289 @param name: field name 1290 @type type: str 1291 @param type: 'aisstr6' 1292 @type startindex: int 1293 @param startindex: bit that begins the int(s) 1294 @type numbits: int >= 1 1295 @param numbits: How many bits per unit datum 1296 @type required: restricted str or None 1297 @param required: If not None, then the value must be set to this. 1298 @type arraylen: int >= 1 1299 @param arraylen: many ints will there be? FIX: handle variable 1300 @type unavailable: restricted str or None 1301 @param unavailable: the default value to use if none given (if not None) 1302 @type bv: str 1303 @param bv: BitVector containing the incoming data 1304 @type dataDict: str 1305 @param dataDict: dictionary in which to place the results 1306 @type decodeOnly: bool 1307 @param decodeOnly: Set to true to only get the code for decoding 1308 @rtype: int 1309 @return: index one past the end of where this read 1310 ''' 1311 assert type=='aisstr6' 1312 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1313 if None==arraylen: arraylen=1 1314 end = startindex+int(numbits)*int(arraylen) 1315 assert arraylen >= 1 # FIX... handle arrays 1316 assert numbits>=1 1317 1318 if None != required: 1319 float(required) # Make sure required is a number 1320 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1321 o.write(required) 1322 if not decodeOnly: o.write('\n') 1323 return end 1324 1325 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1326 o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])') 1327 if not decodeOnly: o.write('\n') 1328 1329 return end
1330 1331
1332 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1333 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1334 ''' 1335 Build the decoder for signed decimal variables 1336 1337 @type o: file like obj 1338 @param o: where write the code 1339 @type name: str 1340 @param name: field name 1341 @type type: str 1342 @param type: 'decimal' 1343 @type startindex: int 1344 @param startindex: bit that begins the int(s) 1345 @type numbits: int >= 1 1346 @param numbits: How many bits per unit datum 1347 @type required: Decimal or None 1348 @param required: If not None, then the value must be set to this. 1349 @type arraylen: int >= 1 1350 @param arraylen: many ints will there be? FIX: handle variable 1351 @type unavailable: Decimal or None 1352 @param unavailable: the default value to use if none given (if not None) 1353 @type bv: str 1354 @param bv: BitVector containing the incoming data 1355 @type dataDict: str 1356 @param dataDict: dictionary in which to place the results 1357 @type decodeOnly: bool 1358 @param decodeOnly: Set to true to only get the code for decoding 1359 @rtype: int 1360 @return: index one past the end of where this read 1361 ''' 1362 assert type=='decimal' 1363 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1364 if None==arraylen: arraylen=1 1365 end = startindex+int(numbits)*int(arraylen) 1366 assert arraylen == 1 # FIX... handle arrays 1367 assert numbits>=1 and numbits <= 32 1368 1369 if None == scale: scale='1' # Warning about this was in the encode section 1370 1371 if None != required: 1372 Decimal(required) # Make sure required is a number 1373 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1374 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1375 if not decodeOnly: o.write('\n') 1376 return end 1377 1378 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1379 o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')') 1380 if not decodeOnly: o.write('\n') 1381 1382 return end
1383 1384 1385 1386
1387 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1388 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1389 ''' 1390 Build the decoder for unsigned decimal variables 1391 1392 @type o: file like obj 1393 @param o: where write the code 1394 @type name: str 1395 @param name: field name 1396 @type type: str 1397 @param type: 'udecimal' 1398 @type startindex: int 1399 @param startindex: bit that begins the int(s) 1400 @type numbits: int >= 1 1401 @param numbits: How many bits per unit datum 1402 @type required: Decimal or None 1403 @param required: If not None, then the value must be set to this. 1404 @type arraylen: int >= 1 1405 @param arraylen: many ints will there be? FIX: handle variable 1406 @type unavailable: Decimal or None 1407 @param unavailable: the default value to use if none given (if not None) 1408 @type bv: str 1409 @param bv: BitVector containing the incoming data 1410 @type dataDict: str 1411 @param dataDict: dictionary in which to place the results 1412 @type decodeOnly: bool 1413 @param decodeOnly: Set to true to only get the code for decoding 1414 @rtype: int 1415 @return: index one past the end of where this read 1416 ''' 1417 assert type=='udecimal' 1418 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1419 if None==arraylen: arraylen=1 1420 end = startindex+int(numbits)*int(arraylen) 1421 assert arraylen == 1 # FIX... handle arrays 1422 assert numbits>=1 and numbits <= 32 1423 1424 if None == scale: scale='1' # Warning about this was in the encode section 1425 1426 if None != required: 1427 assert (Decimal(required)>=0.) # Make sure required is a number and not negative 1428 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1429 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1430 if not decodeOnly: o.write('\n') 1431 return end 1432 1433 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1434 o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')') 1435 if not decodeOnly: o.write('\n') 1436 1437 return end
1438 1439
1440 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1441 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1442 ''' 1443 Build the decoder for unsigned decimal variables 1444 1445 @type o: file like obj 1446 @param o: where write the code 1447 @type name: str 1448 @param name: field name 1449 @type type: str 1450 @param type: 'udecimal' 1451 @type startindex: int 1452 @param startindex: bit that begins the int(s) 1453 @type numbits: int >= 1 1454 @param numbits: How many bits per unit datum. If -1, then read to the end of the message 1455 @type required: Decimal or None 1456 @param required: If not None, then the value must be set to this. 1457 @type arraylen: int >= 1 1458 @param arraylen: many ints will there be? FIX: handle variable 1459 @type unavailable: Decimal or None 1460 @param unavailable: the default value to use if none given (if not None) 1461 @type bv: str 1462 @param bv: BitVector containing the incoming data 1463 @type dataDict: str 1464 @param dataDict: dictionary in which to place the results 1465 @type decodeOnly: bool 1466 @param decodeOnly: Set to true to only get the code for decoding 1467 @rtype: int 1468 @return: index one past the end of where this read 1469 ''' 1470 assert type=='binary' 1471 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1472 if None==arraylen: arraylen=1 1473 end = startindex+int(numbits)*int(arraylen) 1474 assert arraylen == 1 # FIX... handle arrays 1475 assert (numbits>=1 and numbits <= 1024) or -1==numbits # What is good max? 1476 # FIX: assert not required and not an array an not unavailable 1477 1478 1479 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1480 o.write(bv+'['+str(startindex)+':') 1481 if int(numbits) != -1: o.write(str(end)) # -1 means go to the end of the message 1482 o.write(']') 1483 if not decodeOnly: o.write('\n') 1484 1485 return end
1486 1487 1488 1489 ###################################################################### 1490 # THE REST 1491 ###################################################################### 1492 1493 1494
1495 -def buildTestParamFunc(o,msgET, verbose=False, prefixName=False):
1496 '''Scrape the testvalues to make a basic param 1497 1498 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies? 1499 ''' 1500 name = msgET.attrib['name'] 1501 1502 if prefixName: o.write('def '+name+'TestParams():\n') 1503 else: o.write('def testParams():\n') 1504 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") 1505 o.write('\tparams = {}\n') 1506 for field in msgET.xpath('field'): 1507 name = field.attrib['name'] 1508 type = field.attrib['type'] 1509 if verbose: print 'buildTestParamFunc ...',name,type 1510 val = None 1511 if hasSubTag(field,'testvalue') and hasSubTag(field,'required'): 1512 print 'ERROR: can not have both test value and required tags in the same field' 1513 assert(False) 1514 if hasSubTag(field,'testvalue'): 1515 val = field.xpath('testvalue')[0].text 1516 else: 1517 if not hasSubTag(field,'required'): 1518 sys.exit("ERROR: missing required or testvalue for field: "+name) 1519 val = field.xpath('required')[0].text 1520 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val 1521 o.write('\tparams[\''+name+'\'] = ') 1522 if type=='bool': 1523 if val=='1' or val.lower=='true': val = 'True' 1524 else: val = 'False' 1525 o.write(val) 1526 elif type in ('uint','int','float'): 1527 o.write(val) 1528 elif type in ('decimal','udecimal'): 1529 o.write('Decimal(\''+val+'\')') 1530 1531 elif type in ('aisstr6'): 1532 o.write('\''+val+'\'') 1533 elif type in ('binary'): 1534 o.write('BitVector(bitstring=\''+val+'\')') 1535 else: 1536 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee' 1537 suggestType(name,type) 1538 assert(False) 1539 1540 o.write('\n') 1541 1542 1543 o.write('\n\treturn params\n\n')
1544
1545 -def buildUnitTest(o,msgET, verbose=False, prefixName=False):
1546 ''' 1547 Write the unittests for a message 1548 1549 @param o: open file where resulting code will be written 1550 @param msgET: Element Tree starting at a message node 1551 ''' 1552 assert(msgET.tag=='message') 1553 name = msgET.attrib['name'] 1554 1555 buildTestParamFunc(o,msgET, prefixName=prefixName) 1556 1557 o.write('class Test'+name+'(unittest.TestCase):\n') 1558 o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n") 1559 o.write('\tdef testEncodeDecode(self):\n\n') 1560 if prefixName: 1561 o.write('\t\tparams = '+name+'TestParams()\n') 1562 o.write('\t\tbits = '+name+'Encode(params)\n') 1563 o.write('\t\tr = '+name+'Decode(bits)\n\n') 1564 else: 1565 o.write('\t\tparams = testParams()\n') 1566 o.write('\t\tbits = encode(params)\n') 1567 o.write('\t\tr = decode(bits)\n\n') 1568 1569 o.write('\t\t# Check that each parameter came through ok.\n') 1570 for field in msgET.xpath('field'): 1571 name = field.attrib['name'] 1572 type = field.attrib['type'] 1573 if type in ('bool','uint','int','aisstr6','binary'): 1574 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n') 1575 else: 1576 # float, decimal, udecimal 1577 # FIX: look up the decimal places if decimal 1578 places = '3' 1579 if hasSubTag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text 1580 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
1581 1582 1583
1584 -def buildEncode(o,msgET, verbose=False, prefixName=False):
1585 ''' 1586 Write the encoder/decoder for a message 1587 1588 http://jaynes.colorado.edu/PythonIdioms.html 1589 1590 @param o: open file where resulting code will be written 1591 @param msgET: Element Tree starting at a message node 1592 ''' 1593 assert(msgET.tag=='message') 1594 name = msgET.attrib['name'] # fix rename this variable to avoid hiding later on by field name 1595 1596 print 'Generating encoder for',name # FIX: verbose? 1597 funcName = 'encode' 1598 if prefixName: funcName = name+'Encode' 1599 o.write('def '+funcName+'(params, validate=False):\n') 1600 1601 ######################################## 1602 # doc string 1603 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n") 1604 o.write('\tFields in params:\n') 1605 for field in msgET.xpath('field'): 1606 if verbose: print field.tag,field.attrib['name'] 1607 desc = field[0].text.replace('\n',' ') # get ride of new lines 1608 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1609 if len(field.xpath("required")) == 1: 1610 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1611 1612 o.write('\n') 1613 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n') 1614 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1615 1616 o.write("\t@rtype: BitVector\n") 1617 o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n") 1618 o.write("\t@note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.\n") 1619 o.write("\t'''\n\n") 1620 1621 ######################################## 1622 # Actually build the code 1623 1624 o.write('\tbvList = []\n') 1625 1626 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1627 1628 dynamicArrays = False # Set to true when positioning must be calculated 1629 1630 for field in msgET.xpath('field'): 1631 name = field.attrib['name'] 1632 type = field.attrib['type'] 1633 numbits = int(field.attrib['numberofbits']) 1634 required = None; 1635 if hasSubTag(field,'required'): 1636 required = field.xpath('required')[0].text 1637 #print 'required set for',name,'to',required 1638 unavailable=None; 1639 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1640 arraylen=1 1641 if 'arraylength' in field.attrib: 1642 arraylen=int(field.attrib['arraylength']) 1643 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1644 else: 1645 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 1646 1647 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable) 1648 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable) 1649 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable) 1650 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable) 1651 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable) 1652 elif type=='decimal': 1653 scale = None 1654 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1655 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale) 1656 elif type=='udecimal': 1657 scale = None 1658 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1659 encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale) 1660 elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable) 1661 else: 1662 print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type 1663 suggestType (name,type) 1664 assert False 1665 1666 # o.write('\n\tbv=binary.joinBV(bvList)\n') 1667 # o.write('\n\tbvLen=len(bv)\n') 1668 # o.write('\n\tif bvLen%6!=0:\n') 1669 # o.write('\n\t bv = bv + BitVector(size=bvLen%6) # \n') 1670 # o.write('\n\treturn bv\n\n') 1671 o.write('\n\treturn binary.joinBV(bvList)\n\n')
1672 1673 1674 ###################################################################### 1675 # DECODER ONE PART AT A TIME - if only using a small part, save some cycles! 1676 1677 1678 #, msgDict=None):
1679 -def buildDecodeParts(o,msgET, verbose=False, prefixName=False):
1680 1681 ''' 1682 Write the decoder for a message 1683 1684 @param o: open file where resulting code will be written 1685 @type msgET: elementtree 1686 @param prefixName: if True, put the name of the message on the functions. 1687 @param msgET: Element Tree starting at a message node 1688 @return: None 1689 1690 @todo: FIX: doc strings for each decode! 1691 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 1692 @todo: May want to take a dictionary of already decoded fields to speed things that need prior info 1693 for things like variable length arrays 1694 ''' 1695 1696 assert(msgET.tag=='message') 1697 name = msgET.attrib['name'] 1698 1699 print 'Generating partial decode functions for',name 1700 1701 baseName = name+'Decode' 1702 if not prefixName: baseName = 'decode' 1703 1704 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 1705 1706 for field in msgET.xpath('field'): 1707 name = field.attrib['name'] 1708 type = field.attrib['type'] 1709 1710 o.write('def '+baseName+name+'(bv, validate=False):\n') 1711 # Follow the same convention of decoding into a dict so that code is the same 1712 #o.write('\tr={};') 1713 o.write('\treturn ') 1714 1715 numbits = int(field.attrib['numberofbits']) 1716 required = None; 1717 if hasSubTag(field,'required'): 1718 required = field.xpath('required')[0].text 1719 unavailable=None; 1720 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1721 arraylen=1 1722 if 'arraylength' in field.attrib: 1723 arraylen=int(field.attrib['arraylength']) 1724 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1725 1726 assert None!=startindex 1727 if verbose: print 'startindex',startindex 1728 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1729 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1730 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1731 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1732 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1733 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1734 1735 elif type=='decimal': 1736 scale = None 1737 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1738 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True) 1739 elif type=='udecimal': 1740 scale = None 1741 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1742 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True) 1743 1744 else: 1745 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 1746 suggestType (name,type) 1747 assert False 1748 1749 #o.write('\treturn(r[\''+name+'\'])\n\n') 1750 o.write('\n\n')
1751 1752 1753 ###################################################################### 1754 # DECODER RING 1755
1756 -def buildDecode(o,msgET, verbose=False, prefixName=False):
1757 ''' 1758 Write the decoder for a message 1759 1760 @param o: open file where resulting code will be written 1761 @type msgET: elementtree 1762 @param msgET: Element Tree starting at a message node 1763 @return: None 1764 1765 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 1766 ''' 1767 1768 assert(msgET.tag=='message') 1769 name = msgET.attrib['name'] 1770 1771 print 'Generating decoder for',name 1772 funcName = 'decode' 1773 if prefixName: funcName = name+'Decode' 1774 o.write('def '+funcName+'(bv, validate=False):\n') 1775 1776 ######################################## 1777 # doc string 1778 o.write("\t'''Unpack a "+name+" message \n\n") 1779 o.write('\tFields in params:\n') 1780 for field in msgET.xpath('field'): 1781 desc = field[0].text.replace('\n',' ') # get ride of new lines 1782 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1783 if len(field.xpath("required")) == 1: 1784 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1785 1786 o.write('\n') 1787 o.write('\t@type bv: BitVector\n') 1788 o.write('\t@param bv: Bits defining a message\n') 1789 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1790 1791 o.write("\t@rtype: dict\n") 1792 o.write("\t@return: params\n") 1793 o.write("\t'''\n\n") 1794 1795 1796 o.write('\t#Would be nice to check the bit count here..\n') 1797 o.write('\t#if validate:\n') 1798 o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n') 1799 1800 1801 ######################################## 1802 # Actually build the code 1803 1804 o.write('\tr = {}\n') 1805 1806 1807 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1808 1809 dynamicArrays = False # Set to true when positioning must be calculated 1810 1811 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 1812 1813 for field in msgET.xpath('field'): 1814 name = field.attrib['name'] 1815 type = field.attrib['type'] 1816 numbits = int(field.attrib['numberofbits']) 1817 required = None; 1818 if hasSubTag(field,'required'): 1819 required = field.xpath('required')[0].text 1820 #print 'required set for',name,'to',required 1821 unavailable=None; 1822 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1823 arraylen=1 1824 if 'arraylength' in field.attrib: 1825 arraylen=int(field.attrib['arraylength']) 1826 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1827 else: 1828 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 1829 1830 assert None!=startindex 1831 if verbose: print 'startindex',startindex 1832 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable) 1833 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 1834 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 1835 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable) 1836 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable) 1837 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable) 1838 1839 elif type=='decimal': 1840 scale = None 1841 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1842 #print 'pre call...' 1843 #print ' required: "'+str(required)+'" scale: "'+str(scale)+'" unavail:', unavailable 1844 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale) 1845 elif type=='udecimal': 1846 scale = None 1847 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 1848 #print 'pre call...' 1849 #print ' required: "'+str(required)+'" scale: "'+str(scale)+'" unavail:', unavailable 1850 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale) 1851 1852 else: 1853 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 1854 suggestType (name,type) 1855 assert False 1856 1857 1858 if None==startindex: print 'FIX: here. drat. treat me right' 1859 assert None!=startindex 1860 1861 1862 o.write('\treturn r\n\n')
1863 1864 1865 1866 #def getPythonType(aType): 1867 # ''' 1868 # Translate a 1869 # @rtype: str 1870 # @return: name of the python type 1871 # ''' 1872 1873 aisType2pythonType={ 1874 'bool':'Bool', 1875 'uint':'int', 1876 'int':'int', 1877 'udecimal':'Decimal', 1878 'decimal':'Decimal', 1879 'aisstr6':'str', 1880 'binary':'str', 1881 'float':'Float', 1882 } 1883 1884 import copy 1885 aisType2optParseType=copy.deepcopy(aisType2pythonType) 1886 aisType2optParseType['bool']='int' # FIX: make these appear as a flag variable 1887 aisType2optParseType['aisstr6']='string' 1888 aisType2optParseType['aisstr6']='string' 1889 aisType2optParseType['binary']='string' 1890 aisType2optParseType['udecimal']='string' # FIX: Is this a good choice? 1891 aisType2optParseType['decimal']='string' 1892 aisType2optParseType['float']='float' 1893 1894
1895 -def buildOptParse(o,msgET, prefixName=False):
1896 '''Create a function that adds the options to a parse object''' 1897 1898 assert None != msgET 1899 assert msgET.tag=='message' 1900 msgName = msgET.attrib['name'] 1901 1902 prefix='' 1903 if prefixName: prefix=msgName 1904 1905 funcName = 'addMsgOptions' 1906 if prefixName: funcName = msgName + 'AddMsgOptions' 1907 o.write('\ndef '+funcName+'(parser):') 1908 1909 # FIX: write out a doc string 1910 1911 o.write(''' 1912 parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true', 1913 help='decode a "'''+msgName+'''" AIS message') 1914 parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true', 1915 help='encode a "'''+msgName+'''" AIS message') 1916 ''') 1917 1918 #print 'here...', msgName, prefixName 1919 for field in msgET.xpath('field'): 1920 name = field.attrib['name'] 1921 fieldType = field.attrib['type'] 1922 if hasSubTag(field,'required'): 1923 print 'skipping required field ...',name,fieldType 1924 continue 1925 #print 'there',name,fieldType 1926 o.write('\tparser.add_option(\'--') 1927 if prefixName: o.write(msgName+'-') 1928 o.write(name+'-field\', dest=\''+name+'Field\'') 1929 if hasSubTag(field,'unavailable'): 1930 val = field.xpath('unavailable')[0].text 1931 o.write(',default=') 1932 if fieldType in ('uint','int','float'): 1933 o.write(val) 1934 elif fieldType in ('decimal','udecimal'): 1935 o.write('Decimal(\''+val+'\')') 1936 elif fieldType in ('aisstr6','bitvector'): 1937 o.write('\''+val+'\'') 1938 o.write(',metavar=\''+fieldType+'\',type=\''+aisType2optParseType[fieldType]+'\'') 1939 o.write('\n\t\t,help=\'Field parameter value [default: %default]\')\n')
1940 1941 1942
1943 -def buildMain(o, msgET, prefixName=False):
1944 assert None != msgET 1945 assert msgET.tag=='message' 1946 msgName = msgET.attrib['name'] 1947 1948 prefix='' 1949 if prefixName: prefix=msgName 1950 1951 buildOptParse(o, msgET, prefixName) 1952 1953 1954 o.write(''' 1955 ############################################################ 1956 if __name__=='__main__': 1957 1958 from optparse import OptionParser 1959 parser = OptionParser(usage="%prog [options]", 1960 version="%prog "+__version__) 1961 1962 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 1963 help='run the documentation tests') 1964 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true', 1965 help='run the unit tests') 1966 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 1967 help='Make the test output verbose') 1968 1969 # FIX: remove nmea from binary messages. No way to build the whole packet? 1970 # FIX: or build the surrounding msg 8 for a broadcast? 1971 typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message? 1972 parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType' 1973 ,default='nmeapayload' 1974 ,help='What kind of string to expect ('+', '.join(typeChoices)+') [default: %default]') 1975 1976 1977 outputChoices = ('std','html','csv','sql' ''') 1978 # 'xml' - FIX: need to add xml output 1979 if haveLocatableMessage(msgET): o.write(', \'kml\',\'kml-full\'') 1980 o.write(''') 1981 parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType' 1982 ,default='std' 1983 ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]') 1984 1985 parser.add_option('-o','--output',dest='outputFileName',default=None, 1986 help='Name of the python file to write [default: stdout]') 1987 1988 parser.add_option('-f','--fields',dest='fieldList',default=None, action='append', 1989 choices=fieldList, 1990 help='Which fields to include in the output. Currently only for csv output [default: all]') 1991 1992 parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true', 1993 help='Print the field name for csv') 1994 1995 parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true', 1996 help='Print out an sql create command for the table.') 1997 1998 ''') 1999 2000 2001 o.write('''\taddMsgOptions(parser)\n''') 2002 2003 o.write(''' 2004 (options,args) = parser.parse_args() 2005 success=True 2006 2007 if options.doctest: 2008 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 2009 sys.argv= [sys.argv[0]] 2010 if options.verbose: sys.argv.append('-v') 2011 import doctest 2012 numfail,numtests=doctest.testmod() 2013 if numfail==0: print 'ok' 2014 else: 2015 print 'FAILED' 2016 success=False 2017 2018 if not success: sys.exit('Something Failed') 2019 del success # Hide success from epydoc 2020 2021 if options.unittest: 2022 sys.argv = [sys.argv[0]] 2023 if options.verbose: sys.argv.append('-v') 2024 unittest.main() 2025 2026 outfile = sys.stdout 2027 if None!=options.outputFileName: 2028 outfile = file(options.outputFileName,'w') 2029 2030 ''') 2031 2032 2033 2034 ############################## 2035 # encode 2036 ############################## 2037 # FIX: make everything tabs. Grr. 2038 o.write('\n\tif options.doEncode:\n') 2039 o.write('\t\t# First make sure all non required options are specified\n') 2040 for field in msgET.xpath('field'): 2041 name = field.attrib['name'] 2042 fieldType = field.attrib['type'] 2043 varName = prefix+name+'Field' 2044 if not hasSubTag(field,'required'): 2045 o.write('\t\tif None==options.'+varName+': parser.error("missing value for '+varName+'")\n') 2046 2047 # Build dict 2048 o.write('\t\tmsgDict={\n') 2049 for field in msgET.xpath('field'): 2050 name = field.attrib['name'] 2051 varName = prefix+name+'Field' 2052 if hasSubTag(field,'required'): 2053 o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n') 2054 else: 2055 o.write('\t\t\t\''+name+'\': options.'+varName+',\n') 2056 o.write('\t\t}\n') 2057 2058 encodeFunction = 'encode' 2059 if prefixName: encodeFunction = msgName+'Encode' 2060 o.write(''' 2061 bits = '''+encodeFunction+'''(msgDict) 2062 if 'binary'==options.ioType: print str(bits) 2063 elif 'nmeapayload'==options.ioType: 2064 # FIX: figure out if this might be necessary at compile time 2065 print "bitLen",len(bits) 2066 bitLen=len(bits) 2067 if bitLen%6!=0: 2068 bits = bits + BitVector(size=(6 - (bitLen%6))) # Pad out to multiple of 6 2069 print "result:",binary.bitvectoais6(bits)[0] 2070 2071 2072 # FIX: Do not emit this option for the binary message payloads. Does not make sense. 2073 elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability") 2074 else: sys.exit('ERROR: unknown ioType. Help!') 2075 ''') 2076 2077 2078 ############################## 2079 # decode all 2080 ############################## 2081 decodeFunction = 'decode' 2082 printFields='printFields' 2083 if prefixName: 2084 decodeFunction = msgName+'Decode' 2085 printFields = msgName+'PrintFields' 2086 o.write(''' 2087 2088 if options.sqlCreate: 2089 sqlCreateStr(outfile,options.fieldList) 2090 2091 if options.printCsvfieldList: 2092 # Make a csv separated list of fields that will be displayed for csv 2093 if None == options.fieldList: options.fieldList = fieldList 2094 import StringIO 2095 buf = StringIO.StringIO() 2096 for field in options.fieldList: 2097 buf.write(field+',') 2098 result = buf.getvalue() 2099 if result[-1] == ',': print result[:-1] 2100 else: print result 2101 2102 if options.doDecode: 2103 for msg in args: 2104 bv = None 2105 if 'binary' == options.ioType: bv = BitVector(bitstring=msg) 2106 elif 'nmeapayload'== options.ioType: bv = binary.ais6tobitvec(msg) 2107 elif 'nmea' == options.ioType: bv = binary.ais6tobitvec(msg.split(',')[5]) 2108 else: sys.exit('ERROR: unknown ioType. Help!') 2109 2110 '''+printFields+'''('''+decodeFunction+'''(bv),out=outfile,format=options.outputType,fieldList=options.fieldList) 2111 ''')
2112 2113 2114 2115 2116 ###################################################################### 2117 if __name__=='__main__': 2118 from optparse import OptionParser 2119 parser = OptionParser(usage="%prog [options]", 2120 version="%prog "+__version__) 2121 2122 parser.add_option('-o','--output',dest='outputFileName',default=None, 2123 help='Name of the python file to write') 2124 # help='Name of the python file to write [default: stdout]') 2125 2126 parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None, 2127 help='XML definition file for the msg to use') 2128 # help='XML definition file for the msg to use [default: stdin]') 2129 2130 # parser.add_option('-m','--message',dest='message',default=None, 2131 # help='Which message to write code for [default: all]') 2132 2133 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 2134 help='run the documentation tests') 2135 2136 parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true', 2137 help='put the field name in front of all function names.' 2138 +' Allows multiple messages in one file') 2139 2140 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 2141 help='run the tests run in verbose mode') 2142 2143 (options,args) = parser.parse_args() 2144 2145 success=True 2146 2147 if options.doctest: 2148 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 2149 argvOrig = sys.argv 2150 sys.argv= [sys.argv[0]] 2151 if options.verbose: sys.argv.append('-v') 2152 import doctest 2153 numfail,numtests=doctest.testmod() 2154 if numfail==0: print 'ok' 2155 else: 2156 print 'FAILED' 2157 success=False 2158 sys.argv = argvOrig # Restore the original args 2159 del argvOrig # hide from epydoc 2160 sys.exit() # FIX: Will this exit success? 2161 2162 #infile=sys.stdin 2163 #if options.xmlFileName: infile = file(options.xmlFileName,'r') 2164 2165 #outfile=sys.stdout 2166 #if options.outputFile: outfile = file(options.xmlDefinition,'w') 2167 2168 # FIX: down the road, it would be good to allow either filenames of std{in/out} 2169 2170 if None==options.xmlFileName: 2171 sys.exit('ERROR: must specify an xml definition file.') 2172 if None==options.outputFileName: 2173 sys.exit('ERROR: must specify an python file to write to.') 2174 generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix, verbose=options.verbose) 2175 2176 print '\nrecommend running pychecker like this:' 2177 print ' pychecker -q',options.outputFileName 2178