Package ais :: Module aisxmlbinmsg2py
[hide private]
[frames] | no frames]

Source Code for Module ais.aisxmlbinmsg2py

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