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