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