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,verbose=verbose) 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 # if verbose: 751 # sys.stderr.write (' msgnum:'+msgET.attrib['aismsgnum'] +' - ') 752 # sys.stderr.write (' desc: '+ str(msgET.xpath('description')[0])+' - ') 753 # sys.stderr.write (' name: '+ str(msgET.attrib['name'])+'\n') 754 #sys.stderr.write (' :'+ str()+'\n') 755 756 o.write( (''' // /hline 757 /end{tabular} 758 /caption{AIS message number '''+msgET.attrib['aismsgnum']+': '+msgET.xpath('description')[0].text.replace('\n',' ')+'''} 759 /label{tab:'''+msgET.attrib['name']+'''} 760 /end{table} 761 \'\'\') 762 ''').replace('/','\\\\'))
763 764 # FIX: should it return a copy of outfile? 765 766 767 ###################################################################### 768
769 -def buildSQL(o,msgET, verbose=False, prefixName=False):
770 ''' 771 Write SQL code 772 773 @param o: open file where resulting code will be written 774 @param msgET: Element Tree starting at a message node 775 @param verbose: talk lots in the process 776 @param prefixName: set to a string to have the commands prefixed by that character. 777 ''' 778 assert(msgET.tag=='message') 779 msgName = msgET.attrib['name'] 780 print 'Generating SQL commands ...', msgName 781 782 createFuncName = 'sqlCreate' 783 if prefixName: createFuncName = msgName+'SqlCreate' 784 insertFuncName = 'sqlInsert' 785 if prefixName: insertFuncName = msgName+'SqlInsert' 786 787 o.write('''###################################################################### 788 # SQL SUPPORT 789 ###################################################################### 790 791 ''') 792 793 o.write('dbTableName=\''+msgName+'\'\n') 794 o.write('\'Database table name\'\n\n') 795 796 # Should this really default to true for the uscg fields? 797 o.write('def '+createFuncName+'''Str(outfile=sys.stdout, fields=None, extraFields=None 798 ,addCoastGuardFields=True 799 ,dbType='postgres' 800 ): 801 \'\'\' 802 Return the SQL CREATE command for this message type 803 @param outfile: file like object to print to. 804 @param fields: which fields to put in the create. Defaults to all. 805 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 806 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 807 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres') 808 @type addCoastGuardFields: bool 809 @return: sql create string 810 @rtype: str 811 812 @see: sqlCreate 813 \'\'\' 814 # FIX: should this sqlCreate be the same as in LaTeX (createFuncName) rather than hard coded? 815 outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType))) 816 817 ''') 818 819 o.write('def '+createFuncName+'''(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'): 820 \'\'\' 821 Return the sqlhelp object to create the table. 822 823 @param fields: which fields to put in the create. Defaults to all. 824 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields 825 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format 826 @type addCoastGuardFields: bool 827 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres') 828 @return: An object that can be used to generate a return 829 @rtype: sqlhelp.create 830 \'\'\' 831 if None == fields: fields = fieldList 832 import sqlhelp 833 ''') 834 835 o.write('\tc = sqlhelp.create(\''+msgName+'\',dbType=dbType)\n') 836 o.write('\tc.addPrimaryKey()\n'); 837 838 for field in msgET.xpath('field'): 839 fieldName = field.attrib['name'] 840 fieldType = field.attrib['type'] 841 842 postgis = False 843 if 'postgisType' in field.attrib: 844 postgis=True 845 o.write('\tif dbType != \'postgres\':\n\t') 846 o.write('\tif \''+fieldName+'\' in fields: c.add') 847 # FIX: add the ADD command here. 848 if fieldType in ('int','uint'): o.write('Int (\''+fieldName+'\')') 849 elif fieldType in ('float' ): o.write('Real(\''+fieldName+'\')') 850 elif fieldType == 'bool': o.write('Bool(\''+fieldName+'\')') 851 elif fieldType in ('decimal','udecimal'): 852 if not hasSubTag(field,'decimalplaces'): 853 print '\n ** ERROR: missing decimalplaces field for ',fieldName,'\n' 854 assert (False) 855 scaleSQL = int(field.xpath('decimalplaces')[0].text) # number of decimal places 856 numBits = int(field.attrib['numberofbits']) 857 if not hasSubTag(field,'scale'): 858 print 'ERROR: missing required <scale> for',fieldName,'of type',fieldType 859 scaleVal = float(field.xpath('scale')[0].text) 860 precision = scaleSQL + len(str(int((2**numBits)/scaleVal))) 861 #precision += 1 # FIX: do I really need this safety factor 862 o.write('Decimal(\''+fieldName+'\','+str(precision)+','+str(scaleSQL)+')') 863 elif fieldType == 'binary': 864 numBits = int(field.attrib['numberofbits']) 865 if -1 == numBits: numBits=1024 # FIX: what is a good maximum for AIS? 866 o.write('BitVarying(\''+fieldName+'\','+str(numBits)+')') 867 elif fieldType == 'aisstr6': 868 arrayLength = int(field.attrib['arraylength']) 869 o.write('VarChar(\''+fieldName+'\','+str(arrayLength)+')') 870 else: 871 print '\n\n *** Unknown type in SQL code generation for field',fieldName+':',fieldType,'\n\n' 872 assert(False) 873 o.write('\n') 874 875 o.write(''' 876 if addCoastGuardFields: 877 # c.addInt('cg_rssi') # Relative signal strength indicator 878 # c.addInt('cg_d') # dBm receive strength 879 # c.addInt('cg_T') # Receive timestamp from the AIS equipment 880 # c.addInt('cg_S') # Slot received in 881 # c.addVarChar('cg_x',10) # Idonno 882 c.addVarChar('cg_r',15) # Receiver station ID - should usually be an MMSI, but sometimes is a string 883 c.addInt('cg_sec') # UTC seconds since the epoch 884 885 c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream 886 ''') 887 888 889 # 890 # Postgres with PostGIS added 891 # 892 postgisFields = msgET.xpath('field[@postgisType]') 893 if len(postgisFields)<1: 894 if verbose: print 'No postgis fields' 895 else: 896 o.write('\n\tif dbType == \'postgres\':\n') 897 finished=[] # Should be a set? 898 print 'processing postgis fields for create:' 899 for field in postgisFields: 900 #print ' ',field.attrib['name'],field.attrib['postgisName'] 901 pgName = field.attrib['postgisName'] 902 if pgName in finished: 903 print 'already handled',pgName 904 continue 905 finished.append(pgName) 906 pgType = field.attrib['postgisType'] 907 #print ' doing',pgName,pgType 908 components = msgET.xpath('field[@postgisName=\''+pgName+'\']') 909 #print ' all components' 910 #for c in components: 911 # print ' ',c.attrib['postgisName'] 912 o.write('\t\t#--- EPSG 4326 : WGS 84\n') 913 914 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''') 915 916 o.write('\t\tc.addPostGIS(\''+pgName+'\',\''+pgType+'\','+str(len(components))+',SRID=4326);\n') 917 918 o.write('\n\treturn c\n\n') 919 920 921 # FIX: function name 922 o.write('''def '''+insertFuncName+'''Str(params, outfile=sys.stdout, extraParams=None, dbType='postgres'): 923 \'\'\' 924 Return the SQL INSERT command for this message type 925 @param params: dictionary of values keyed by field name 926 @param outfile: file like object to print to. 927 @param extraParams: A sequence of tuples containing (name,sql type) for additional fields 928 @return: sql create string 929 @rtype: str 930 931 @see: sqlCreate 932 \'\'\' 933 outfile.write(str(sqlInsert(params,extraParams,dbType=dbType))) 934 935 936 ''') 937 938 # FIX: function name 939 o.write('''def '''+insertFuncName+'''(params,extraParams=None,dbType='postgres'): 940 \'\'\' 941 Give the SQL INSERT statement 942 @param params: dict keyed by field name of values 943 @param extraParams: any extra fields that you have created beyond the normal ais message fields 944 @rtype: sqlhelp.insert 945 @return: insert class instance 946 @todo: allow optional type checking of params? 947 @warning: this will take invalid keys happily and do what??? 948 \'\'\' 949 import sqlhelp 950 i = sqlhelp.insert(\'''' + msgName + '''\',dbType=dbType) 951 952 if dbType=='postgres': 953 finished = [] 954 for key in params: 955 if key in finished: 956 continue 957 958 if key not in toPgFields and key not in fromPgFields: 959 if type(params[key])==Decimal: i.add(key,float(params[key])) 960 else: i.add(key,params[key]) 961 else: 962 if key in fromPgFields: 963 val = params[key] 964 # Had better be a WKT type like POINT(-88.1 30.321) 965 i.addPostGIS(key,val) 966 finished.append(key) 967 else: 968 # Need to construct the type. 969 pgName = toPgFields[key] 970 #valStr='GeomFromText(\\''+pgTypes[pgName]+'(' 971 valStr=pgTypes[pgName]+'(' 972 vals = [] 973 for nonPgKey in fromPgFields[pgName]: 974 vals.append(str(params[nonPgKey])) 975 finished.append(nonPgKey) 976 valStr+=' '.join(vals)+')' 977 i.addPostGIS(pgName,valStr) 978 else: 979 for key in params: 980 if type(params[key])==Decimal: i.add(key,float(params[key])) 981 else: i.add(key,params[key]) 982 983 if None != extraParams: 984 for key in extraParams: 985 i.add(key,extraParams[key]) 986 987 return i 988 ''')
989 990 991 992 ###################################################################### 993 # Build Lookup tables for enumerated int or uint fields 994 ###################################################################### 995 996
997 -def buildLUT(o,msgET, verbose=False, prefixName=False):
998 ''' 999 Write lookup tables for enumerated types (uint or int, maybe bool too). 1000 1001 @todo: FIX: what to do about multiple entries with the same text? Need to ban that kind of thing 1002 @todo: Make doc strings for each LUT. 1003 1004 @param o: open file where resulting code will be written 1005 @param msgET: Element Tree starting at a message node 1006 ''' 1007 assert(msgET.tag=='message') 1008 msgname = msgET.attrib['name'] 1009 1010 print 'Generating lookup tables ...',msgname # FIX: verbose? 1011 1012 for field in msgET.xpath('field'): 1013 name = field.attrib['name'] 1014 if not hasSubTag(field,'lookuptable'): continue 1015 lut = field.xpath('lookuptable')[0] 1016 1017 lutName = name 1018 if prefixName: lutName = msgname+name.capitalize() 1019 1020 o.write(lutName+'EncodeLut = {\n') 1021 for entry in lut.xpath('entry'): 1022 o.write('\t\''+entry.text+'\':\''+entry.attrib['key']+'\',\n') 1023 o.write('\t} #'+lutName+'EncodeLut\n') 1024 o.write('\n') 1025 1026 # FIX: make doc string for LUT here 1027 1028 o.write(lutName+'DecodeLut = {\n') 1029 for entry in lut.xpath('entry'): 1030 o.write('\t\''+entry.attrib['key']+'\':\''+entry.text+'\',\n') 1031 o.write('\t} # '+lutName+'EncodeLut\n') 1032 o.write('\n')
1033 1034 # FIX: make doc string for LUT here 1035 1036 1037 1038 1039 ###################################################################### 1040 # ENCODERS 1041 ######################################################################
1042 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1043 ''' 1044 Build the encoder for boolean variables 1045 @type o: file like obj 1046 @param o: where write the code 1047 @type name: str 1048 @param name: field name 1049 @type type: str 1050 @param type: bool, etc. 1051 @type numbits: int = 1 1052 @param numbits: How many bits per unit datum (must be 1 for bools) 1053 @type required: bool or None 1054 @param required: If not None, then the value must be set to this. 1055 @type arraylen: int >= 1 1056 @param arraylen: many bools will there be? FIX: handle variable 1057 @type unavailable: bool or None 1058 @param unavailable: the default value to use if none given (if not None) 1059 @return: None 1060 ''' 1061 1062 if verbose: print 'bool encode',name,': unvail=',unavailable 1063 1064 assert type.lower()=='bool' 1065 assert numbits==1 1066 if arraylen != 1: assert False # FIX... handle arrays 1067 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n') 1068 if None != required: 1069 assert type(required)==bool 1070 if required: o.write('\t\tbvList.append(TrueBV)\n') 1071 else: o.write('\t\tbvList.append(FalseBV)\n') 1072 if verbose: o.write('\n') 1073 return 1074 1075 if None==unavailable: 1076 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n') 1077 o.write('\telse: bvList.append(FalseBV)\n') 1078 else: # Have a default value that can be filled in 1079 assert type(unavailable)==bool 1080 o.write("\tif '"+name+"' in params:\n") 1081 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n') 1082 o.write('\t\telse: bvList.append(FalseBV)\n') 1083 o.write('\telse:\n') 1084 if unavailable: o.write('\t\tbvList.append(TrueBV)\n') 1085 else: o.write('\t\tbvList.append(FalseBV)\n') 1086 if verbose: o.write('\n')
1087
1088 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1089 ''' 1090 Build the encoder for unsigned integer variables 1091 1092 @type o: file like obj 1093 @param o: where write the code 1094 @type name: str 1095 @param name: field name 1096 @type type: str 1097 @param type: uint, bool, etc. 1098 @type numbits: int >= 1 1099 @param numbits: How many bits per unit datum (must be 1..32) 1100 @type required: bool or None 1101 @param required: If not None, then the value must be set to this. 1102 @type arraylen: int >= 1 1103 @param arraylen: many unsigned ints will there be? FIX: handle variable 1104 @type unavailable: bool or None 1105 @param unavailable: the default value to use if none given (if not None) 1106 @return: None 1107 ''' 1108 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1109 1110 assert type=='uint' 1111 assert numbits>=1 and numbits<=32 1112 if arraylen != 1: assert False # FIX... handle arrays 1113 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1114 1115 if None != required: 1116 if verbose: print ' required:',required 1117 required=int(required) 1118 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n') 1119 if verbose: o.write('\n') 1120 return 1121 1122 if None==unavailable: 1123 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n') 1124 else: # Have a default value that can be filled in 1125 #assert type(unavailable)== 1126 int(unavailable) # Make sure unavailable is a number object 1127 o.write("\tif '"+name+"' in params:\n") 1128 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n') 1129 o.write('\telse:\n') 1130 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n') 1131 1132 if verbose: o.write('\n')
1133 1134
1135 -def encodeFloat(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1136 ''' 1137 Build the encoder for IEEE float variables 1138 1139 @type o: file like obj 1140 @param o: where write the code 1141 @type name: str 1142 @param name: field name 1143 @type fieldType: str 1144 @param fieldType: uint, bool, etc. 1145 @type numbits: int >= 1 1146 @param numbits: How many bits per unit datum (must be 1..32) 1147 @type required: bool or None 1148 @param required: If not None, then the value must be set to this. 1149 @type arraylen: int >= 1 1150 @param arraylen: many unsigned ints will there be? FIX: handle variable 1151 @type unavailable: bool or None 1152 @param unavailable: the default value to use if none given (if not None) 1153 @return: None 1154 ''' 1155 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 1156 1157 assert numbits==32 # Force by the IEEE spec 1158 if arraylen != 1: assert False # FIX... handle arrays 1159 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 1160 1161 if None != required: 1162 if verbose: print ' required:',required 1163 required=int(required) 1164 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n') 1165 if verbose: o.write('\n') 1166 return 1167 1168 if None==unavailable: 1169 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n') 1170 else: # Have a default value that can be filled in 1171 int(unavailable) # Make sure unavailable is a number object 1172 o.write("\tif '"+name+"' in params:\n") 1173 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n') 1174 o.write('\telse:\n') 1175 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n') 1176 1177 if verbose: o.write('\n')
1178 1179
1180 -def encodeAisstr6(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1181 ''' 1182 Build the encoder for aisstr6 variables. Generally are arrays. 1183 @bug: do we need to optionally check for a valid string? 1184 1185 @type o: file like obj 1186 @param o: where write the code 1187 @type name: str 1188 @param name: field name 1189 @type fieldType: str 1190 @param fieldType: uint, bool, etc. 1191 @type numbits: int >= 1 1192 @param numbits: How many bits per unit datum (must be 1..32) 1193 @type required: bool or None 1194 @param required: If not None, then the value must be set to this. 1195 @type arraylen: int >= 1 1196 @param arraylen: many unsigned ints will there be? FIX: handle variable 1197 @type unavailable: bool or None 1198 @param unavailable: the default value to use if none given (if not None) 1199 @return: None 1200 ''' 1201 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable 1202 totLen = str(numbits*arraylen) 1203 assert numbits==6 # Each character must be 6 bits 1204 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n') 1205 1206 if None != required: 1207 if verbose: print ' required:',required 1208 required=int(required) 1209 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+totLen+'))\n') 1210 if verbose: o.write('\n') 1211 return 1212 1213 if None==unavailable: 1214 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 1215 else: # Have a default value that can be filled in 1216 o.write("\tif '"+name+"' in params:\n") 1217 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n') 1218 o.write('\telse:\n') 1219 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+totLen+'))\n') 1220 1221 if verbose: o.write('\n')
1222 1223
1224 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1225 ''' 1226 Build the encoder for signed integer variables 1227 1228 @type o: file like obj 1229 @param o: where write the code 1230 @type name: str 1231 @param name: field name 1232 @type type: str 1233 @param type: uint, bool, etc. 1234 @type numbits: int >= 1 1235 @param numbits: How many bits per unit datum (must be 1..32) 1236 @type required: bool or None 1237 @param required: If not None, then the value must be set to this. 1238 @type arraylen: int >= 1 1239 @param arraylen: many signed ints will there be? FIX: handle variable 1240 @type unavailable: number or None 1241 @param unavailable: the default value to use if none given (if not None) 1242 @return: None 1243 ''' 1244 if verbose: print ' encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1245 1246 assert numbits>=1 and numbits<=32 1247 if arraylen != 1: assert False # FIX... handle arrays 1248 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1249 1250 if None != required: 1251 if verbose: print ' required:',required 1252 required=int(required) 1253 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n') 1254 if verbose: o.write('\n') 1255 return 1256 1257 1258 if None==unavailable: 1259 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n') 1260 else: # Have a default value that can be filled in 1261 #assert type(unavailable)== 1262 int(unavailable) # Make sure unavailable is a number object 1263 o.write("\tif '"+name+"' in params:\n") 1264 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n') 1265 o.write('\telse:\n') 1266 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n') 1267 1268 if verbose: o.write('\n')
1269 1270 1271 1272 # FIX: Ummm... why am I passing the type? I guess it makes the one print statement easier
1273 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None, offset=None):
1274 ''' 1275 Build the encoder for signed decimal variables 1276 1277 @type o: file like obj 1278 @param o: where write the code 1279 @type name: str 1280 @param name: field name 1281 @type type: str 1282 @param type: decimal 1283 @type numbits: int >= 1 1284 @param numbits: How many bits per unit datum (must be 1..32) 1285 @type required: bool or None 1286 @param required: If not None, then the value must be set to this. 1287 @type arraylen: int >= 1 1288 @param arraylen: many decimals will there be? FIX: handle variable 1289 @type unavailable: Decimal or None 1290 @param unavailable: the default value to use if none given (if not None) 1291 @return: None 1292 ''' 1293 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1294 1295 assert numbits>=1 and numbits<=32 1296 if arraylen != 1: assert False # FIX... handle arrays 1297 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1298 1299 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 1300 if None == scale: 1301 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 1302 print 'Beware canadians bearing travel videos' 1303 scale='1' 1304 1305 if None != required: 1306 if verbose: print ' required:',required 1307 required=int(required) 1308 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n') 1309 if verbose: o.write('\n') 1310 return 1311 1312 offsetStr='' 1313 if None != offset: 1314 offsetStr='-('+offset+')' 1315 1316 # FIX: can I get rid of the Decimal around params? 1317 if None==unavailable: 1318 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 1319 else: # Have a default value that can be filled in 1320 o.write("\tif '"+name+"' in params:\n") 1321 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 1322 o.write('\telse:\n') 1323 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n') 1324 1325 if verbose: o.write('\n')
1326 1327 1328
1329 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None, offset=None):
1330 ''' 1331 Build the encoder for signed decimal variables 1332 1333 @type o: file like obj 1334 @param o: where write the code 1335 @type name: str 1336 @param name: field name 1337 @type type: str 1338 @param type: decimal 1339 @type numbits: int >= 1 1340 @param numbits: How many bits per unit datum (must be 1..32) 1341 @type required: bool or None 1342 @param required: If not None, then the value must be set to this. 1343 @type arraylen: int >= 1 1344 @param arraylen: many decimals will there be? FIX: handle variable 1345 @type unavailable: Decimal or None 1346 @param unavailable: the default value to use if none given (if not None) 1347 @return: None 1348 ''' 1349 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 1350 assert type=='udecimal' 1351 assert numbits>=1 and numbits<=32 1352 if arraylen != 1: assert False # FIX... handle arrays 1353 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1354 1355 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 1356 if None == scale: 1357 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 1358 print 'Beware canadians bearing travel videos' 1359 scale='1' 1360 1361 if None != required: 1362 if verbose: print ' required:',required 1363 required=int(required) 1364 assert(0<=required) 1365 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n') 1366 if verbose: o.write('\n') 1367 return 1368 1369 offsetStr='' 1370 if None != offset: 1371 offsetStr='-('+offset+')' 1372 1373 # FIX: can I get rid of the Decimal around params? 1374 if None==unavailable: 1375 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1376 else: # Have a default value that can be filled in 1377 o.write("\tif '"+name+"' in params:\n") 1378 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\']'+offsetStr+')*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 1379 o.write('\telse:\n') 1380 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n') 1381 1382 if verbose: o.write('\n')
1383 1384 1385
1386 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1387 ''' 1388 Build the encoder for binary variables. This is just a pass through. 1389 This is used for the ais binary message wrapper (e.g. msg 8). Do 1390 not use it within binary messages. 1391 1392 @type o: file like obj 1393 @param o: where write the code 1394 @type name: str 1395 @param name: field name 1396 @type type: str 1397 @param type: binary 1398 @type numbits: int >= 1 1399 @param numbits: How many bits per unit datum (must be 1..1024 or so) 1400 @type required: bool or None 1401 @param required: If not None, then the value must be set to this. 1402 @type arraylen: int >= 1 1403 @param arraylen: many decimals will there be? FIX: handle variable 1404 @type unavailable: Decimal or None 1405 @param unavailable: the default value to use if none given (if not None) 1406 @return: None 1407 ''' 1408 if verbose: print ' encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable 1409 assert type=='binary' 1410 assert (numbits>=1 and numbits<=1024) or numbits==-1 1411 assert (None == required) # don't allow this 1412 assert (None == unavailable) # don't allow this 1413 1414 if arraylen != 1: assert False # Do not handle arrays. Arrays of bits is just not necessary. 1415 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 1416 1417 # FIX: can I get rid of the Decimal around params? 1418 o.write('\tbvList.append(params[\''+name+'\'])\n') # Just pass it through 1419 1420 if verbose: o.write('\n')
1421 1422 1423 ###################################################################### 1424 # DECODERS 1425 ###################################################################### 1426
1427 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1428 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1429 ''' 1430 Build the decoder for boolean variables 1431 1432 @type o: file like obj 1433 @param o: where write the code 1434 @type name: str 1435 @param name: field name 1436 @type type: str 1437 @param type: uint, bool, etc. 1438 @type startindex: int 1439 @param startindex: bit that begins the bool(s) 1440 @type numbits: int = 1 1441 @param numbits: How many bits per unit datum (must be 1 for bools) 1442 @type required: bool or None 1443 @param required: If not None, then the value must be set to this. 1444 @type arraylen: int >= 1 1445 @param arraylen: many bools will there be? FIX: handle variable 1446 @type unavailable: bool or None 1447 @param unavailable: the default value to use if none given (if not None) 1448 @type bv: str 1449 @param bv: BitVector containing the incoming data 1450 @type dataDict: str 1451 @param dataDict: dictionary in which to place the results 1452 @type decodeOnly: bool 1453 @param decodeOnly: Set to true to only get the code for decoding 1454 @rtype: int 1455 @return: index one past the end of where this read 1456 ''' 1457 assert(type=='bool') 1458 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1459 #int(startindex); int(numbits) # Make sure it is a number 1460 assert numbits==1 1461 assert arraylen == 1 # FIX... handle arrays 1462 1463 if None != required: 1464 assert type(required)==bool 1465 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1466 if required: o.write('True\n') 1467 else: o.write('False\n') 1468 if not decodeOnly: o.write('\n') 1469 return int(startindex)+int(numbits) 1470 1471 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1472 o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))') 1473 if not decodeOnly: o.write('\n') 1474 1475 return int(startindex)+int(numbits)
1476 1477
1478 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1479 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1480 ''' 1481 Build the decoder for unsigned integer variables 1482 1483 @type o: file like obj 1484 @param o: where write the code 1485 @type name: str 1486 @param name: field name 1487 @type type: str 1488 @param type: uint, etc. 1489 @type startindex: int 1490 @param startindex: bit that begins the uint(s) 1491 @type numbits: int >= 1 1492 @param numbits: How many bits per unit datum 1493 @type required: int or None 1494 @param required: If not None, then the value must be set to this. 1495 @type arraylen: int >= 1 1496 @param arraylen: many ints will there be? FIX: handle variable 1497 @type unavailable: int or None 1498 @param unavailable: the default value to use if none given (if not None) 1499 @type bv: str 1500 @param bv: BitVector containing the incoming data 1501 @type dataDict: str 1502 @param dataDict: dictionary in which to place the results 1503 @type decodeOnly: bool 1504 @param decodeOnly: Set to true to only get the code for decoding 1505 @rtype: int 1506 @return: index one past the end of where this read 1507 ''' 1508 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1509 if None==arraylen: arraylen=1 1510 assert arraylen == 1 # FIX... handle arrays 1511 assert numbits>=1 1512 if not decodeOnly: verbose=False 1513 1514 if None != required: 1515 int(required) # Make sure required is a number 1516 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1517 o.write(str(required)) 1518 if not decodeOnly: o.write('\n') 1519 return startindex+numbits 1520 1521 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1522 o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])') 1523 if not decodeOnly: o.write('\n') 1524 if verbose: o.write('\n') 1525 1526 return startindex+numbits
1527 1528
1529 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1530 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1531 ''' 1532 Build the decoder for unsigned integer variables 1533 1534 @type o: file like obj 1535 @param o: where write the code 1536 @type name: str 1537 @param name: field name 1538 @type type: str 1539 @param type: int 1540 @type startindex: int 1541 @param startindex: bit that begins the int(s) 1542 @type numbits: int >= 1 1543 @param numbits: How many bits per unit datum 1544 @type required: int or None 1545 @param required: If not None, then the value must be set to this. 1546 @type arraylen: int >= 1 1547 @param arraylen: many ints will there be? FIX: handle variable 1548 @type unavailable: int or None 1549 @param unavailable: the default value to use if none given (if not None) 1550 @type bv: str 1551 @param bv: BitVector containing the incoming data 1552 @type dataDict: str 1553 @param dataDict: dictionary in which to place the results 1554 @type decodeOnly: bool 1555 @param decodeOnly: Set to true to only get the code for decoding 1556 @rtype: int 1557 @return: index one past the end of where this read 1558 ''' 1559 assert type=='int' 1560 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1561 if None==arraylen: arraylen=1 1562 end = startindex+int(numbits)*int(arraylen) 1563 assert arraylen == 1 # FIX... handle arrays 1564 assert numbits>=1 1565 1566 if None != required: 1567 int(required) # Make sure required is a number 1568 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1569 o.write(str(required)) 1570 if not decodeOnly: o.write('\n') 1571 return end 1572 1573 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1574 o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])') 1575 if not decodeOnly: o.write('\n') 1576 if verbose: o.write('\n') 1577 1578 return end
1579
1580 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1581 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1582 ''' 1583 Build the decoder for IEEE float variables 1584 1585 @type o: file like obj 1586 @param o: where write the code 1587 @type name: str 1588 @param name: field name 1589 @type type: str 1590 @param type: int 1591 @type startindex: int 1592 @param startindex: bit that begins the int(s) 1593 @type numbits: int >= 1 1594 @param numbits: How many bits per unit datum 1595 @type required: float or None 1596 @param required: If not None, then the value must be set to this. 1597 @type arraylen: int >= 1 1598 @param arraylen: many ints will there be? FIX: handle variable 1599 @type unavailable: float or None 1600 @param unavailable: the default value to use if none given (if not None) 1601 @type bv: str 1602 @param bv: BitVector containing the incoming data 1603 @type dataDict: str 1604 @param dataDict: dictionary in which to place the results 1605 @type decodeOnly: bool 1606 @param decodeOnly: Set to true to only get the code for decoding 1607 @rtype: int 1608 @return: index one past the end of where this read 1609 ''' 1610 assert type=='float' 1611 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1612 if None==arraylen: arraylen=1 1613 end = startindex+int(numbits)*int(arraylen) 1614 assert arraylen == 1 # FIX... handle arrays 1615 assert numbits>=1 1616 1617 if None != required: 1618 float(required) # Make sure required is a number 1619 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1620 o.write(str(required)) 1621 if not decodeOnly: o.write('\n') 1622 if verbose: o.write('\n') 1623 return end 1624 1625 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1626 o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])') 1627 if not decodeOnly: o.write('\n') 1628 1629 return end
1630 1631
1632 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1633 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1634 ''' 1635 Build the decoder for aisstr6 variables. Generally arrays. 1636 @bug: FIX: validate strings?? 1637 @type o: file like obj 1638 @param o: where write the code 1639 @type name: str 1640 @param name: field name 1641 @type type: str 1642 @param type: 'aisstr6' 1643 @type startindex: int 1644 @param startindex: bit that begins the int(s) 1645 @type numbits: int >= 1 1646 @param numbits: How many bits per unit datum 1647 @type required: restricted str or None 1648 @param required: If not None, then the value must be set to this. 1649 @type arraylen: int >= 1 1650 @param arraylen: many ints will there be? FIX: handle variable 1651 @type unavailable: restricted str or None 1652 @param unavailable: the default value to use if none given (if not None) 1653 @type bv: str 1654 @param bv: BitVector containing the incoming data 1655 @type dataDict: str 1656 @param dataDict: dictionary in which to place the results 1657 @type decodeOnly: bool 1658 @param decodeOnly: Set to true to only get the code for decoding 1659 @rtype: int 1660 @return: index one past the end of where this read 1661 ''' 1662 assert type=='aisstr6' 1663 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1664 if None==arraylen: arraylen=1 1665 end = startindex+int(numbits)*int(arraylen) 1666 assert arraylen >= 1 # FIX... handle arrays 1667 assert numbits>=1 1668 1669 if None != required: 1670 float(required) # Make sure required is a number 1671 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1672 o.write(required) 1673 if not decodeOnly: o.write('\n') 1674 return end 1675 1676 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1677 o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])') 1678 if not decodeOnly: o.write('\n') 1679 1680 return end
1681 1682
1683 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1684 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False,offset=None):
1685 ''' 1686 Build the decoder for signed decimal variables 1687 1688 @type o: file like obj 1689 @param o: where write the code 1690 @type name: str 1691 @param name: field name 1692 @type type: str 1693 @param type: 'decimal' 1694 @type startindex: int 1695 @param startindex: bit that begins the int(s) 1696 @type numbits: int >= 1 1697 @param numbits: How many bits per unit datum 1698 @type required: Decimal or None 1699 @param required: If not None, then the value must be set to this. 1700 @type arraylen: int >= 1 1701 @param arraylen: many ints will there be? FIX: handle variable 1702 @type unavailable: Decimal or None 1703 @param unavailable: the default value to use if none given (if not None) 1704 @type bv: str 1705 @param bv: BitVector containing the incoming data 1706 @type dataDict: str 1707 @param dataDict: dictionary in which to place the results 1708 @type decodeOnly: bool 1709 @param decodeOnly: Set to true to only get the code for decoding 1710 @rtype: int 1711 @return: index one past the end of where this read 1712 ''' 1713 assert type=='decimal' 1714 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1715 if None==arraylen: arraylen=1 1716 end = startindex+int(numbits)*int(arraylen) 1717 assert arraylen == 1 # FIX... handle arrays 1718 assert numbits>=1 and numbits <= 32 1719 1720 if None == scale: scale='1' # Warning about this was in the encode section 1721 1722 if None != required: 1723 Decimal(required) # Make sure required is a number 1724 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1725 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1726 if not decodeOnly: o.write('\n') 1727 return end 1728 1729 offsetStr='' 1730 if offset != None: 1731 offsetStr += '+Decimal(\''+offset+'\')' 1732 1733 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1734 o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')'+offsetStr+'') 1735 if not decodeOnly: o.write('\n') 1736 1737 return end
1738 1739 1740 1741
1742 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1743 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False,offset=None):
1744 ''' 1745 Build the decoder for unsigned decimal variables 1746 1747 @type o: file like obj 1748 @param o: where write the code 1749 @type name: str 1750 @param name: field name 1751 @type type: str 1752 @param type: 'udecimal' 1753 @type startindex: int 1754 @param startindex: bit that begins the int(s) 1755 @type numbits: int >= 1 1756 @param numbits: How many bits per unit datum 1757 @type required: Decimal or None 1758 @param required: If not None, then the value must be set to this. 1759 @type arraylen: int >= 1 1760 @param arraylen: many ints will there be? FIX: handle variable 1761 @type unavailable: Decimal or None 1762 @param unavailable: the default value to use if none given (if not None) 1763 @type bv: str 1764 @param bv: BitVector containing the incoming data 1765 @type dataDict: str 1766 @param dataDict: dictionary in which to place the results 1767 @type decodeOnly: bool 1768 @param decodeOnly: Set to true to only get the code for decoding 1769 @rtype: int 1770 @return: index one past the end of where this read 1771 ''' 1772 assert type=='udecimal' 1773 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1774 if None==arraylen: arraylen=1 1775 end = startindex+int(numbits)*int(arraylen) 1776 assert arraylen == 1 # FIX... handle arrays 1777 assert numbits>=1 and numbits <= 32 1778 1779 if None == scale: scale='1' # Warning about this was in the encode section 1780 1781 if None != required: 1782 assert (Decimal(required)>=0.) # Make sure required is a number and not negative 1783 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1784 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1785 if not decodeOnly: o.write('\n') 1786 return end 1787 1788 offsetStr='' 1789 if offset != None: 1790 offsetStr += '+Decimal(\''+offset+'\')' 1791 1792 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1793 o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')'+offsetStr+'') 1794 if not decodeOnly: o.write('\n') 1795 1796 return end
1797 1798
1799 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1800 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1801 ''' 1802 Build the decoder for unsigned decimal variables 1803 1804 @type o: file like obj 1805 @param o: where write the code 1806 @type name: str 1807 @param name: field name 1808 @type type: str 1809 @param type: 'udecimal' 1810 @type startindex: int 1811 @param startindex: bit that begins the int(s) 1812 @type numbits: int >= 1 1813 @param numbits: How many bits per unit datum. If -1, then read to the end of the message 1814 @type required: Decimal or None 1815 @param required: If not None, then the value must be set to this. 1816 @type arraylen: int >= 1 1817 @param arraylen: many ints will there be? FIX: handle variable 1818 @type unavailable: Decimal or None 1819 @param unavailable: the default value to use if none given (if not None) 1820 @type bv: str 1821 @param bv: BitVector containing the incoming data 1822 @type dataDict: str 1823 @param dataDict: dictionary in which to place the results 1824 @type decodeOnly: bool 1825 @param decodeOnly: Set to true to only get the code for decoding 1826 @rtype: int 1827 @return: index one past the end of where this read 1828 ''' 1829 assert type=='binary' 1830 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1831 if None==arraylen: arraylen=1 1832 end = startindex+int(numbits)*int(arraylen) 1833 assert arraylen == 1 # FIX... handle arrays 1834 assert (numbits>=1 and numbits <= 1024) or -1==numbits # What is good max? 1835 # FIX: assert not required and not an array an not unavailable 1836 1837 1838 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1839 o.write(bv+'['+str(startindex)+':') 1840 if int(numbits) != -1: o.write(str(end)) # -1 means go to the end of the message 1841 o.write(']') 1842 if not decodeOnly: o.write('\n') 1843 1844 return end
1845 1846 1847 1848 ###################################################################### 1849 # THE REST 1850 ###################################################################### 1851 1852 1853
1854 -def buildTestParamFunc(o,msgET, verbose=False, prefixName=False):
1855 '''Scrape the testvalues to make a basic param 1856 1857 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies? 1858 ''' 1859 name = msgET.attrib['name'] 1860 1861 if prefixName: o.write('def '+name+'TestParams():\n') 1862 else: o.write('def testParams():\n') 1863 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") 1864 o.write('\tparams = {}\n') 1865 for field in msgET.xpath('field'): 1866 name = field.attrib['name'] 1867 type = field.attrib['type'] 1868 if verbose: print 'buildTestParamFunc ...',name,type 1869 val = None 1870 if hasSubTag(field,'testvalue') and hasSubTag(field,'required'): 1871 print 'ERROR: can not have both test value and required tags in the same field' 1872 assert(False) 1873 if hasSubTag(field,'testvalue'): 1874 val = field.xpath('testvalue')[0].text 1875 else: 1876 if not hasSubTag(field,'required'): 1877 sys.exit("ERROR: missing required or testvalue for field: "+name) 1878 val = field.xpath('required')[0].text 1879 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val 1880 o.write('\tparams[\''+name+'\'] = ') 1881 if type=='bool': 1882 if val=='1' or val.lower=='true': val = 'True' 1883 else: val = 'False' 1884 o.write(val) 1885 elif type in ('uint','int','float'): 1886 o.write(val) 1887 elif type in ('decimal','udecimal'): 1888 o.write('Decimal(\''+val+'\')') 1889 1890 elif type in ('aisstr6'): 1891 o.write('\''+val+'\'') 1892 elif type in ('binary'): 1893 o.write('BitVector(bitstring=\''+val+'\')') 1894 else: 1895 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee' 1896 suggestType(name,type) 1897 assert(False) 1898 1899 o.write('\n') 1900 1901 1902 o.write('\n\treturn params\n\n')
1903
1904 -def buildUnitTest(o,msgET, verbose=False, prefixName=False):
1905 ''' 1906 Write the unittests for a message 1907 1908 @param o: open file where resulting code will be written 1909 @param msgET: Element Tree starting at a message node 1910 ''' 1911 assert(msgET.tag=='message') 1912 name = msgET.attrib['name'] 1913 1914 buildTestParamFunc(o,msgET, prefixName=prefixName) 1915 1916 o.write('class Test'+name+'(unittest.TestCase):\n') 1917 o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n") 1918 o.write('\tdef testEncodeDecode(self):\n\n') 1919 if prefixName: 1920 o.write('\t\tparams = '+name+'TestParams()\n') 1921 o.write('\t\tbits = '+name+'Encode(params)\n') 1922 o.write('\t\tr = '+name+'Decode(bits)\n\n') 1923 else: 1924 o.write('\t\tparams = testParams()\n') 1925 o.write('\t\tbits = encode(params)\n') 1926 o.write('\t\tr = decode(bits)\n\n') 1927 1928 o.write('\t\t# Check that each parameter came through ok.\n') 1929 for field in msgET.xpath('field'): 1930 name = field.attrib['name'] 1931 type = field.attrib['type'] 1932 if type in ('bool','uint','int','aisstr6','binary'): 1933 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n') 1934 else: 1935 # float, decimal, udecimal 1936 # FIX: look up the decimal places if decimal 1937 places = '3' 1938 if hasSubTag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text 1939 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
1940 1941 1942
1943 -def buildEncode(o,msgET, verbose=False, prefixName=False):
1944 ''' 1945 Write the encoder/decoder for a message 1946 1947 http://jaynes.colorado.edu/PythonIdioms.html 1948 1949 @param o: open file where resulting code will be written 1950 @param msgET: Element Tree starting at a message node 1951 @todo: handle ais message 20 optional. very troublesome 1952 ''' 1953 assert(msgET.tag=='message') 1954 name = msgET.attrib['name'] # fix rename this variable to avoid hiding later on by field name 1955 1956 print 'Generating encoder ...',name # FIX: verbose? 1957 funcName = 'encode' 1958 if prefixName: funcName = name+'Encode' 1959 o.write('def '+funcName+'(params, validate=False):\n') 1960 1961 ######################################## 1962 # doc string 1963 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n") 1964 o.write('\tFields in params:\n') 1965 for field in msgET.xpath('field'): 1966 if verbose: 1967 print field.tag,field.attrib['name'] 1968 print field.attrib 1969 desc = field[0].text.replace('\n',' ') # get ride of new lines 1970 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1971 if len(field.xpath("required")) == 1: 1972 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1973 1974 o.write('\n') 1975 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n') 1976 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1977 1978 o.write("\t@rtype: BitVector\n") 1979 o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n") 1980 o.write("\t@note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.\n") 1981 o.write("\t'''\n\n") 1982 1983 ######################################## 1984 # Actually build the code 1985 1986 o.write('\tbvList = []\n') 1987 1988 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1989 1990 dynamicArrays = False # Set to true when positioning must be calculated 1991 1992 for field in msgET.xpath('field'): 1993 name = field.attrib['name'] 1994 type = field.attrib['type'] 1995 numbits = int(field.attrib['numberofbits']) 1996 required = None; 1997 if hasSubTag(field,'required'): 1998 required = field.xpath('required')[0].text 1999 unavailable=None; 2000 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 2001 arraylen=1 2002 if 'arraylength' in field.attrib: 2003 arraylen=int(field.attrib['arraylength']) 2004 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 2005 else: 2006 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 2007 2008 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable) 2009 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable) 2010 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable) 2011 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable) 2012 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable) 2013 elif type=='decimal': 2014 scale = None 2015 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2016 offset = None 2017 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2018 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale, offset=offset) 2019 elif type=='udecimal': 2020 scale = None 2021 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2022 offset = None 2023 if hasSubTag(field,'offset'): 2024 offset = field.xpath('offset')[0].text 2025 encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale,offset=offset) 2026 elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable) 2027 else: 2028 print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type 2029 suggestType (name,type) 2030 assert False 2031 2032 # o.write('\n\tbv=binary.joinBV(bvList)\n') 2033 # o.write('\n\tbvLen=len(bv)\n') 2034 # o.write('\n\tif bvLen%6!=0:\n') 2035 # o.write('\n\t bv = bv + BitVector(size=bvLen%6) # \n') 2036 # o.write('\n\treturn bv\n\n') 2037 o.write('\n\treturn binary.joinBV(bvList)\n\n')
2038 2039 2040 ###################################################################### 2041 # DECODER ONE PART AT A TIME - if only using a small part, save some cycles! 2042 2043 2044 #, msgDict=None):
2045 -def buildDecodeParts(o,msgET, verbose=False, prefixName=False):
2046 2047 ''' 2048 Write the decoder for a message 2049 2050 @param o: open file where resulting code will be written 2051 @type msgET: elementtree 2052 @param prefixName: if True, put the name of the message on the functions. 2053 @param msgET: Element Tree starting at a message node 2054 @return: None 2055 2056 @todo: FIX: doc strings for each decode! 2057 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 2058 @todo: May want to take a dictionary of already decoded fields to speed things that need prior info 2059 for things like variable length arrays 2060 ''' 2061 2062 assert(msgET.tag=='message') 2063 name = msgET.attrib['name'] 2064 2065 print 'Generating partial decode functions ...',name 2066 2067 baseName = name+'Decode' 2068 if not prefixName: baseName = 'decode' 2069 2070 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 2071 2072 for field in msgET.xpath('field'): 2073 name = field.attrib['name'] 2074 type = field.attrib['type'] 2075 2076 o.write('def '+baseName+name+'(bv, validate=False):\n') 2077 # Follow the same convention of decoding into a dict so that code is the same 2078 #o.write('\tr={};') 2079 o.write('\treturn ') 2080 2081 numbits = int(field.attrib['numberofbits']) 2082 required = None; 2083 if hasSubTag(field,'required'): 2084 required = field.xpath('required')[0].text 2085 unavailable=None; 2086 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 2087 arraylen=1 2088 if 'arraylength' in field.attrib: 2089 arraylen=int(field.attrib['arraylength']) 2090 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 2091 2092 assert None!=startindex 2093 if verbose: print 'startindex',startindex 2094 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2095 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2096 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2097 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2098 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2099 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 2100 2101 elif type=='decimal': 2102 scale = None 2103 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2104 offset = None 2105 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2106 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True,offset=offset) 2107 elif type=='udecimal': 2108 scale = None 2109 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2110 offset = None 2111 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2112 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True,offset=offset) 2113 2114 else: 2115 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 2116 suggestType (name,type) 2117 assert False 2118 2119 #o.write('\treturn(r[\''+name+'\'])\n\n') 2120 o.write('\n\n')
2121 2122 2123 ###################################################################### 2124 # DECODER RING 2125
2126 -def buildDecode(o,msgET, verbose=False, prefixName=False):
2127 ''' 2128 Write the decoder for a message 2129 2130 @param o: open file where resulting code will be written 2131 @type msgET: elementtree 2132 @param msgET: Element Tree starting at a message node 2133 @return: None 2134 2135 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 2136 ''' 2137 2138 assert(msgET.tag=='message') 2139 name = msgET.attrib['name'] 2140 2141 print 'Generating decoder ...',name 2142 funcName = 'decode' 2143 if prefixName: funcName = name+'Decode' 2144 o.write('def '+funcName+'(bv, validate=False):\n') 2145 2146 ######################################## 2147 # doc string 2148 o.write("\t'''Unpack a "+name+" message \n\n") 2149 o.write('\tFields in params:\n') 2150 for field in msgET.xpath('field'): 2151 desc = field[0].text.replace('\n',' ') # get ride of new lines 2152 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 2153 if len(field.xpath("required")) == 1: 2154 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 2155 2156 o.write('\n') 2157 o.write('\t@type bv: BitVector\n') 2158 o.write('\t@param bv: Bits defining a message\n') 2159 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 2160 2161 o.write("\t@rtype: dict\n") 2162 o.write("\t@return: params\n") 2163 o.write("\t'''\n\n") 2164 2165 2166 o.write('\t#Would be nice to check the bit count here..\n') 2167 o.write('\t#if validate:\n') 2168 o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n') 2169 2170 2171 ######################################## 2172 # Actually build the code 2173 2174 o.write('\tr = {}\n') 2175 2176 2177 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 2178 2179 dynamicArrays = False # Set to true when positioning must be calculated 2180 2181 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 2182 2183 for field in msgET.xpath('field'): 2184 name = field.attrib['name'] 2185 type = field.attrib['type'] 2186 numbits = int(field.attrib['numberofbits']) 2187 required = None; 2188 if hasSubTag(field,'required'): 2189 required = field.xpath('required')[0].text 2190 unavailable=None; 2191 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 2192 arraylen=1 2193 if 'arraylength' in field.attrib: 2194 arraylen=int(field.attrib['arraylength']) 2195 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 2196 else: 2197 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 2198 2199 assert None!=startindex 2200 if verbose: print 'startindex',startindex 2201 2202 if hasSubTag(field,'optional'): 2203 # NoReturn means that this field is not a point that needs a check to see if we are done 2204 # FIX: need to do something more efficient 2205 if msgET.attrib['aismsgnum']!='20': 2206 sys.exit('optional is only allow on AIS msg 20. Do NOT use it on new messages!!!') 2207 text = field.xpath('optional')[0].text 2208 if None==field.xpath('optional')[0].text: # Found a NoReturn 2209 # Message 20 is a pain! 2210 pad = 8-(startindex%8) 2211 print 'not found: noreturn. pad=',pad 2212 assert(pad<=6) 2213 o.write('\tif len(bv)<='+str(startindex+pad)+': return r; # All fields below are optional\n') 2214 elif text != 'NoReturn': 2215 sys.exit ('ERROR: optional text must be NoReturn or empty') 2216 2217 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable) 2218 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 2219 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 2220 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable) 2221 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable) 2222 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable) 2223 2224 elif type=='decimal': 2225 scale = None 2226 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2227 offset = None 2228 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2229 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,offset=offset) 2230 2231 elif type=='udecimal': 2232 scale = None 2233 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text 2234 offset = None 2235 if hasSubTag(field,'offset'): offset = field.xpath('offset')[0].text 2236 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,offset=offset) 2237 2238 else: 2239 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 2240 suggestType (name,type) 2241 assert False 2242 2243 2244 if None==startindex: print 'FIX: here. drat. treat me right' 2245 assert None!=startindex 2246 2247 2248 o.write('\treturn r\n\n')
2249 2250 2251 2252 #def getPythonType(aType): 2253 # ''' 2254 # Translate a 2255 # @rtype: str 2256 # @return: name of the python type 2257 # ''' 2258 2259 aisType2pythonType={ 2260 'bool':'Bool', 2261 'uint':'int', 2262 'int':'int', 2263 'udecimal':'Decimal', 2264 'decimal':'Decimal', 2265 'aisstr6':'str', 2266 'binary':'str', 2267 'float':'Float', 2268 } 2269 2270 import copy 2271 aisType2optParseType=copy.deepcopy(aisType2pythonType) 2272 aisType2optParseType['bool']='int' # FIX: make these appear as a flag variable 2273 aisType2optParseType['aisstr6']='string' 2274 aisType2optParseType['aisstr6']='string' 2275 aisType2optParseType['binary']='string' 2276 aisType2optParseType['udecimal']='string' # FIX: Is this a good choice? 2277 aisType2optParseType['decimal']='string' 2278 aisType2optParseType['float']='float' 2279 2280
2281 -def buildOptParse(o,msgET, prefixName=False):
2282 '''Create a function that adds the options to a parse object''' 2283 2284 assert None != msgET 2285 assert msgET.tag=='message' 2286 msgName = msgET.attrib['name'] 2287 2288 prefix='' 2289 if prefixName: prefix=msgName 2290 2291 funcName = 'addMsgOptions' 2292 if prefixName: funcName = msgName + 'AddMsgOptions' 2293 o.write('\ndef '+funcName+'(parser):') 2294 2295 # FIX: write out a doc string 2296 2297 o.write(''' 2298 parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true', 2299 help='decode a "'''+msgName+'''" AIS message') 2300 parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true', 2301 help='encode a "'''+msgName+'''" AIS message') 2302 ''') 2303 2304 #print 'here...', msgName, prefixName 2305 for field in msgET.xpath('field'): 2306 name = field.attrib['name'] 2307 fieldType = field.attrib['type'] 2308 if hasSubTag(field,'required'): 2309 print 'skipping required field ...',name,fieldType 2310 continue 2311 #print 'there',name,fieldType 2312 o.write('\tparser.add_option(\'--') 2313 if prefixName: o.write(msgName+'-') 2314 o.write(name+'-field\', dest=\''+name+'Field\'') 2315 if hasSubTag(field,'unavailable'): 2316 val = field.xpath('unavailable')[0].text 2317 o.write(',default=') 2318 if fieldType in ('uint','int','float'): 2319 o.write(val) 2320 elif fieldType in ('decimal','udecimal'): 2321 o.write('Decimal(\''+val+'\')') 2322 elif fieldType in ('aisstr6','bitvector'): 2323 o.write('\''+val+'\'') 2324 o.write(',metavar=\''+fieldType+'\',type=\''+aisType2optParseType[fieldType]+'\'') 2325 o.write('\n\t\t,help=\'Field parameter value [default: %default]\')\n')
2326 2327 2328
2329 -def buildMain(o, msgET, prefixName=False):
2330 assert None != msgET 2331 assert msgET.tag=='message' 2332 msgName = msgET.attrib['name'] 2333 2334 prefix='' 2335 if prefixName: prefix=msgName 2336 2337 buildOptParse(o, msgET, prefixName) 2338 2339 2340 o.write(''' 2341 ############################################################ 2342 if __name__=='__main__': 2343 2344 from optparse import OptionParser 2345 parser = OptionParser(usage="%prog [options]", 2346 version="%prog "+__version__) 2347 2348 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 2349 help='run the documentation tests') 2350 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true', 2351 help='run the unit tests') 2352 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 2353 help='Make the test output verbose') 2354 2355 # FIX: remove nmea from binary messages. No way to build the whole packet? 2356 # FIX: or build the surrounding msg 8 for a broadcast? 2357 typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message? 2358 parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType' 2359 ,default='nmeapayload' 2360 ,help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]') 2361 2362 2363 outputChoices = ('std','html','csv','sql' ''') 2364 # 'xml' - FIX: need to add xml output 2365 if haveLocatableMessage(msgET): o.write(', \'kml\',\'kml-full\'') 2366 o.write(''') 2367 parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType' 2368 ,default='std' 2369 ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]') 2370 2371 parser.add_option('-o','--output',dest='outputFileName',default=None, 2372 help='Name of the python file to write [default: stdout]') 2373 2374 parser.add_option('-f','--fields',dest='fieldList',default=None, action='append', 2375 choices=fieldList, 2376 help='Which fields to include in the output. Currently only for csv output [default: all]') 2377 2378 parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true', 2379 help='Print the field name for csv') 2380 2381 parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true', 2382 help='Print out an sql create command for the table.') 2383 2384 parser.add_option('--latex-table',dest='latexDefinitionTable',default=False,action='store_true', 2385 help='Print a LaTeX table of the type') 2386 2387 parser.add_option('--text-table',dest='textDefinitionTable',default=False,action='store_true', 2388 help='Print delimited table of the type (for Word table importing)') 2389 parser.add_option('--delimt-text-table',dest='delimTextDefinitionTable',default='\\t' 2390 ,help='Delimiter for text table [default: \\\'%default\\\'](for Word table importing)') 2391 2392 2393 dbChoices = ('sqlite','postgres') 2394 parser.add_option('-D','--db-type',dest='dbType',default='postgres' 2395 ,choices=dbChoices,type='choice' 2396 ,help='What kind of database ('+', '.join(dbChoices)+') [default: %default]') 2397 2398 ''') 2399 2400 o.write('''\taddMsgOptions(parser)\n''') 2401 2402 o.write(''' 2403 (options,args) = parser.parse_args() 2404 success=True 2405 2406 if options.doctest: 2407 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 2408 sys.argv= [sys.argv[0]] 2409 if options.verbose: sys.argv.append('-v') 2410 import doctest 2411 numfail,numtests=doctest.testmod() 2412 if numfail==0: print 'ok' 2413 else: 2414 print 'FAILED' 2415 success=False 2416 2417 if not success: sys.exit('Something Failed') 2418 del success # Hide success from epydoc 2419 2420 if options.unittest: 2421 sys.argv = [sys.argv[0]] 2422 if options.verbose: sys.argv.append('-v') 2423 unittest.main() 2424 2425 outfile = sys.stdout 2426 if None!=options.outputFileName: 2427 outfile = file(options.outputFileName,'w') 2428 2429 ''') 2430 2431 2432 2433 ############################## 2434 # encode 2435 ############################## 2436 # FIX: make everything tabs. Grr. 2437 o.write('\n\tif options.doEncode:\n') 2438 o.write('\t\t# First make sure all non required options are specified\n') 2439 for field in msgET.xpath('field'): 2440 name = field.attrib['name'] 2441 fieldType = field.attrib['type'] 2442 varName = prefix+name+'Field' 2443 if not hasSubTag(field,'required'): 2444 o.write('\t\tif None==options.'+varName+': parser.error("missing value for '+varName+'")\n') 2445 2446 # Build dict 2447 o.write('\t\tmsgDict={\n') 2448 for field in msgET.xpath('field'): 2449 name = field.attrib['name'] 2450 varName = prefix+name+'Field' 2451 if hasSubTag(field,'required'): 2452 o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n') 2453 else: 2454 o.write('\t\t\t\''+name+'\': options.'+varName+',\n') 2455 o.write('\t\t}\n') 2456 2457 encodeFunction = 'encode' 2458 if prefixName: encodeFunction = msgName+'Encode' 2459 o.write(''' 2460 bits = '''+encodeFunction+'''(msgDict) 2461 if 'binary'==options.ioType: print str(bits) 2462 elif 'nmeapayload'==options.ioType: 2463 # FIX: figure out if this might be necessary at compile time 2464 print "bitLen",len(bits) 2465 bitLen=len(bits) 2466 if bitLen%6!=0: 2467 bits = bits + BitVector(size=(6 - (bitLen%6))) # Pad out to multiple of 6 2468 print "result:",binary.bitvectoais6(bits)[0] 2469 2470 2471 # FIX: Do not emit this option for the binary message payloads. Does not make sense. 2472 elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability") 2473 else: sys.exit('ERROR: unknown ioType. Help!') 2474 ''') 2475 2476 2477 ############################## 2478 # decode all 2479 ############################## 2480 decodeFunction = 'decode' 2481 printFields='printFields' 2482 if prefixName: 2483 decodeFunction = msgName+'Decode' 2484 printFields = msgName+'PrintFields' 2485 o.write(''' 2486 2487 if options.sqlCreate: 2488 sqlCreateStr(outfile,options.fieldList,dbType=options.dbType) 2489 2490 if options.latexDefinitionTable: 2491 latexDefinitionTable(outfile) 2492 2493 # For conversion to word tables 2494 if options.textDefinitionTable: 2495 textDefinitionTable(outfile,options.delimTextDefinitionTable) 2496 2497 if options.printCsvfieldList: 2498 # Make a csv separated list of fields that will be displayed for csv 2499 if None == options.fieldList: options.fieldList = fieldList 2500 import StringIO 2501 buf = StringIO.StringIO() 2502 for field in options.fieldList: 2503 buf.write(field+',') 2504 result = buf.getvalue() 2505 if result[-1] == ',': print result[:-1] 2506 else: print result 2507 2508 if options.doDecode: 2509 if len(args)==0: args = sys.stdin 2510 for msg in args: 2511 bv = None 2512 2513 if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'): 2514 # Found nmea 2515 # FIX: do checksum 2516 bv = binary.ais6tobitvec(msg.split(',')[5]) 2517 else: # either binary or nmeapayload... expect mostly nmeapayloads 2518 # assumes that an all 0 and 1 string can not be a nmeapayload 2519 binaryMsg=True 2520 for c in msg: 2521 if c not in ('0','1'): 2522 binaryMsg=False 2523 break 2524 if binaryMsg: 2525 bv = BitVector(bitstring=msg) 2526 else: # nmeapayload 2527 bv = binary.ais6tobitvec(msg) 2528 2529 '''+printFields+'''('''+decodeFunction+'''(bv) 2530 ,out=outfile 2531 ,format=options.outputType 2532 ,fieldList=options.fieldList 2533 ,dbType=options.dbType 2534 ) 2535 2536 ''')
2537 2538 2539 ###################################################################### 2540 if __name__=='__main__': 2541 from optparse import OptionParser 2542 parser = OptionParser(usage="%prog [options]", 2543 version="%prog "+__version__) 2544 2545 parser.add_option('-o','--output',dest='outputFileName',default=None, 2546 help='Name of the python file to write') 2547 # help='Name of the python file to write [default: stdout]') 2548 2549 parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None, 2550 help='XML definition file for the msg to use') 2551 # help='XML definition file for the msg to use [default: stdin]') 2552 2553 # parser.add_option('-m','--message',dest='message',default=None, 2554 # help='Which message to write code for [default: all]') 2555 2556 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 2557 help='run the documentation tests') 2558 2559 parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true', 2560 help='put the field name in front of all function names.' 2561 +' Allows multiple messages in one file') 2562 2563 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 2564 help='run the tests run in verbose mode') 2565 2566 (options,args) = parser.parse_args() 2567 2568 success=True 2569 2570 if options.doctest: 2571 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 2572 argvOrig = sys.argv 2573 sys.argv= [sys.argv[0]] 2574 if options.verbose: sys.argv.append('-v') 2575 import doctest 2576 numfail,numtests=doctest.testmod() 2577 if numfail==0: print 'ok' 2578 else: 2579 print 'FAILED' 2580 success=False 2581 sys.argv = argvOrig # Restore the original args 2582 del argvOrig # hide from epydoc 2583 sys.exit() # FIX: Will this exit success? 2584 2585 #infile=sys.stdin 2586 #if options.xmlFileName: infile = file(options.xmlFileName,'r') 2587 2588 #outfile=sys.stdout 2589 #if options.outputFile: outfile = file(options.xmlDefinition,'w') 2590 2591 # FIX: down the road, it would be good to allow either filenames of std{in/out} 2592 2593 if None==options.xmlFileName: 2594 sys.exit('ERROR: must specify an xml definition file.') 2595 if None==options.outputFileName: 2596 sys.exit('ERROR: must specify an python file to write to.') 2597 generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix, verbose=options.verbose) 2598 2599 print '\nrecommend running pychecker like this:' 2600 print ' pychecker -q',options.outputFileName 2601