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  @todo: handle entry ranges in lookup tables 
  38  @todo: arraylength of -1 for messages 12 and 14 
  39  @bug: NOT complete 
  40  ''' 
  41   
  42  import sys, os 
  43  from decimal import Decimal 
  44  from lxml import etree  
  45   
46 -def suggestType(name,curType,printout=True):
47 ''' 48 Try to suggest a type name if one did not work. 49 50 @param printout: if true, write a suggestion to stdout. 51 52 >>> suggestType('myFieldName','unsigned int') 53 Recommend switching "unsigned int" to "uint" for field "myFieldName" 54 'uint' 55 56 >>> suggestType('JohnWarfon','yoyodyne') 57 Sorry! No recommendation available for bad type "yoyodyne" for field "JohnWarfon" 58 ''' 59 newType = None 60 if curType.lower()=='unsigned int': 61 newType = 'uint' 62 elif curType.lower()=='unsigned decimal': 63 newType = 'udecimal' 64 65 if printout: 66 if None != newType: 67 print 'Recommend switching "'+curType+'" to "'+newType+'" for field "'+name+'"' 68 else: 69 print 'Sorry! No recommendation available for bad type "'+curType+'" for field "'+name+'"' 70 return newType
71 72
73 -def hasSubTag(et,subtag):
74 ''' 75 @return: true if the tag a sub tag with name subtag 76 ''' 77 if 0<len(et.xpath(subtag)): return True 78 return False
79 80
81 -def writeBeginning(o):
82 ''' 83 Write the doc string header for the message file 84 85 @param o: Open output file to write code to. 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 @todo: FIX: put in a description of the message here with fields and types. 131 \'\'\' 132 133 import sys 134 from decimal import Decimal 135 from BitVector import BitVector 136 137 import binary, aisstring 138 139 # FIX: check to see if these will be needed 140 TrueBV = BitVector(bitstring="1") 141 "Why always rebuild the True bit? This should speed things up a bunch" 142 FalseBV = BitVector(bitstring="0") 143 "Why always rebuild the False bit? This should speed things up a bunch" 144 145 146 ''') 147 return
148
149 -def generatePython(infile,outfile, prefixName=False,verbose=False):
150 ''' 151 @param infile: xml ais binary message definition file 152 @param outfile: where to dump the python code 153 ''' 154 155 aisMsgsET = etree.parse(infile).getroot() 156 157 o = file(outfile,'w') 158 os.chmod(outfile,0755) 159 160 writeBeginning(o) 161 162 for msgET in aisMsgsET: 163 if msgET.tag != 'message': continue 164 print msgET.tag, msgET.attrib['name'] 165 166 if len(msgET.xpath('include-struct')) > 0: 167 sys.exit("ERROR: cannot handle xml that still has include-struct tags.\n Please use expandais.py.") 168 buildHelpers(o,msgET,prefixName=prefixName,verbose=verbose) 169 buildEncode(o,msgET,prefixName=prefixName,verbose=verbose) 170 buildDecode(o,msgET,prefixName=prefixName) 171 buildDecodeParts(o,msgET,prefixName=prefixName) # functions that only decode one field 172 buildPrint(o,msgET,prefixName=prefixName) 173 buildLUT(o,msgET,prefixName=prefixName) 174 buildSQL(o,msgET,prefixName=prefixName) 175 buildLaTeX(o,msgET,prefixName=prefixName) 176 buildTextDef(o,msgET,prefixName=prefixName) 177 178 o.write('\n\n######################################################################\n') 179 o.write('# UNIT TESTING\n') 180 o.write('######################################################################\n') 181 o.write('import unittest\n') 182 183 for msgET in aisMsgsET: 184 if msgET.tag != 'message': continue 185 print 'Building unit tests for message ...', msgET.attrib['name'] 186 187 buildUnitTest(o,msgET, prefixName=prefixName) 188 189 for msgET in aisMsgsET: 190 if msgET.tag != 'message': continue 191 buildMain(o,msgET, prefixName=prefixName) 192 193 return
194 195 ###################################################################### 196 # Build Helpers 197 ###################################################################### 198
199 -def buildHelpers(o,msgET, verbose=False, prefixName=False):
200 ''' 201 emit the fieldList and other things??? 202 203 @param o: open file where resulting code will be written 204 @param msgET: Element Tree starting at a message node 205 206 @todo: for lookuptable/entry values, make it also print the decoded value. 207 @todo: use a different name for message and field 208 @todo: put in comments with the python and sql data type for each field 209 ''' 210 211 if verbose: 212 msgname = msgET.attrib['name'] 213 print 'Building helpers ...',msgname 214 215 if prefixName: 216 o.write(prefixName,'FieldList = (\n') 217 else: 218 o.write('fieldList = (\n') 219 220 for field in msgET.xpath('field'): 221 name = field.attrib['name'] 222 o.write('\t\''+name+'\',\n') 223 224 o.write(')\n\n') 225 226 # FIX: add some documentation to the fieldList 227 # FIX: like that it is used as the default csv list 228 229 if prefixName: 230 o.write(prefixName,'FieldListPostgres = (\n') 231 else: 232 o.write('fieldListPostgres = (\n') 233 finished=[] # postgis names 234 for field in msgET.xpath('field'): 235 name = field.attrib['name'] 236 if 'postgisName' in field.attrib: 237 name = field.attrib['postgisName'] 238 if name in finished: continue # already done, so skip 239 finished.append(name) 240 o.write('\t\''+name+'\',\t# PostGIS data type\n') 241 else: 242 o.write('\t\''+name+'\',\n') 243 244 o.write(')\n\n') 245 246 del finished 247 248 249 250 if prefixName: 251 o.write(prefixName,'ToPgFields = {\n') 252 else: 253 o.write('toPgFields = {\n') 254 for field in msgET.xpath('field'): 255 if 'postgisName' not in field.attrib: continue 256 name = field.attrib['name'] 257 pgName = field.attrib['postgisName'] 258 o.write('\t\''+name+'\':\''+pgName+'\',\n') 259 o.write('}\n') 260 o.write('''\'\'\' 261 Go to the Postgis field names from the straight field name 262 \'\'\' 263 264 ''') 265 266 267 if prefixName: 268 o.write(prefixName,'FromPgFields = {\n') 269 else: 270 o.write('fromPgFields = {\n') 271 finished=[] # postgis names 272 for field in msgET.xpath('field'): 273 if 'postgisName' not in field.attrib: continue 274 name = field.attrib['name'] 275 pgName = field.attrib['postgisName'] 276 277 if pgName in finished: continue # already done, so skip 278 finished.append(pgName) 279 o.write('\t\''+pgName+'\':(') 280 xp = 'field[@postgisName=\''+pgName+'\']' 281 parts = ['\''+part.attrib['name']+'\'' for part in msgET.xpath(xp)] 282 o.write(','.join(parts)+',),\n') 283 o.write('}\n') 284 o.write('''\'\'\' 285 Go from the Postgis field names to the straight field name 286 \'\'\' 287 288 ''') 289 290 291 if prefixName: 292 o.write(prefixName,'PgTypes = {\n') 293 else: 294 o.write('pgTypes = {\n') 295 finished=[] # postgis names 296 for field in msgET.xpath('field'): 297 if 'postgisName' not in field.attrib: continue 298 pgType = field.attrib['postgisType'] 299 pgName = field.attrib['postgisName'] 300 if pgName in finished: continue # already done, so skip 301 finished.append(pgName) 302 o.write('\t\''+pgName+'\':\''+pgType+'\',\n') 303 o.write('}\n') 304 o.write('''\'\'\' 305 Lookup table for each postgis field name to get its type. 306 \'\'\' 307 308 ''')
309 310 311 312 313 314 315 316 ###################################################################### 317 # SIMPLE PRINT 318 ###################################################################### 319
320 -def getMaxFieldNameLen(msgET):
321 '''Get the maximum string length of any field name''' 322 maxStrLen=0 323 for field in msgET.xpath('field'): 324 fieldLen = len(field.attrib['name']) 325 if fieldLen>maxStrLen: maxStrLen = fieldLen 326 return maxStrLen
327
328 -def padStrRight(aStr,strlen):
329 '''Pad a string out to the length requested with spaces out to the right''' 330 return aStr + ' '*(strlen-len(aStr))
331
332 -def haveLocatableMessage(msgET):
333 '''Make sure this message has both long/x and lat/y fields. 334 @rtype: bool 335 ''' 336 #if getLongitudeFieldName(msgET) and getLatitudeFieldName(msgET): return True 337 338 if 0==len(msgET.xpath('field[contains(@name, "longitude")]')): return False 339 if 0==len(msgET.xpath('field[contains(@name, "latitude")]')): return False 340 return True
341
342 -def getLongitudeFieldName(msgET):
343 ''' 344 Dig up the first field name that include longitude and return it 345 @todo: might want to allow a search for a special tag to mark this 346 ''' 347 return msgET.xpath('field[contains(@name, "longitude")]')[0].attrib['name']
348
349 -def getLatitudeFieldName(msgET):
350 ''' 351 Dig up the first field name that include longitude and return it 352 @todo: might want to allow a search for a special tag to mark this 353 ''' 354 return msgET.xpath('field[contains(@name, "latitude")]')[0].attrib['name']
355
356 -def buildPrint(o,msgET, verbose=False, prefixName=False):
357 ''' 358 Write a simple in order print for the resulting dictionary. 359 360 @param o: open file where resulting code will be written 361 @param msgET: Element Tree starting at a message node 362 363 @todo: for lookuptable/entry values, make it also print the decoded value. 364 @todo: use a different name for message and field 365 ''' 366 assert(msgET.tag=='message') 367 msgName = msgET.attrib['name'] 368 #msgName = msgname = name # FIX: make these all msgName 369 370 print 'Generating print ...',msgName # FIX: verbose? 371 # +1 for the ':' 372 maxFieldLen = 1 + getMaxFieldNameLen(msgET) 373 374 375 ############################## 376 # Html builder 377 ############################## 378 379 printHtmlName = 'printHtml' 380 if prefixName: printHtmlName = name+'printHtml' 381 382 383 384 385 #################### 386 ####### HTML format 387 #################### 388 #o.write('\telif \'html\'==format:\n') 389 o.write('\n') 390 o.write('def '+printHtmlName+'(params, out=sys.stdout):\n') 391 o.write('\t\tout.write("<h3>'+msgName+'<h3>\\n")\n') 392 o.write('\t\tout.write("<table border=\\"1\\">\\n")\n') 393 #o.write('\t\tout.write("<tr bgcolor=\\"#9acd32\\">\\n")\n') 394 o.write('\t\tout.write("<tr bgcolor=\\"orange\\">\\n")\n') 395 o.write('\t\tout.write("<th align=\\"left\\">Field Name</th>\\n")\n') 396 o.write('\t\tout.write("<th align=\\"left\\">Type</th>\\n")\n') 397 o.write('\t\tout.write("<th align=\\"left\\">Value</th>\\n")\n') 398 o.write('\t\tout.write("<th align=\\"left\\">Value in Lookup Table</th>\\n")\n') 399 o.write('\t\tout.write("<th align=\\"left\\">Units</th>\\n")\n') 400 401 402 for field in msgET.xpath('field'): 403 o.write('\t\tout.write("\\n")\n') 404 o.write('\t\tout.write("<tr>\\n")\n') 405 fieldname = field.attrib['name'] 406 fieldtype = field.attrib['type'] 407 o.write('\t\tout.write("<td>'+fieldname+'</td>\\n")\n') 408 o.write('\t\tout.write("<td>'+fieldtype+'</td>\\n")\n') 409 410 numbits = int(field.attrib['numberofbits']) 411 required = None; 412 if hasSubTag(field,'required'): 413 required = field.xpath('required')[0].text 414 unavailable=None; 415 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 416 arraylen=1 417 if 'arraylength' in field.attrib: arraylen=int(field.attrib['arraylength']) 418 419 if 1==arraylen or fieldtype=='aisstr6': 420 o.write('\t\tif \''+fieldname+'\' in params:\n\t\t\tout.write("\t<td>"+str(params[\''+fieldname+'\'])+"</td>\\n")\n') 421 if not hasSubTag(field,'lookuptable'): 422 # Just duplicate the value through 423 o.write('\t\t\tout.write("\t<td>"+str(params[\''+fieldname+'\'])+"</td>\\n")\n') 424 else: 425 lutName = fieldname+'DecodeLut' 426 if prefixName: lutName = msgname+fieldname.capitalize()+'DecodeLut' 427 428 o.write('\t\t\tif str(params[\''+fieldname+'\']) in '+lutName+':\n') 429 o.write('\t\t\t\tout.write("<td>"+'+lutName+'[str(params[\''+fieldname+'\'])]+"</td>")\n') 430 o.write('\t\t\telse:\n') 431 o.write('\t\t\t\tout.write("<td><i>Missing LUT entry</i></td>")\n') 432 else: sys.exit ('FIX: handle arrays in the buildPrint func') 433 434 if hasSubTag(field,'units'): 435 o.write('\t\tout.write("<td>'+field.xpath('units')[0].text+'</td>\\n")\n') 436 437 o.write('\t\tout.write("</tr>\\n")\n') 438 439 o.write('\t\tout.write("</table>\\n")\n') 440 o.write('\n') 441 442 ############################## 443 ####### KML GoogleEarth 444 ############################## 445 446 printKmlName = 'printKml' 447 if prefixName: printKmlName = msgName+'printKml' 448 if not haveLocatableMessage(msgET): printKmlName=None 449 else: 450 # FIX: use the first postgis print tagged lat and lon field 451 o.write('\n') 452 o.write('def printKml(params, out=sys.stdout):\n') 453 o.write('\t\'\'\'KML (Keyhole Markup Language) for Google Earth, but without the header/footer\'\'\'\n') 454 o.write('\tout.write("\\\t<Placemark>\\n")\n') 455 o.write('\tout.write("\\t\t<name>"+str(params[\''+msgET.attrib['titlefield']+'\'])+"</name>\\n")\n') 456 o.write('\tout.write("\\t\\t<description>\\n")\n') 457 458 o.write('\timport StringIO\n') 459 o.write('\tbuf = StringIO.StringIO()\n') 460 o.write('\tprintHtml(params,buf)\n') 461 o.write('\timport cgi\n') 462 o.write('\tout.write(cgi.escape(buf.getvalue()))\n') 463 464 o.write('\tout.write("\\t\\t</description>\\n")\n') 465 o.write('\tout.write("\\t\\t<styleUrl>#m_ylw-pushpin_copy0</styleUrl>\\n")\n') 466 o.write('\tout.write("\\t\\t<Point>\\n")\n') 467 o.write('\tout.write("\\t\\t\\t<coordinates>")\n') 468 o.write('\tout.write(str(params[\''+getLongitudeFieldName(msgET)+'\']))\n') 469 o.write('\tout.write(\',\')\n') 470 o.write('\tout.write(str(params[\''+getLatitudeFieldName(msgET)+'\']))\n') 471 o.write('\tout.write(",0</coordinates>\\n")\n') 472 o.write('\tout.write("\\t\\t</Point>\\n")\n') 473 o.write('\tout.write("\\t</Placemark>\\n")\n') 474 o.write('\n') 475 476 477 ############################## 478 ##### Main print dispatch 479 ############################## 480 481 funcName = 'printFields' 482 if prefixName: funcName = msgName+'PrintFields' 483 484 o.write('def '+funcName+'(params, out=sys.stdout, format=\'std\', fieldList=None, dbType=\'postgres\'):\n') 485 486 ######################################## 487 # doc string 488 o.write("\t'''Print a "+msgName+" message to stdout.\n\n") 489 o.write('\tFields in params:\n') 490 for field in msgET.xpath('field'): 491 desc = field[0].text.replace('\n',' ') # get ride of new lines 492 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 493 if len(field.xpath("required")) == 1: 494 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 495 496 o.write('\n') 497 o.write('\t@param params: Dictionary of field names/values. \n') 498 o.write('\t@param out: File like object to write to\n') 499 500 o.write("\t@rtype: stdout\n") 501 o.write("\t@return: text to out\n") 502 o.write("\t'''\n\n") 503 504 ######################################## 505 # Actually build the code 506 507 o.write('\tif \'std\'==format:\n') 508 o.write('\t\tout.write("'+msgName+':\\n")\n') 509 510 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 511 512 513 for field in msgET.xpath('field'): 514 fieldname = field.attrib['name'] 515 fieldtype = field.attrib['type'] 516 numbits = int(field.attrib['numberofbits']) 517 required = None; 518 if hasSubTag(field,'required'): 519 required = field.xpath('required')[0].text 520 unavailable=None; 521 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 522 arraylen=1 523 if 'arraylength' in field.attrib: 524 arraylen=int(field.attrib['arraylength']) 525 if verbose: print 'Processing field ...',fieldname,'('+fieldtype+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 526 else: 527 if verbose: print 'Processing field ...',fieldname,'(',fieldtype+' ',numbits,')' 528 529 if 1==arraylen or fieldtype=='aisstr6': 530 o.write('\t\tif \''+fieldname+'\' in params: out.write("\t'+padStrRight(fieldname+':',maxFieldLen)+' "+str(params[\''+fieldname+'\'])+"\\n")\n') 531 # FIX: elif aisstr6 strip @@@ and then print 532 else: 533 print '\n','ERROR:',fieldname,fieldtype 534 sys.exit ('ERROR: FIX: handle arrays in the buildPrint func') 535 536 #################### 537 ####### Comma separated values (csv) 538 #################### 539 o.write(''' elif 'csv'==format: 540 if None == options.fieldList: 541 options.fieldList = fieldList 542 needComma = False; 543 for field in fieldList: 544 if needComma: out.write(',') 545 needComma = True 546 if field in params: 547 out.write(str(params[field])) 548 # else: leave it empty 549 out.write("\\n") 550 ''') 551 552 #################### 553 ####### Other dispatchers 554 #################### 555 o.write('\telif \'html\'==format:\n') 556 o.write('\t\t'+printHtmlName+'(params,out)\n') 557 558 o.write(''' elif 'sql'==format: 559 sqlInsertStr(params,out,dbType=dbType) 560 ''') 561 562 if haveLocatableMessage(msgET): 563 # Has a lon/lat pair somewhere in there. 564 o.write('\telif \'kml\'==format:\n') 565 o.write('\t\t'+printKmlName+'(params,out)\n') 566 567 o.write('\telif \'kml-full\'==format:\n') 568 569 o.write('\t\tout.write(\"<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>\\n")\n') 570 o.write('\t\tout.write("<kml xmlns=\\"http://earth.google.com/kml/2.1\\">\\n")\n') 571 o.write('\t\tout.write("<Document>\\n")\n') 572 # o.write('\t\tout.write("\t<name>Position</name>\\n")\n') 573 o.write('\t\tout.write("\t<name>'+msgName+'</name>\\n")\n') 574 575 o.write('\t\t'+printKmlName+'(params,out)\n') 576 577 o.write('\t\tout.write("</Document>\\n")\n') 578 o.write('\t\tout.write("</kml>\\n")\n') 579 580 581 582 #################### 583 ####### Make safety check 584 #################### 585 o.write('\telse: \n') 586 o.write('\t\tprint "ERROR: unknown format:",format\n') 587 o.write('\t\tassert False\n') 588 589 o.write('\n\treturn # Nothing to return\n\n')
590 591 592 ###################################################################### 593 # Build Lookup tables for enumerated int or uint fields 594 ###################################################################### 595 596 597 ###################################################################### 598 # DELIMITED TEXT FILE 599 ###################################################################### 600
601 -def buildTextDef(o,msgET, verbose=False, prefixName=False):
602 ''' 603 Write functions for text definition output 604 605 @param o: open file where resulting code will be written 606 @param msgET: Element Tree starting at a message node 607 @param verbose: talk lots in the process 608 @param prefixName: set to a string to have the commands prefixed by that character. 609 610 @todo: should this be a style sheet thing instead? 611 ''' 612 assert(msgET.tag=='message') 613 msgName = msgET.attrib['name'] 614 print 'Generating text table definition ...', msgName 615 616 createFuncName = 'textDefinitionTable' 617 if prefixName: createFuncName = msgName+'textDefinitionTable' 618 619 o.write('''\n###################################################################### 620 # Text Definition 621 ###################################################################### 622 623 ''') 624 625 # Should this really default to true for the uscg fields? 626 o.write('def '+createFuncName+'''(outfile=sys.stdout 627 ,delim='\\t' 628 ): 629 \'\'\' 630 Return the text definition table for this message type 631 @param outfile: file like object to print to. 632 @type outfile: file obj 633 @return: text table string via the outfile 634 @rtype: str 635 636 \'\'\' 637 o = outfile 638 ''') 639 640 # NOTE: using / to mean one \ in the output compiled code 641 o.write('\to.write(\'\'\'Parameter\'\'\'+delim+\'Number of bits\'\'\'+delim+\'\'\'Description \n') 642 643 lines = [] 644 totalBits = 0 645 for field in msgET.xpath('field'): 646 fieldName = field.attrib['name'] 647 fieldType = field.attrib['type'] 648 fieldBits = field.attrib['numberofbits'] 649 if 'arraylength' in field.attrib: 650 fieldBits = str(int(fieldBits) * int(field.attrib['arraylength'])) 651 totalBits += int(fieldBits) 652 # FIX: maybe cut down to the first sentence? 653 fieldDescription = field.xpath('description')[0].text.replace('\n',' ') 654 lines.append(fieldName+'\'\'\'+delim+\'\'\''+fieldBits+'\'\'\'+delim+\'\'\''+fieldDescription) 655 656 o.write('\n'.join(lines)) 657 # 168 in the first slot and following slots get to use the whole 256?? FIX: is this right? 658 from math import ceil 659 numSlots = 1 + int( ceil((totalBits-168)/256.) ) 660 emptySlotBits = ( 168 + (numSlots-1)*256 ) - totalBits 661 s = '' 662 if numSlots>1: s = 's' 663 o.write(('\nTotal bits\'\'\'+delim+\'\'\''+str(totalBits)+'\'\'\'+delim+\'\'\'Appears to take '+str(numSlots)+' slot'+s)) 664 if emptySlotBits > 0: 665 o.write(' with '+str(emptySlotBits)+' pad bits to fill the last slot') 666 667 o.write( ('''\'\'\') 668 '''))
669 670 # FIX: should it return a copy of outfile? 671 672 673 ######################################################################
674 -def buildLaTeX(o,msgET, verbose=False, prefixName=False):
675 ''' 676 Write functions for LaTeX output 677 678 @param o: open file where resulting code will be written 679 @param msgET: Element Tree starting at a message node 680 @param verbose: talk lots in the process 681 @param prefixName: set to a string to have the commands prefixed by that character. 682 683 @todo: should this be a style sheet thing instead? 684 ''' 685 assert(msgET.tag=='message') 686 msgName = msgET.attrib['name'] 687 print 'Generating LaTeX definition table ...', msgName 688 689 createFuncName = 'latexDefinitionTable' 690 if prefixName: createFuncName = msgName+'LaTeXDefinitionTable' 691 692 o.write('''\n###################################################################### 693 # LATEX SUPPORT 694 ###################################################################### 695 696 ''') 697 698 # Should this really default to true for the uscg fields? 699 o.write('def '+createFuncName+'''(outfile=sys.stdout 700 ): 701 \'\'\' 702 Return the LaTeX definition table for this message type 703 @param outfile: file like object to print to. 704 @type outfile: file obj 705 @return: LaTeX table string via the outfile 706 @rtype: str 707 708 \'\'\' 709 o = outfile 710 ''') 711 712 # NOTE: using / to mean one \ in the output compiled code 713 o.write(''' 714 o.write(\'\'\' 715 /begin{table}%[htb] 716 /centering 717 /begin{tabular}{|l|c|l|} 718 /hline 719 Parameter & Number of bits & Description 720 // /hline/hline 721 '''.replace('/','\\\\')) 722 723 lines = [] 724 totalBits = 0 725 for field in msgET.xpath('field'): 726 fieldName = field.attrib['name'].replace('_','\_') # _ means subscript to latex 727 fieldType = field.attrib['type'] 728 fieldBits = field.attrib['numberofbits'] 729 if 'arraylength' in field.attrib: 730 fieldBits = str(int(fieldBits) * int(field.attrib['arraylength'])) 731 totalBits += int(fieldBits) 732 # FIX: maybe cut down to the first sentence? 733 fieldDescription = field.xpath('description')[0].text.replace('\n',' ') 734 lines.append(fieldName+' & '+fieldBits+' & '+fieldDescription) 735 736 o.write(' \\\\\\\\ \\hline \n'.join(lines)) 737 # 168 in the first slot and following slots get to use the whole 256?? FIX: is this right? 738 from math import ceil 739 numSlots = 1 + int( ceil((totalBits-168)/256.) ) 740 emptySlotBits = ( 168 + (numSlots-1)*256 ) - totalBits 741 s = '' 742 if numSlots>1: s = 's' 743 o.write(('// /hline /hline\nTotal bits & '+str(totalBits)+' & Appears to take '+str(numSlots)+' slot'+s).replace('/','\\\\')) 744 if emptySlotBits > 0: 745 o.write(' with '+str(emptySlotBits)+' pad bits to fill the last slot') 746 747 o.write( (''' // /hline 748 /end{tabular} 749 /caption{AIS message number '''+msgET.attrib['aismsgnum']+': '+msgET.xpath('description')[0].text.replace('\n',' ')+'''} 750 /label{tab:'''+msgET.attrib['name']+'''} 751 /end{table} 752 \'\'\') 753 ''').replace('/','\\\\'))
754 755 # FIX: should it return a copy of outfile? 756 757 758 ###################################################################### 759
760 -def buildSQL(o,msgET, verbose=False, prefixName=False):
761 ''' 762 Write SQL code 763 764 @param o: open file where resulting code will be written 765 @param msgET: Element Tree starting at a message node 766 @param verbose: talk lots in the process 767 @param prefixName: set to a string to have the commands prefixed by that character. 768 ''' 769 assert(msgET.tag=='message') 770 msgName = msgET.attrib['name'] 771 print 'Generating SQL commands ...', msgName 772 773 createFuncName = 'sqlCreate' 774 if prefixName: createFuncName = msgName+'SqlCreate' 775 insertFuncName = 'sqlInsert' 776 if prefixName: insertFuncName = msgName+'SqlInsert' 777 778 o.write('''###################################################################### 779 # SQL SUPPORT 780 ###################################################################### 781 782 ''') 783 784 # Should this really default to true for the uscg fields? 785 o.write('def '+createFuncName+'''Str(outfile=sys.stdout, fields=None, extraFields=None 786 ,addCoastGuardFields=True 787 ,dbType='postgres' 788 ): 789 \'\'\' 790 Return the SQL CREATE command for this message type 791 @param outfile: file like object to print to. 792 @param fields: which fields to put in the create. Defaults to all. 793 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 794 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 795 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres') 796 @type addCoastGuardFields: bool 797 @return: sql create string 798 @rtype: str 799 800 @see: sqlCreate 801 \'\'\' 802 # FIX: should this sqlCreate be the same as in LaTeX (createFuncName) rather than hard coded? 803 outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType))) 804 805 ''') 806 807 o.write('def '+createFuncName+'''(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'): 808 \'\'\' 809 Return the sqlhelp object to create the table. 810 811 @param fields: which fields to put in the create. Defaults to all. 812 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 813 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 814 @type addCoastGuardFields: bool 815 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres') 816 @return: An object that can be used to generate a return 817 @rtype: sqlhelp.create 818 \'\'\' 819 if None == fields: fields = fieldList 820 import sqlhelp 821 ''') 822 823 o.write('\tc = sqlhelp.create(\''+msgName+'\',dbType=dbType)\n') 824 o.write('\tc.addPrimaryKey()\n'); 825 826 for field in msgET.xpath('field'): 827 fieldName = field.attrib['name'] 828 fieldType = field.attrib['type'] 829 830 postgis = False 831 if 'postgisType' in field.attrib: 832 postgis=True 833 o.write('\tif dbType != \'postgres\':\n\t') 834 o.write('\tif \''+fieldName+'\' in fields: c.add') 835 # FIX: add the ADD command here. 836 if fieldType in ('int','uint'): o.write('Int (\''+fieldName+'\')') 837 elif fieldType in ('float' ): o.write('Real(\''+fieldName+'\')') 838 elif fieldType == 'bool': o.write('Bool(\''+fieldName+'\')') 839 elif fieldType in ('decimal','udecimal'): 840 if not hasSubTag(field,'decimalplaces'): 841 print '\n ** ERROR: missing decimalplaces field for ',fieldName,'\n' 842 assert (False) 843 scaleSQL = int(field.xpath('decimalplaces')[0].text) # number of decimal places 844 numBits = int(field.attrib['numberofbits']) 845 if not hasSubTag(field,'scale'): 846 print 'ERROR: missing required <scale> for',fieldName,'of type',fieldType 847 scaleVal = float(field.xpath('scale')[0].text) 848 precision = scaleSQL + len(str(int((2**numBits)/scaleVal))) 849 #precision += 1 # FIX: do I really need this safety factor 850 o.write('Decimal(\''+fieldName+'\','+str(precision)+','+str(scaleSQL)+')') 851 elif fieldType == 'binary': 852 numBits = int(field.attrib['numberofbits']) 853 if -1 == numBits: numBits=1024 # FIX: what is a good maximum for AIS? 854 o.write('BitVarying(\''+fieldName+'\','+str(numBits)+')') 855 elif fieldType == 'aisstr6': 856 arrayLength = int(field.attrib['arraylength']) 857 o.write('VarChar(\''+fieldName+'\','+str(arrayLength)+')') 858 else: 859 print '\n\n *** Unknown type in SQL code generation for field',fieldName+':',fieldType,'\n\n' 860 assert(False) 861 o.write('\n') 862 863 o.write(''' 864 if addCoastGuardFields: 865 # c.addInt('cg_rssi') # Relative signal strength indicator 866 # c.addInt('cg_d') # dBm receive strength 867 # c.addInt('cg_T') # Receive timestamp from the AIS equipment 868 # c.addInt('cg_S') # Slot received in 869 # c.addVarChar('cg_x',10) # Idonno 870 c.addVarChar('cg_r',15) # Receiver station ID - should usually be an MMSI, but sometimes is a string 871 c.addInt('cg_sec') # UTC seconds since the epoch 872 873 c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream 874 ''') 875 876 877 # 878 # Postgres with PostGIS added 879 # 880 postgisFields = msgET.xpath('field[@postgisType]') 881 if len(postgisFields)<1: 882 if verbose: print 'No postgis fields' 883 else: 884 o.write('\n\tif dbType == \'postgres\':\n') 885 finished=[] # Should be a set? 886 print 'processing postgis fields for create:' 887 for field in postgisFields: 888 #print ' ',field.attrib['name'],field.attrib['postgisName'] 889 pgName = field.attrib['postgisName'] 890 if pgName in finished: 891 print 'already handled',pgName 892 continue 893 finished.append(pgName) 894 pgType = field.attrib['postgisType'] 895 #print ' doing',pgName,pgType 896 components = msgET.xpath('field[@postgisName=\''+pgName+'\']') 897 #print ' all components' 898 #for c in components: 899 # print ' ',c.attrib['postgisName'] 900 o.write('\t\tc.addPostGIS(\''+pgName+'\',\''+pgType+'\','+str(len(components))+');\n') 901 902 o.write('\n\treturn c\n\n') 903 904 905 # FIX: function name 906 o.write('''def '''+insertFuncName+'''Str(params, outfile=sys.stdout, extraParams=None, dbType='postgres'): 907 \'\'\' 908 Return the SQL INSERT command for this message type 909 @param params: dictionary of values keyed by field name 910 @param outfile: file like object to print to. 911 @param extraParams: A sequence of tuples containing (name,sql type) for additional fields 912 @return: sql create string 913 @rtype: str 914 915 @see: sqlCreate 916 \'\'\' 917 outfile.write(str(sqlInsert(params,extraParams,dbType=dbType))) 918 919 920 ''') 921 922 # FIX: function name 923 o.write('''def '''+insertFuncName+'''(params,extraParams=None,dbType='postgres'): 924 \'\'\' 925 Give the SQL INSERT statement 926 @param params: dict keyed by field name of values 927 @param extraParams: any extra fields that you have created beyond the normal ais message fields 928 @rtype: sqlhelp.insert 929 @return: insert class instance 930 @todo: allow optional type checking of params? 931 @warning: this will take invalid keys happily and do what??? 932 \'\'\' 933 import sqlhelp 934 i = sqlhelp.insert(\'''' + msgName + '''\',dbType=dbType) 935 936 if dbType=='postgres': 937 finished = [] 938 for key in params: 939 if key in finished: 940 continue 941 942 if key not in toPgFields and key not in fromPgFields: 943 if type(params[key])==Decimal: i.add(key,float(params[key])) 944 else: i.add(key,params[key]) 945 else: 946 if key in fromPgFields: 947 val = params[key] 948 # Had better be a WKT type like POINT(-88.1 30.321) 949 i.addPostGIS(key,val) 950 finished.append(key) 951 else: 952 # Need to construct the type. 953 pgName = toPgFields[key] 954 #valStr='GeomFromText(\\''+pgTypes[pgName]+'(' 955 valStr=pgTypes[pgName]+'(' 956 vals = [] 957 for nonPgKey in fromPgFields[pgName]: 958 vals.append(str(params[nonPgKey])) 959 finished.append(nonPgKey) 960 valStr+=' '.join(vals)+')' 961 i.addPostGIS(pgName,valStr) 962 else: 963 for key in params: 964 if type(params[key])==Decimal: i.add(key,float(params[key])) 965 else: i.add(key,params[key]) 966 967 if None != extraParams: 968 for key in extraParams: 969 i.add(key,extraParams[key]) 970 971 return i 972 ''')
973 974 975 976 ###################################################################### 977 # Build Lookup tables for enumerated int or uint fields 978 ###################################################################### 979 980
981 -def buildLUT(o,msgET, verbose=False, prefixName=False):
982 ''' 983 Write lookup tables for enumerated types (uint or int, maybe bool too). 984 985 @todo: FIX: what to do about multiple entries with the same text? Need to ban that kind of thing 986 @todo: Make doc strings for each LUT. 987 988 @param o: open file where resulting code will be written 989 @param msgET: Element Tree starting at a message node 990 ''' 991 assert(msgET.tag=='message') 992 msgname = msgET.attrib['name'] 993 994 print 'Generating lookup tables ...',msgname # FIX: verbose? 995 996 for field in msgET.xpath('field'): 997 name = field.attrib['name'] 998 if not hasSubTag(field,'lookuptable'): continue 999 lut = field.xpath('lookuptable')[0] 1000 1001 lutName = name 1002 if prefixName: lutName = msgname+name.capitalize() 1003 1004 o.write(lutName+'EncodeLut = {\n') 1005 for entry in lut.xpath('entry'): 1006 o.write('\t\''+entry.text+'\':\''+entry.attrib['key']+'\',\n') 1007 o.write('\t} #'+lutName+'EncodeLut\n') 1008 o.write('\n') 1009 1010 # FIX: make doc string for LUT here 1011 1012 o.write(lutName+'DecodeLut = {\n') 1013 for entry in lut.xpath('entry'): 1014 o.write('\t\''+entry.attrib['key']+'\':\''+entry.text+'\',\n') 1015 o.write('\t} # '+lutName+'EncodeLut\n') 1016 o.write('\n')
1017 1018 # FIX: make doc string for LUT here 1019 1020 1021 1022 1023 ###################################################################### 1024 # ENCODERS 1025 ######################################################################
1026 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1027 ''' 1028 Build the encoder for boolean variables 1029 @type o: file like obj 1030 @param o: where write the code 1031 @type name: str 1032 @param name: field name 1033 @type type: str 1034 @param type: bool, etc. 1035 @type numbits: int = 1 1036 @param numbits: How many bits per unit datum (must be 1 for bools) 1037 @type required: bool or None 1038 @param required: If not None, then the value must be set to this. 1039 @type arraylen: int >= 1 1040 @param arraylen: many bools will there be? FIX: handle variable 1041 @type unavailable: bool or None 1042 @param unavailable: the default value to use if none given (if not None) 1043 @return: None 1044 ''' 1045 1046 if verbose: print 'bool encode',name,': unvail=',unavailable 1047 1048 assert type.lower()=='bool' 1049 assert numbits==1 1050 if arraylen != 1: assert False # FIX... handle arrays 1051 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n') 1052 if None != required: 1053 assert type(required)==bool 1054 if required: o.write('\t\tbvList.append(TrueBV)\n') 1055 else: o.write('\t\tbvList.append(FalseBV)\n') 1056 if verbose: o.write('\n') 1057 return 1058 1059 if None==unavailable: 1060 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n') 1061 o.write('\telse: bvList.append(FalseBV)\n') 1062 else: # Have a default value that can be filled in 1063 assert type(unavailable)==bool 1064 o.write("\tif '"+name+"' in params:\n") 1065 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n') 1066 o.write('\t\telse: bvList.append(FalseBV)\n') 1067 o.write('\telse:\n') 1068 if unavailable: o.write('\t\tbvList.append(TrueBV)\n') 1069 else: o.write('\t\tbvList.append(FalseBV)\n') 1070 if verbose: o.write('\n')
1071
1072 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1073 ''' 1074 Build the encoder for unsigned integer variables 1075 1076 @type o: file like obj 1077 @param o: where write the code 1078 @type name: str 1079 @param name: field name 1080 @type type: str 1081 @param type: uint, bool, etc. 1082 @type numbits: int >= 1 1083 @param numbits: How many bits per unit datum (must be 1..32) 1084 @type required: bool or None 1085 @param required: If not None, then the value must be set to this. 1086 @type arraylen: int >= 1 1087 @param arraylen: many unsigned ints will there be? FIX: handle variable 1088 @type unavailable: bool or None 1089 @param unavailable: the default value to use if none given (if not None) 1090 @return: None 1091 ''' 1092 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1093 1094 assert type=='uint' 1095 assert numbits>=1 and numbits<=32 1096 if arraylen != 1: assert False # FIX... handle arrays 1097 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1098 1099 if None != required: 1100 if verbose: print ' required:',required 1101 required=int(required) 1102 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n') 1103 if verbose: o.write('\n') 1104 return 1105 1106 if None==unavailable: 1107 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n') 1108 else: # Have a default value that can be filled in 1109 #assert type(unavailable)== 1110 int(unavailable) # Make sure unavailable is a number object 1111 o.write("\tif '"+name+"' in params:\n") 1112 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n') 1113 o.write('\telse:\n') 1114 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n') 1115 1116 if verbose: o.write('\n')
1117 1118
1119 -def encodeFloat(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1120 ''' 1121 Build the encoder for IEEE float variables 1122 1123 @type o: file like obj 1124 @param o: where write the code 1125 @type name: str 1126 @param name: field name 1127 @type fieldType: str 1128 @param fieldType: uint, bool, etc. 1129 @type numbits: int >= 1 1130 @param numbits: How many bits per unit datum (must be 1..32) 1131 @type required: bool or None 1132 @param required: If not None, then the value must be set to this. 1133 @type arraylen: int >= 1 1134 @param arraylen: many unsigned ints will there be? FIX: handle variable 1135 @type unavailable: bool or None 1136 @param unavailable: the default value to use if none given (if not None) 1137 @return: None 1138 ''' 1139 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 1140 1141 assert numbits==32 # Force by the IEEE spec 1142 if arraylen != 1: assert False # FIX... handle arrays 1143 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 1144 1145 if None != required: 1146 if verbose: print ' required:',required 1147 required=int(required) 1148 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n') 1149 if verbose: o.write('\n') 1150 return 1151 1152 if None==unavailable: 1153 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n') 1154 else: # Have a default value that can be filled in 1155 int(unavailable) # Make sure unavailable is a number object 1156 o.write("\tif '"+name+"' in params:\n") 1157 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n') 1158 o.write('\telse:\n') 1159 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n') 1160 1161 if verbose: o.write('\n')
1162 1163
1164 -def encodeAisstr6(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1165 ''' 1166 Build the encoder for aisstr6 variables. Generally are arrays. 1167 @bug: do we need to optionally check for a valid string? 1168 1169 @type o: file like obj 1170 @param o: where write the code 1171 @type name: str 1172 @param name: field name 1173 @type fieldType: str 1174 @param fieldType: uint, bool, etc. 1175 @type numbits: int >= 1 1176 @param numbits: How many bits per unit datum (must be 1..32) 1177 @type required: bool or None 1178 @param required: If not None, then the value must be set to this. 1179 @type arraylen: int >= 1 1180 @param arraylen: many unsigned ints will there be? FIX: handle variable 1181 @type unavailable: bool or None 1182 @param unavailable: the default value to use if none given (if not None) 1183 @return: None 1184 ''' 1185 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 1186 totLen = str(numbits*arraylen) 1187 assert numbits==6 # Each character must be 6 bits 1188 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 1189 1190 if None != required: 1191 if verbose: print ' required:',required 1192 required=int(required) 1193 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+totLen+'))\n') 1194 if verbose: o.write('\n') 1195 return 1196 1197 if None==unavailable: 1198 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 1199 else: # Have a default value that can be filled in 1200 o.write("\tif '"+name+"' in params:\n") 1201 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 1202 o.write('\telse:\n') 1203 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+totLen+'))\n') 1204 1205 if verbose: o.write('\n')
1206 1207
1208 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1209 ''' 1210 Build the encoder for signed integer variables 1211 1212 @type o: file like obj 1213 @param o: where write the code 1214 @type name: str 1215 @param name: field name 1216 @type type: str 1217 @param type: uint, bool, etc. 1218 @type numbits: int >= 1 1219 @param numbits: How many bits per unit datum (must be 1..32) 1220 @type required: bool or None 1221 @param required: If not None, then the value must be set to this. 1222 @type arraylen: int >= 1 1223 @param arraylen: many signed ints will there be? FIX: handle variable 1224 @type unavailable: number or None 1225 @param unavailable: the default value to use if none given (if not None) 1226 @return: None 1227 ''' 1228 if verbose: print ' encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1229 1230 assert numbits>=1 and numbits<=32 1231 if arraylen != 1: assert False # FIX... handle arrays 1232 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1233 1234 if None != required: 1235 if verbose: print ' required:',required 1236 required=int(required) 1237 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n') 1238 if verbose: o.write('\n') 1239 return 1240 1241 1242 if None==unavailable: 1243 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n') 1244 else: # Have a default value that can be filled in 1245 #assert type(unavailable)== 1246 int(unavailable) # Make sure unavailable is a number object 1247 o.write("\tif '"+name+"' in params:\n") 1248 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n') 1249 o.write('\telse:\n') 1250 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n') 1251 1252 if verbose: o.write('\n')
1253 1254 1255 1256 # FIX: Ummm... why am I passing the type? I guess it makes the one print statement easier
1257 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None, offset=None):
1258 ''' 1259 Build the encoder for signed decimal variables 1260 1261 @type o: file like obj 1262 @param o: where write the code 1263 @type name: str 1264 @param name: field name 1265 @type type: str 1266 @param type: decimal 1267 @type numbits: int >= 1 1268 @param numbits: How many bits per unit datum (must be 1..32) 1269 @type required: bool or None 1270 @param required: If not None, then the value must be set to this. 1271 @type arraylen: int >= 1 1272 @param arraylen: many decimals will there be? FIX: handle variable 1273 @type unavailable: Decimal or None 1274 @param unavailable: the default value to use if none given (if not None) 1275 @return: None 1276 ''' 1277 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1278 1279 assert numbits>=1 and numbits<=32 1280 if arraylen != 1: assert False # FIX... handle arrays 1281 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1282 1283 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 1284 if None == scale: 1285 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 1286 print 'Beware canadians bearing travel videos' 1287 scale='1' 1288 1289 if None != required: 1290 if verbose: print ' required:',required 1291 required=int(required) 1292 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n') 1293 if verbose: o.write('\n') 1294 return 1295 1296 offsetStr='' 1297 if None != offset: 1298 offsetStr='-('+offset+')' 1299 1300 # FIX: can I get rid of the Decimal around params? 1301 if None==unavailable: 1302 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 1303 else: # Have a default value that can be filled in 1304 o.write("\tif '"+name+"' in params:\n") 1305 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 1306 o.write('\telse:\n') 1307 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n') 1308 1309 if verbose: o.write('\n')
1310 1311 1312
1313 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None, offset=None):
1314 ''' 1315 Build the encoder 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 numbits: int >= 1 1324 @param numbits: How many bits per unit datum (must be 1..32) 1325 @type required: bool or None 1326 @param required: If not None, then the value must be set to this. 1327 @type arraylen: int >= 1 1328 @param arraylen: many decimals will there be? FIX: handle variable 1329 @type unavailable: Decimal or None 1330 @param unavailable: the default value to use if none given (if not None) 1331 @return: None 1332 ''' 1333 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1334 assert type=='udecimal' 1335 assert numbits>=1 and numbits<=32 1336 if arraylen != 1: assert False # FIX... handle arrays 1337 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1338 1339 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 1340 if None == scale: 1341 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 1342 print 'Beware canadians bearing travel videos' 1343 scale='1' 1344 1345 if None != required: 1346 if verbose: print ' required:',required 1347 required=int(required) 1348 assert(0<=required) 1349 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n') 1350 if verbose: o.write('\n') 1351 return 1352 1353 offsetStr='' 1354 if None != offset: 1355 offsetStr='-('+offset+')' 1356 1357 # FIX: can I get rid of the Decimal around params? 1358 if None==unavailable: 1359 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1360 else: # Have a default value that can be filled in 1361 o.write("\tif '"+name+"' in params:\n") 1362 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1363 o.write('\telse:\n') 1364 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n') 1365 1366 if verbose: o.write('\n')
1367 1368 1369
1370 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1371 ''' 1372 Build the encoder for binary variables. This is just a pass through. 1373 This is used for the ais binary message wrapper (e.g. msg 8). Do 1374 not use it within binary messages. 1375 1376 @type o: file like obj 1377 @param o: where write the code 1378 @type name: str 1379 @param name: field name 1380 @type type: str 1381 @param type: binary 1382 @type numbits: int >= 1 1383 @param numbits: How many bits per unit datum (must be 1..1024 or so) 1384 @type required: bool or None 1385 @param required: If not None, then the value must be set to this. 1386 @type arraylen: int >= 1 1387 @param arraylen: many decimals will there be? FIX: handle variable 1388 @type unavailable: Decimal or None 1389 @param unavailable: the default value to use if none given (if not None) 1390 @return: None 1391 ''' 1392 if verbose: print ' encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable 1393 assert type=='binary' 1394 assert (numbits>=1 and numbits<=1024) or numbits==-1 1395 assert (None == required) # don't allow this 1396 assert (None == unavailable) # don't allow this 1397 1398 if arraylen != 1: assert False # Do not handle arrays. Arrays of bits is just not necessary. 1399 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1400 1401 # FIX: can I get rid of the Decimal around params? 1402 o.write('\tbvList.append(params[\''+name+'\'])\n') # Just pass it through 1403 1404 if verbose: o.write('\n')
1405 1406 1407 ###################################################################### 1408 # DECODERS 1409 ###################################################################### 1410
1411 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1412 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1413 ''' 1414 Build the decoder for boolean variables 1415 1416 @type o: file like obj 1417 @param o: where write the code 1418 @type name: str 1419 @param name: field name 1420 @type type: str 1421 @param type: uint, bool, etc. 1422 @type startindex: int 1423 @param startindex: bit that begins the bool(s) 1424 @type numbits: int = 1 1425 @param numbits: How many bits per unit datum (must be 1 for bools) 1426 @type required: bool or None 1427 @param required: If not None, then the value must be set to this. 1428 @type arraylen: int >= 1 1429 @param arraylen: many bools will there be? FIX: handle variable 1430 @type unavailable: bool or None 1431 @param unavailable: the default value to use if none given (if not None) 1432 @type bv: str 1433 @param bv: BitVector containing the incoming data 1434 @type dataDict: str 1435 @param dataDict: dictionary in which to place the results 1436 @type decodeOnly: bool 1437 @param decodeOnly: Set to true to only get the code for decoding 1438 @rtype: int 1439 @return: index one past the end of where this read 1440 ''' 1441 assert(type=='bool') 1442 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1443 #int(startindex); int(numbits) # Make sure it is a number 1444 assert numbits==1 1445 assert arraylen == 1 # FIX... handle arrays 1446 1447 if None != required: 1448 assert type(required)==bool 1449 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1450 if required: o.write('True\n') 1451 else: o.write('False\n') 1452 if not decodeOnly: o.write('\n') 1453 return int(startindex)+int(numbits) 1454 1455 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1456 o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))') 1457 if not decodeOnly: o.write('\n') 1458 1459 return int(startindex)+int(numbits)
1460 1461
1462 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1463 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1464 ''' 1465 Build the decoder for unsigned integer variables 1466 1467 @type o: file like obj 1468 @param o: where write the code 1469 @type name: str 1470 @param name: field name 1471 @type type: str 1472 @param type: uint, etc. 1473 @type startindex: int 1474 @param startindex: bit that begins the uint(s) 1475 @type numbits: int >= 1 1476 @param numbits: How many bits per unit datum 1477 @type required: int or None 1478 @param required: If not None, then the value must be set to this. 1479 @type arraylen: int >= 1 1480 @param arraylen: many ints will there be? FIX: handle variable 1481 @type unavailable: int or None 1482 @param unavailable: the default value to use if none given (if not None) 1483 @type bv: str 1484 @param bv: BitVector containing the incoming data 1485 @type dataDict: str 1486 @param dataDict: dictionary in which to place the results 1487 @type decodeOnly: bool 1488 @param decodeOnly: Set to true to only get the code for decoding 1489 @rtype: int 1490 @return: index one past the end of where this read 1491 ''' 1492 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1493 if None==arraylen: arraylen=1 1494 assert arraylen == 1 # FIX... handle arrays 1495 assert numbits>=1 1496 if not decodeOnly: verbose=False 1497 1498 if None != required: 1499 int(required) # Make sure required is a number 1500 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1501 o.write(str(required)) 1502 if not decodeOnly: o.write('\n') 1503 return startindex+numbits 1504 1505 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1506 o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])') 1507 if not decodeOnly: o.write('\n') 1508 if verbose: o.write('\n') 1509 1510 return startindex+numbits
1511 1512
1513 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1514 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1515 ''' 1516 Build the decoder for unsigned integer variables 1517 1518 @type o: file like obj 1519 @param o: where write the code 1520 @type name: str 1521 @param name: field name 1522 @type type: str 1523 @param type: int 1524 @type startindex: int 1525 @param startindex: bit that begins the int(s) 1526 @type numbits: int >= 1 1527 @param numbits: How many bits per unit datum 1528 @type required: int or None 1529 @param required: If not None, then the value must be set to this. 1530 @type arraylen: int >= 1 1531 @param arraylen: many ints will there be? FIX: handle variable 1532 @type unavailable: int or None 1533 @param unavailable: the default value to use if none given (if not None) 1534 @type bv: str 1535 @param bv: BitVector containing the incoming data 1536 @type dataDict: str 1537 @param dataDict: dictionary in which to place the results 1538 @type decodeOnly: bool 1539 @param decodeOnly: Set to true to only get the code for decoding 1540 @rtype: int 1541 @return: index one past the end of where this read 1542 ''' 1543 assert type=='int' 1544 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1545 if None==arraylen: arraylen=1 1546 end = startindex+int(numbits)*int(arraylen) 1547 assert arraylen == 1 # FIX... handle arrays 1548 assert numbits>=1 1549 1550 if None != required: 1551 int(required) # Make sure required is a number 1552 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1553 o.write(str(required)) 1554 if not decodeOnly: o.write('\n') 1555 return end 1556 1557 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1558 o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])') 1559 if not decodeOnly: o.write('\n') 1560 if verbose: o.write('\n') 1561 1562 return end
1563
1564 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1565 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1566 ''' 1567 Build the decoder for IEEE float variables 1568 1569 @type o: file like obj 1570 @param o: where write the code 1571 @type name: str 1572 @param name: field name 1573 @type type: str 1574 @param type: int 1575 @type startindex: int 1576 @param startindex: bit that begins the int(s) 1577 @type numbits: int >= 1 1578 @param numbits: How many bits per unit datum 1579 @type required: float or None 1580 @param required: If not None, then the value must be set to this. 1581 @type arraylen: int >= 1 1582 @param arraylen: many ints will there be? FIX: handle variable 1583 @type unavailable: float or None 1584 @param unavailable: the default value to use if none given (if not None) 1585 @type bv: str 1586 @param bv: BitVector containing the incoming data 1587 @type dataDict: str 1588 @param dataDict: dictionary in which to place the results 1589 @type decodeOnly: bool 1590 @param decodeOnly: Set to true to only get the code for decoding 1591 @rtype: int 1592 @return: index one past the end of where this read 1593 ''' 1594 assert type=='float' 1595 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1596 if None==arraylen: arraylen=1 1597 end = startindex+int(numbits)*int(arraylen) 1598 assert arraylen == 1 # FIX... handle arrays 1599 assert numbits>=1 1600 1601 if None != required: 1602 float(required) # Make sure required is a number 1603 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1604 o.write(str(required)) 1605 if not decodeOnly: o.write('\n') 1606 if verbose: o.write('\n') 1607 return end 1608 1609 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1610 o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])') 1611 if not decodeOnly: o.write('\n') 1612 1613 return end
1614 1615
1616 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1617 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1618 ''' 1619 Build the decoder for aisstr6 variables. Generally arrays. 1620 @bug: FIX: validate strings?? 1621 @type o: file like obj 1622 @param o: where write the code 1623 @type name: str 1624 @param name: field name 1625 @type type: str 1626 @param type: 'aisstr6' 1627 @type startindex: int 1628 @param startindex: bit that begins the int(s) 1629 @type numbits: int >= 1 1630 @param numbits: How many bits per unit datum 1631 @type required: restricted str or None 1632 @param required: If not None, then the value must be set to this. 1633 @type arraylen: int >= 1 1634 @param arraylen: many ints will there be? FIX: handle variable 1635 @type unavailable: restricted str or None 1636 @param unavailable: the default value to use if none given (if not None) 1637 @type bv: str 1638 @param bv: BitVector containing the incoming data 1639 @type dataDict: str 1640 @param dataDict: dictionary in which to place the results 1641 @type decodeOnly: bool 1642 @param decodeOnly: Set to true to only get the code for decoding 1643 @rtype: int 1644 @return: index one past the end of where this read 1645 ''' 1646 assert type=='aisstr6' 1647 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1648 if None==arraylen: arraylen=1 1649 end = startindex+int(numbits)*int(arraylen) 1650 assert arraylen >= 1 # FIX... handle arrays 1651 assert numbits>=1 1652 1653 if None != required: 1654 float(required) # Make sure required is a number 1655 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1656 o.write(required) 1657 if not decodeOnly: o.write('\n') 1658 return end 1659 1660 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1661 o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])') 1662 if not decodeOnly: o.write('\n') 1663 1664 return end
1665 1666
1667 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1668 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False,offset=None):
1669 ''' 1670 Build the decoder for signed decimal variables 1671 1672 @type o: file like obj 1673 @param o: where write the code 1674 @type name: str 1675 @param name: field name 1676 @type type: str 1677 @param type: 'decimal' 1678 @type startindex: int 1679 @param startindex: bit that begins the int(s) 1680 @type numbits: int >= 1 1681 @param numbits: How many bits per unit datum 1682 @type required: Decimal or None 1683 @param required: If not None, then the value must be set to this. 1684 @type arraylen: int >= 1 1685 @param arraylen: many ints will there be? FIX: handle variable 1686 @type unavailable: Decimal or None 1687 @param unavailable: the default value to use if none given (if not None) 1688 @type bv: str 1689 @param bv: BitVector containing the incoming data 1690 @type dataDict: str 1691 @param dataDict: dictionary in which to place the results 1692 @type decodeOnly: bool 1693 @param decodeOnly: Set to true to only get the code for decoding 1694 @rtype: int 1695 @return: index one past the end of where this read 1696 ''' 1697 assert type=='decimal' 1698 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1699 if None==arraylen: arraylen=1 1700 end = startindex+int(numbits)*int(arraylen) 1701 assert arraylen == 1 # FIX... handle arrays 1702 assert numbits>=1 and numbits <= 32 1703 1704 if None == scale: scale='1' # Warning about this was in the encode section 1705 1706 if None != required: 1707 Decimal(required) # Make sure required is a number 1708 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1709 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1710 if not decodeOnly: o.write('\n') 1711 return end 1712 1713 offsetStr='' 1714 if offset != None: 1715 offsetStr += '+Decimal(\''+offset+'\')' 1716 1717 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1718 o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')'+offsetStr+'') 1719 if not decodeOnly: o.write('\n') 1720 1721 return end
1722 1723 1724 1725
1726 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1727 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False,offset=None):
1728 ''' 1729 Build the decoder for unsigned decimal variables 1730 1731 @type o: file like obj 1732 @param o: where write the code 1733 @type name: str 1734 @param name: field name 1735 @type type: str 1736 @param type: 'udecimal' 1737 @type startindex: int 1738 @param startindex: bit that begins the int(s) 1739 @type numbits: int >= 1 1740 @param numbits: How many bits per unit datum 1741 @type required: Decimal or None 1742 @param required: If not None, then the value must be set to this. 1743 @type arraylen: int >= 1 1744 @param arraylen: many ints will there be? FIX: handle variable 1745 @type unavailable: Decimal or None 1746 @param unavailable: the default value to use if none given (if not None) 1747 @type bv: str 1748 @param bv: BitVector containing the incoming data 1749 @type dataDict: str 1750 @param dataDict: dictionary in which to place the results 1751 @type decodeOnly: bool 1752 @param decodeOnly: Set to true to only get the code for decoding 1753 @rtype: int 1754 @return: index one past the end of where this read 1755 ''' 1756 assert type=='udecimal' 1757 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1758 if None==arraylen: arraylen=1 1759 end = startindex+int(numbits)*int(arraylen) 1760 assert arraylen == 1 # FIX... handle arrays 1761 assert numbits>=1 and numbits <= 32 1762 1763 if None == scale: scale='1' # Warning about this was in the encode section 1764 1765 if None != required: 1766 assert (Decimal(required)>=0.) # Make sure required is a number and not negative 1767 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1768 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1769 if not decodeOnly: o.write('\n') 1770 return end 1771 1772 offsetStr='' 1773 if offset != None: 1774 offsetStr += '+Decimal(\''+offset+'\')' 1775 1776 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1777 o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')'+offsetStr+'') 1778 if not decodeOnly: o.write('\n') 1779 1780 return end
1781 1782
1783 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1784 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1785 ''' 1786 Build the decoder for unsigned decimal variables 1787 1788 @type o: file like obj 1789 @param o: where write the code 1790 @type name: str 1791 @param name: field name 1792 @type type: str 1793 @param type: 'udecimal' 1794 @type startindex: int 1795 @param startindex: bit that begins the int(s) 1796 @type numbits: int >= 1 1797 @param numbits: How many bits per unit datum. If -1, then read to the end of the message 1798 @type required: Decimal or None 1799 @param required: If not None, then the value must be set to this. 1800 @type arraylen: int >= 1 1801 @param arraylen: many ints will there be? FIX: handle variable 1802 @type unavailable: Decimal or None 1803 @param unavailable: the default value to use if none given (if not None) 1804 @type bv: str 1805 @param bv: BitVector containing the incoming data 1806 @type dataDict: str 1807 @param dataDict: dictionary in which to place the results 1808 @type decodeOnly: bool 1809 @param decodeOnly: Set to true to only get the code for decoding 1810 @rtype: int 1811 @return: index one past the end of where this read 1812 ''' 1813 assert type=='binary' 1814 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1815 if None==arraylen: arraylen=1 1816 end = startindex+int(numbits)*int(arraylen) 1817 assert arraylen == 1 # FIX... handle arrays 1818 assert (numbits>=1 and numbits <= 1024) or -1==numbits # What is good max? 1819 # FIX: assert not required and not an array an not unavailable 1820 1821 1822 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1823 o.write(bv+'['+str(startindex)+':') 1824 if int(numbits) != -1: o.write(str(end)) # -1 means go to the end of the message 1825 o.write(']') 1826 if not decodeOnly: o.write('\n') 1827 1828 return end
1829 1830 1831 1832 ###################################################################### 1833 # THE REST 1834 ###################################################################### 1835 1836 1837
1838 -def buildTestParamFunc(o,msgET, verbose=False, prefixName=False):
1839 '''Scrape the testvalues to make a basic param 1840 1841 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies? 1842 ''' 1843 name = msgET.attrib['name'] 1844 1845 if prefixName: o.write('def '+name+'TestParams():\n') 1846 else: o.write('def testParams():\n') 1847 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") 1848 o.write('\tparams = {}\n') 1849 for field in msgET.xpath('field'): 1850 name = field.attrib['name'] 1851 type = field.attrib['type'] 1852 if verbose: print 'buildTestParamFunc ...',name,type 1853 val = None 1854 if hasSubTag(field,'testvalue') and hasSubTag(field,'required'): 1855 print 'ERROR: can not have both test value and required tags in the same field' 1856 assert(False) 1857 if hasSubTag(field,'testvalue'): 1858 val = field.xpath('testvalue')[0].text 1859 else: 1860 if not hasSubTag(field,'required'): 1861 sys.exit("ERROR: missing required or testvalue for field: "+name) 1862 val = field.xpath('required')[0].text 1863 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val 1864 o.write('\tparams[\''+name+'\'] = ') 1865 if type=='bool': 1866 if val=='1' or val.lower=='true': val = 'True' 1867 else: val = 'False' 1868 o.write(val) 1869 elif type in ('uint','int','float'): 1870 o.write(val) 1871 elif type in ('decimal','udecimal'): 1872 o.write('Decimal(\''+val+'\')') 1873 1874 elif type in ('aisstr6'): 1875 o.write('\''+val+'\'') 1876 elif type in ('binary'): 1877 o.write('BitVector(bitstring=\''+val+'\')') 1878 else: 1879 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee' 1880 suggestType(name,type) 1881 assert(False) 1882 1883 o.write('\n') 1884 1885 1886 o.write('\n\treturn params\n\n')
1887
1888 -def buildUnitTest(o,msgET, verbose=False, prefixName=False):
1889 ''' 1890 Write the unittests for a message 1891 1892 @param o: open file where resulting code will be written 1893 @param msgET: Element Tree starting at a message node 1894 ''' 1895 assert(msgET.tag=='message') 1896 name = msgET.attrib['name'] 1897 1898 buildTestParamFunc(o,msgET, prefixName=prefixName) 1899 1900 o.write('class Test'+name+'(unittest.TestCase):\n') 1901 o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n") 1902 o.write('\tdef testEncodeDecode(self):\n\n') 1903 if prefixName: 1904 o.write('\t\tparams = '+name+'TestParams()\n') 1905 o.write('\t\tbits = '+name+'Encode(params)\n') 1906 o.write('\t\tr = '+name+'Decode(bits)\n\n') 1907 else: 1908 o.write('\t\tparams = testParams()\n') 1909 o.write('\t\tbits = encode(params)\n') 1910 o.write('\t\tr = decode(bits)\n\n') 1911 1912 o.write('\t\t# Check that each parameter came through ok.\n') 1913 for field in msgET.xpath('field'): 1914 name = field.attrib['name'] 1915 type = field.attrib['type'] 1916 if type in ('bool','uint','int','aisstr6','binary'): 1917 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n') 1918 else: 1919 # float, decimal, udecimal 1920 # FIX: look up the decimal places if decimal 1921 places = '3' 1922 if hasSubTag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text 1923 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
1924 1925 1926
1927 -def buildEncode(o,msgET, verbose=False, prefixName=False):
1928 ''' 1929 Write the encoder/decoder for a message 1930 1931 http://jaynes.colorado.edu/PythonIdioms.html 1932 1933 @param o: open file where resulting code will be written 1934 @param msgET: Element Tree starting at a message node 1935 @todo: handle ais message 20 optional. very troublesome 1936 ''' 1937 assert(msgET.tag=='message') 1938 name = msgET.attrib['name'] # fix rename this variable to avoid hiding later on by field name 1939 1940 print 'Generating encoder ...',name # FIX: verbose? 1941 funcName = 'encode' 1942 if prefixName: funcName = name+'Encode' 1943 o.write('def '+funcName+'(params, validate=False):\n') 1944 1945 ######################################## 1946 # doc string 1947 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n") 1948 o.write('\tFields in params:\n') 1949 for field in msgET.xpath('field'): 1950 if verbose: 1951 print field.tag,field.attrib['name'] 1952 print field.attrib 1953 desc = field[0].text.replace('\n',' ') # get ride of new lines 1954 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1955 if len(field.xpath("required")) == 1: 1956 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1957 1958 o.write('\n') 1959 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n') 1960 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1961 1962 o.write("\t@rtype: BitVector\n") 1963 o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n") 1964 o.write("\t@note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.\n") 1965 o.write("\t'''\n\n") 1966 1967 ######################################## 1968 # Actually build the code 1969 1970 o.write('\tbvList = []\n') 1971 1972 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1973 1974 dynamicArrays = False # Set to true when positioning must be calculated 1975 1976 for field in msgET.xpath('field'): 1977 name = field.attrib['name'] 1978 type = field.attrib['type'] 1979 numbits = int(field.attrib['numberofbits']) 1980 required = None; 1981 if hasSubTag(field,'required'): 1982 required = field.xpath('required')[0].text 1983 unavailable=None; 1984 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1985 arraylen=1 1986 if 'arraylength' in field.attrib: 1987 arraylen=int(field.attrib['arraylength']) 1988 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1989 else: 1990 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 1991 1992 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable) 1993 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable) 1994 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable) 1995 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable) 1996 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable) 1997 elif type=='decimal': 1998 scale = None 1999 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2000 offset = None 2001 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2002 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale, offset=offset) 2003 elif type=='udecimal': 2004 scale = None 2005 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2006 offset = None 2007 if hasSubTag(field,'offset'): 2008 offset = field.xpath('offset')[0].text 2009 encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale,offset=offset) 2010 elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable) 2011 else: 2012 print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type 2013 suggestType (name,type) 2014 assert False 2015 2016 # o.write('\n\tbv=binary.joinBV(bvList)\n') 2017 # o.write('\n\tbvLen=len(bv)\n') 2018 # o.write('\n\tif bvLen%6!=0:\n') 2019 # o.write('\n\t bv = bv + BitVector(size=bvLen%6) # \n') 2020 # o.write('\n\treturn bv\n\n') 2021 o.write('\n\treturn binary.joinBV(bvList)\n\n')
2022 2023 2024 ###################################################################### 2025 # DECODER ONE PART AT A TIME - if only using a small part, save some cycles! 2026 2027 2028 #, msgDict=None):
2029 -def buildDecodeParts(o,msgET, verbose=False, prefixName=False):
2030 2031 ''' 2032 Write the decoder for a message 2033 2034 @param o: open file where resulting code will be written 2035 @type msgET: elementtree 2036 @param prefixName: if True, put the name of the message on the functions. 2037 @param msgET: Element Tree starting at a message node 2038 @return: None 2039 2040 @todo: FIX: doc strings for each decode! 2041 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 2042 @todo: May want to take a dictionary of already decoded fields to speed things that need prior info 2043 for things like variable length arrays 2044 ''' 2045 2046 assert(msgET.tag=='message') 2047 name = msgET.attrib['name'] 2048 2049 print 'Generating partial decode functions ...',name 2050 2051 baseName = name+'Decode' 2052 if not prefixName: baseName = 'decode' 2053 2054 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 2055 2056 for field in msgET.xpath('field'): 2057 name = field.attrib['name'] 2058 type = field.attrib['type'] 2059 2060 o.write('def '+baseName+name+'(bv, validate=False):\n') 2061 # Follow the same convention of decoding into a dict so that code is the same 2062 #o.write('\tr={};') 2063 o.write('\treturn ') 2064 2065 numbits = int(field.attrib['numberofbits']) 2066 required = None; 2067 if hasSubTag(field,'required'): 2068 required = field.xpath('required')[0].text 2069 unavailable=None; 2070 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 2071 arraylen=1 2072 if 'arraylength' in field.attrib: 2073 arraylen=int(field.attrib['arraylength']) 2074 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 2075 2076 assert None!=startindex 2077 if verbose: print 'startindex',startindex 2078 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2079 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2080 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2081 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2082 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2083 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2084 2085 elif type=='decimal': 2086 scale = None 2087 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2088 offset = None 2089 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2090 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True,offset=offset) 2091 elif type=='udecimal': 2092 scale = None 2093 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2094 offset = None 2095 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2096 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True,offset=offset) 2097 2098 else: 2099 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 2100 suggestType (name,type) 2101 assert False 2102 2103 #o.write('\treturn(r[\''+name+'\'])\n\n') 2104 o.write('\n\n')
2105 2106 2107 ###################################################################### 2108 # DECODER RING 2109
2110 -def buildDecode(o,msgET, verbose=False, prefixName=False):
2111 ''' 2112 Write the decoder for a message 2113 2114 @param o: open file where resulting code will be written 2115 @type msgET: elementtree 2116 @param msgET: Element Tree starting at a message node 2117 @return: None 2118 2119 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 2120 ''' 2121 2122 assert(msgET.tag=='message') 2123 name = msgET.attrib['name'] 2124 2125 print 'Generating decoder ...',name 2126 funcName = 'decode' 2127 if prefixName: funcName = name+'Decode' 2128 o.write('def '+funcName+'(bv, validate=False):\n') 2129 2130 ######################################## 2131 # doc string 2132 o.write("\t'''Unpack a "+name+" message \n\n") 2133 o.write('\tFields in params:\n') 2134 for field in msgET.xpath('field'): 2135 desc = field[0].text.replace('\n',' ') # get ride of new lines 2136 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 2137 if len(field.xpath("required")) == 1: 2138 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 2139 2140 o.write('\n') 2141 o.write('\t@type bv: BitVector\n') 2142 o.write('\t@param bv: Bits defining a message\n') 2143 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 2144 2145 o.write("\t@rtype: dict\n") 2146 o.write("\t@return: params\n") 2147 o.write("\t'''\n\n") 2148 2149 2150 o.write('\t#Would be nice to check the bit count here..\n') 2151 o.write('\t#if validate:\n') 2152 o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n') 2153 2154 2155 ######################################## 2156 # Actually build the code 2157 2158 o.write('\tr = {}\n') 2159 2160 2161 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 2162 2163 dynamicArrays = False # Set to true when positioning must be calculated 2164 2165 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 2166 2167 for field in msgET.xpath('field'): 2168 name = field.attrib['name'] 2169 type = field.attrib['type'] 2170 numbits = int(field.attrib['numberofbits']) 2171 required = None; 2172 if hasSubTag(field,'required'): 2173 required = field.xpath('required')[0].text 2174 unavailable=None; 2175 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 2176 arraylen=1 2177 if 'arraylength' in field.attrib: 2178 arraylen=int(field.attrib['arraylength']) 2179 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 2180 else: 2181 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 2182 2183 assert None!=startindex 2184 if verbose: print 'startindex',startindex 2185 2186 if hasSubTag(field,'optional'): 2187 # NoReturn means that this field is not a point that needs a check to see if we are done 2188 # FIX: need to do something more efficient 2189 if msgET.attrib['aismsgnum']!='20': 2190 sys.exit('optional is only allow on AIS msg 20. Do NOT use it on new messages!!!') 2191 text = field.xpath('optional')[0].text 2192 if None==field.xpath('optional')[0].text: # Found a NoReturn 2193 # Message 20 is a pain! 2194 pad = 8-(startindex%8) 2195 print 'not found: noreturn. pad=',pad 2196 assert(pad<=6) 2197 o.write('\tif len(bv)<='+str(startindex+pad)+': return r; # All fields below are optional\n') 2198 elif text != 'NoReturn': 2199 sys.exit ('ERROR: optional text must be NoReturn or empty') 2200 2201 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable) 2202 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 2203 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 2204 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable) 2205 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable) 2206 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable) 2207 2208 elif type=='decimal': 2209 scale = None 2210 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2211 offset = None 2212 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2213 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,offset=offset) 2214 2215 elif type=='udecimal': 2216 scale = None 2217 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2218 offset = None 2219 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2220 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,offset=offset) 2221 2222 else: 2223 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 2224 suggestType (name,type) 2225 assert False 2226 2227 2228 if None==startindex: print 'FIX: here. drat. treat me right' 2229 assert None!=startindex 2230 2231 2232 o.write('\treturn r\n\n')
2233 2234 2235 2236 #def getPythonType(aType): 2237 # ''' 2238 # Translate a 2239 # @rtype: str 2240 # @return: name of the python type 2241 # ''' 2242 2243 aisType2pythonType={ 2244 'bool':'Bool', 2245 'uint':'int', 2246 'int':'int', 2247 'udecimal':'Decimal', 2248 'decimal':'Decimal', 2249 'aisstr6':'str', 2250 'binary':'str', 2251 'float':'Float', 2252 } 2253 2254 import copy 2255 aisType2optParseType=copy.deepcopy(aisType2pythonType) 2256 aisType2optParseType['bool']='int' # FIX: make these appear as a flag variable 2257 aisType2optParseType['aisstr6']='string' 2258 aisType2optParseType['aisstr6']='string' 2259 aisType2optParseType['binary']='string' 2260 aisType2optParseType['udecimal']='string' # FIX: Is this a good choice? 2261 aisType2optParseType['decimal']='string' 2262 aisType2optParseType['float']='float' 2263 2264
2265 -def buildOptParse(o,msgET, prefixName=False):
2266 '''Create a function that adds the options to a parse object''' 2267 2268 assert None != msgET 2269 assert msgET.tag=='message' 2270 msgName = msgET.attrib['name'] 2271 2272 prefix='' 2273 if prefixName: prefix=msgName 2274 2275 funcName = 'addMsgOptions' 2276 if prefixName: funcName = msgName + 'AddMsgOptions' 2277 o.write('\ndef '+funcName+'(parser):') 2278 2279 # FIX: write out a doc string 2280 2281 o.write(''' 2282 parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true', 2283 help='decode a "'''+msgName+'''" AIS message') 2284 parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true', 2285 help='encode a "'''+msgName+'''" AIS message') 2286 ''') 2287 2288 #print 'here...', msgName, prefixName 2289 for field in msgET.xpath('field'): 2290 name = field.attrib['name'] 2291 fieldType = field.attrib['type'] 2292 if hasSubTag(field,'required'): 2293 print 'skipping required field ...',name,fieldType 2294 continue 2295 #print 'there',name,fieldType 2296 o.write('\tparser.add_option(\'--') 2297 if prefixName: o.write(msgName+'-') 2298 o.write(name+'-field\', dest=\''+name+'Field\'') 2299 if hasSubTag(field,'unavailable'): 2300 val = field.xpath('unavailable')[0].text 2301 o.write(',default=') 2302 if fieldType in ('uint','int','float'): 2303 o.write(val) 2304 elif fieldType in ('decimal','udecimal'): 2305 o.write('Decimal(\''+val+'\')') 2306 elif fieldType in ('aisstr6','bitvector'): 2307 o.write('\''+val+'\'') 2308 o.write(',metavar=\''+fieldType+'\',type=\''+aisType2optParseType[fieldType]+'\'') 2309 o.write('\n\t\t,help=\'Field parameter value [default: %default]\')\n')
2310 2311 2312
2313 -def buildMain(o, msgET, prefixName=False):
2314 assert None != msgET 2315 assert msgET.tag=='message' 2316 msgName = msgET.attrib['name'] 2317 2318 prefix='' 2319 if prefixName: prefix=msgName 2320 2321 buildOptParse(o, msgET, prefixName) 2322 2323 2324 o.write(''' 2325 ############################################################ 2326 if __name__=='__main__': 2327 2328 from optparse import OptionParser 2329 parser = OptionParser(usage="%prog [options]", 2330 version="%prog "+__version__) 2331 2332 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 2333 help='run the documentation tests') 2334 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true', 2335 help='run the unit tests') 2336 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 2337 help='Make the test output verbose') 2338 2339 # FIX: remove nmea from binary messages. No way to build the whole packet? 2340 # FIX: or build the surrounding msg 8 for a broadcast? 2341 typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message? 2342 parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType' 2343 ,default='nmeapayload' 2344 ,help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]') 2345 2346 2347 outputChoices = ('std','html','csv','sql' ''') 2348 # 'xml' - FIX: need to add xml output 2349 if haveLocatableMessage(msgET): o.write(', \'kml\',\'kml-full\'') 2350 o.write(''') 2351 parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType' 2352 ,default='std' 2353 ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]') 2354 2355 parser.add_option('-o','--output',dest='outputFileName',default=None, 2356 help='Name of the python file to write [default: stdout]') 2357 2358 parser.add_option('-f','--fields',dest='fieldList',default=None, action='append', 2359 choices=fieldList, 2360 help='Which fields to include in the output. Currently only for csv output [default: all]') 2361 2362 parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true', 2363 help='Print the field name for csv') 2364 2365 parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true', 2366 help='Print out an sql create command for the table.') 2367 2368 parser.add_option('--latex-table',dest='latexDefinitionTable',default=False,action='store_true', 2369 help='Print a LaTeX table of the type') 2370 2371 parser.add_option('--text-table',dest='textDefinitionTable',default=False,action='store_true', 2372 help='Print delimited table of the type (for Word table importing)') 2373 parser.add_option('--delimt-text-table',dest='delimTextDefinitionTable',default='\\t' 2374 ,help='Delimiter for text table [default: \\\'%default\\\'](for Word table importing)') 2375 2376 2377 dbChoices = ('sqlite','postgres') 2378 parser.add_option('-D','--db-type',dest='dbType',default='postgres' 2379 ,choices=dbChoices,type='choice' 2380 ,help='What kind of database ('+', '.join(dbChoices)+') [default: %default]') 2381 2382 ''') 2383 2384 o.write('''\taddMsgOptions(parser)\n''') 2385 2386 o.write(''' 2387 (options,args) = parser.parse_args() 2388 success=True 2389 2390 if options.doctest: 2391 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 2392 sys.argv= [sys.argv[0]] 2393 if options.verbose: sys.argv.append('-v') 2394 import doctest 2395 numfail,numtests=doctest.testmod() 2396 if numfail==0: print 'ok' 2397 else: 2398 print 'FAILED' 2399 success=False 2400 2401 if not success: sys.exit('Something Failed') 2402 del success # Hide success from epydoc 2403 2404 if options.unittest: 2405 sys.argv = [sys.argv[0]] 2406 if options.verbose: sys.argv.append('-v') 2407 unittest.main() 2408 2409 outfile = sys.stdout 2410 if None!=options.outputFileName: 2411 outfile = file(options.outputFileName,'w') 2412 2413 ''') 2414 2415 2416 2417 ############################## 2418 # encode 2419 ############################## 2420 # FIX: make everything tabs. Grr. 2421 o.write('\n\tif options.doEncode:\n') 2422 o.write('\t\t# First make sure all non required options are specified\n') 2423 for field in msgET.xpath('field'): 2424 name = field.attrib['name'] 2425 fieldType = field.attrib['type'] 2426 varName = prefix+name+'Field' 2427 if not hasSubTag(field,'required'): 2428 o.write('\t\tif None==options.'+varName+': parser.error("missing value for '+varName+'")\n') 2429 2430 # Build dict 2431 o.write('\t\tmsgDict={\n') 2432 for field in msgET.xpath('field'): 2433 name = field.attrib['name'] 2434 varName = prefix+name+'Field' 2435 if hasSubTag(field,'required'): 2436 o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n') 2437 else: 2438 o.write('\t\t\t\''+name+'\': options.'+varName+',\n') 2439 o.write('\t\t}\n') 2440 2441 encodeFunction = 'encode' 2442 if prefixName: encodeFunction = msgName+'Encode' 2443 o.write(''' 2444 bits = '''+encodeFunction+'''(msgDict) 2445 if 'binary'==options.ioType: print str(bits) 2446 elif 'nmeapayload'==options.ioType: 2447 # FIX: figure out if this might be necessary at compile time 2448 print "bitLen",len(bits) 2449 bitLen=len(bits) 2450 if bitLen%6!=0: 2451 bits = bits + BitVector(size=(6 - (bitLen%6))) # Pad out to multiple of 6 2452 print "result:",binary.bitvectoais6(bits)[0] 2453 2454 2455 # FIX: Do not emit this option for the binary message payloads. Does not make sense. 2456 elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability") 2457 else: sys.exit('ERROR: unknown ioType. Help!') 2458 ''') 2459 2460 2461 ############################## 2462 # decode all 2463 ############################## 2464 decodeFunction = 'decode' 2465 printFields='printFields' 2466 if prefixName: 2467 decodeFunction = msgName+'Decode' 2468 printFields = msgName+'PrintFields' 2469 o.write(''' 2470 2471 if options.sqlCreate: 2472 sqlCreateStr(outfile,options.fieldList,dbType=options.dbType) 2473 2474 if options.latexDefinitionTable: 2475 latexDefinitionTable(outfile) 2476 2477 # For conversion to word tables 2478 if options.textDefinitionTable: 2479 textDefinitionTable(outfile,options.delimTextDefinitionTable) 2480 2481 if options.printCsvfieldList: 2482 # Make a csv separated list of fields that will be displayed for csv 2483 if None == options.fieldList: options.fieldList = fieldList 2484 import StringIO 2485 buf = StringIO.StringIO() 2486 for field in options.fieldList: 2487 buf.write(field+',') 2488 result = buf.getvalue() 2489 if result[-1] == ',': print result[:-1] 2490 else: print result 2491 2492 if options.doDecode: 2493 if len(args)==0: args = sys.stdin 2494 for msg in args: 2495 bv = None 2496 2497 if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'): 2498 # Found nmea 2499 # FIX: do checksum 2500 bv = binary.ais6tobitvec(msg.split(',')[5]) 2501 else: # either binary or nmeapayload... expect mostly nmeapayloads 2502 # assumes that an all 0 and 1 string can not be a nmeapayload 2503 binaryMsg=True 2504 for c in msg: 2505 if c not in ('0','1'): 2506 binaryMsg=False 2507 break 2508 if binaryMsg: 2509 bv = BitVector(bitstring=msg) 2510 else: # nmeapayload 2511 bv = binary.ais6tobitvec(msg) 2512 2513 '''+printFields+'''('''+decodeFunction+'''(bv) 2514 ,out=outfile 2515 ,format=options.outputType 2516 ,fieldList=options.fieldList 2517 ,dbType=options.dbType 2518 ) 2519 2520 ''')
2521 2522 2523 ###################################################################### 2524 if __name__=='__main__': 2525 from optparse import OptionParser 2526 parser = OptionParser(usage="%prog [options]", 2527 version="%prog "+__version__) 2528 2529 parser.add_option('-o','--output',dest='outputFileName',default=None, 2530 help='Name of the python file to write') 2531 # help='Name of the python file to write [default: stdout]') 2532 2533 parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None, 2534 help='XML definition file for the msg to use') 2535 # help='XML definition file for the msg to use [default: stdin]') 2536 2537 # parser.add_option('-m','--message',dest='message',default=None, 2538 # help='Which message to write code for [default: all]') 2539 2540 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 2541 help='run the documentation tests') 2542 2543 parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true', 2544 help='put the field name in front of all function names.' 2545 +' Allows multiple messages in one file') 2546 2547 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 2548 help='run the tests run in verbose mode') 2549 2550 (options,args) = parser.parse_args() 2551 2552 success=True 2553 2554 if options.doctest: 2555 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 2556 argvOrig = sys.argv 2557 sys.argv= [sys.argv[0]] 2558 if options.verbose: sys.argv.append('-v') 2559 import doctest 2560 numfail,numtests=doctest.testmod() 2561 if numfail==0: print 'ok' 2562 else: 2563 print 'FAILED' 2564 success=False 2565 sys.argv = argvOrig # Restore the original args 2566 del argvOrig # hide from epydoc 2567 sys.exit() # FIX: Will this exit success? 2568 2569 #infile=sys.stdin 2570 #if options.xmlFileName: infile = file(options.xmlFileName,'r') 2571 2572 #outfile=sys.stdout 2573 #if options.outputFile: outfile = file(options.xmlDefinition,'w') 2574 2575 # FIX: down the road, it would be good to allow either filenames of std{in/out} 2576 2577 if None==options.xmlFileName: 2578 sys.exit('ERROR: must specify an xml definition file.') 2579 if None==options.outputFileName: 2580 sys.exit('ERROR: must specify an python file to write to.') 2581 generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix, verbose=options.verbose) 2582 2583 print '\nrecommend running pychecker like this:' 2584 print ' pychecker -q',options.outputFileName 2585