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__ myparser 
  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__ myparser 128 @status: under development 129 @license: Generated code has no license 130 \'\'\' 131 132 import sys 133 from decimal import Decimal 134 from BitVector import BitVector 135 136 import binary, aisstring 137 138 # FIX: check to see if these will be needed 139 TrueBV = BitVector(bitstring="1") 140 "Why always rebuild the True bit? This should speed things up a bunch" 141 FalseBV = BitVector(bitstring="0") 142 "Why always rebuild the False bit? This should speed things up a bunch" 143 144 145 ''') 146 return
147
148 -def generatePython(infile,outfile, prefixName=False):
149 ''' 150 @param infile: xml ais binary message definition file 151 @param outfile: where to dump the python code 152 ''' 153 154 aisMsgsET = etree.parse(infile).getroot() 155 156 o = file(outfile,'w') 157 os.chmod(outfile,0755) 158 159 writeBeginning(o) 160 161 for msgET in aisMsgsET: 162 if msgET.tag != 'message': continue 163 print msgET.tag, msgET.attrib['name'] 164 165 if len(msgET.xpath('include-struct')) > 0: 166 sys.exit("ERROR: cannot handle xml that still has include-struct tags.\n Please use expandais.py.") 167 buildEncode(o,msgET,prefixName=prefixName) 168 buildDecode(o,msgET,prefixName=prefixName) 169 buildDecodeParts(o,msgET,prefixName=prefixName) # functions that only decode one field 170 buildPrint(o,msgET,prefixName=prefixName) 171 172 o.write('\n\n######################################################################\n') 173 o.write('# UNIT TESTING\n') 174 o.write('######################################################################\n') 175 o.write('import unittest\n') 176 177 for msgET in aisMsgsET: 178 if msgET.tag != 'message': continue 179 print 'Building unit tests for message ...', msgET.attrib['name'] 180 181 buildUnitTest(o,msgET, prefixName=prefixName) 182 183 for msgET in aisMsgsET: 184 if msgET.tag != 'message': continue 185 buildMain(o,msgET, prefixName=prefixName) 186 187 return
188 189 ###################################################################### 190 # SIMPLE PRINT 191 ###################################################################### 192
193 -def getMaxFieldNameLen(msgET):
194 '''Get the maximum string length of any field name''' 195 maxStrLen=0 196 for field in msgET.xpath('field'): 197 fieldLen = len(field.attrib['name']) 198 if fieldLen>maxStrLen: maxStrLen = fieldLen 199 return maxStrLen
200
201 -def padStrRight(aStr,strlen):
202 '''Pad a string out to the length requested with spaces out to the right''' 203 return aStr + ' '*(strlen-len(aStr))
204
205 -def buildPrint(o,msgET, verbose=False, prefixName=False):
206 ''' 207 Write a simple in order print for the resulting dictionary. 208 209 param o: open file where resulting code will be written 210 param msgET: Element Tree starting at a message node 211 ''' 212 assert(msgET.tag=='message') 213 name = msgET.attrib['name'] 214 215 print 'Generating print for',name # FIX: verbose? 216 funcName = 'printFields' 217 if prefixName: funcName = name+'PrintFields' 218 219 o.write('def '+funcName+'(params, out=sys.stdout, format=\'std\'):\n') 220 221 ######################################## 222 # doc string 223 o.write("\t'''Print a "+name+" message to stdout.\n\n") 224 o.write('\tFields in params:\n') 225 for field in msgET.xpath('field'): 226 desc = field[0].text.replace('\n',' ') # get ride of new lines 227 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 228 if len(field.xpath("required")) == 1: 229 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 230 231 o.write('\n') 232 o.write('\t@param params: Dictionary of field names/values. \n') 233 o.write('\t@param out: File like object to write to\n') 234 235 o.write("\t@rtype: stdout\n") 236 o.write("\t@return: text to out\n") 237 o.write("\t'''\n\n") 238 239 ######################################## 240 # Actually build the code 241 242 o.write('\tif \'std\'==format:\n') 243 o.write('\t\tout.write("'+name+':\\n")\n') 244 245 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 246 247 # +1 for the ':' 248 maxFieldLen = 1 + getMaxFieldNameLen(msgET) 249 250 251 for field in msgET.xpath('field'): 252 name = field.attrib['name'] 253 type = field.attrib['type'] 254 numbits = int(field.attrib['numberofbits']) 255 required = None; 256 if hasSubtag(field,'required'): 257 required = field.xpath('required')[0].text 258 #print 'required set for',name,'to',required 259 unavailable=None; 260 if hasSubtag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 261 arraylen=1 262 if 'arraylength' in field.attrib: 263 arraylen=int(field.attrib['arraylength']) 264 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 265 else: 266 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 267 268 if 1==arraylen or type=='aisstr6': 269 o.write('\t\tout.write("\t'+padStrRight(name+':',maxFieldLen)+' "+str(params[\''+name+'\'])+"\\n")\n') 270 # FIX: elif aisstr6 strip @@@ and then print 271 else: 272 sys.exit ('FIX: handle arrays in the buildPrint func') 273 274 275 276 o.write('\n\treturn # Nothing to return\n\n')
277 278 ###################################################################### 279 # ENCODERS 280 ######################################################################
281 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
282 ''' 283 Build the encoder for boolean variables 284 @type o: file like obj 285 @param o: where write the code 286 @type name: str 287 @param name: field name 288 @type type: str 289 @param type: bool, etc. 290 @type numbits: int = 1 291 @param numbits: How many bits per unit datum (must be 1 for bools) 292 @type required: bool or None 293 @param required: If not None, then the value must be set to this. 294 @type arraylen: int >= 1 295 @param arraylen: many bools will there be? FIX: handle variable 296 @type unavailable: bool or None 297 @param unavailable: the default value to use if none given (if not None) 298 @return: None 299 ''' 300 301 if verbose: print 'bool encode',name,': unvail=',unavailable 302 303 assert type.lower()=='bool' 304 assert numbits==1 305 if arraylen != 1: assert False # FIX... handle arrays 306 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n') 307 if None != required: 308 assert type(required)==bool 309 if required: o.write('\t\tbvList.append(TrueBV)\n') 310 else: o.write('\t\tbvList.append(FalseBV)\n') 311 if verbose: o.write('\n') 312 return 313 314 if None==unavailable: 315 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n') 316 o.write('\telse: bvList.append(FalseBV)\n') 317 else: # Have a default value that can be filled in 318 assert type(unavailable)==bool 319 o.write("\tif '"+name+"' in params:\n") 320 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n') 321 o.write('\t\telse: bvList.append(FalseBV)\n') 322 o.write('\telse:\n') 323 if unavailable: o.write('\t\tbvList.append(TrueBV)\n') 324 else: o.write('\t\tbvList.append(FalseBV)\n') 325 if verbose: o.write('\n')
326
327 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
328 ''' 329 Build the encoder for unsigned integer variables 330 331 @type o: file like obj 332 @param o: where write the code 333 @type name: str 334 @param name: field name 335 @type type: str 336 @param type: uint, bool, etc. 337 @type numbits: int >= 1 338 @param numbits: How many bits per unit datum (must be 1..32) 339 @type required: bool or None 340 @param required: If not None, then the value must be set to this. 341 @type arraylen: int >= 1 342 @param arraylen: many unsigned ints will there be? FIX: handle variable 343 @type unavailable: bool or None 344 @param unavailable: the default value to use if none given (if not None) 345 @return: None 346 ''' 347 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 348 349 assert type=='uint' 350 assert numbits>=1 and numbits<=32 351 if arraylen != 1: assert False # FIX... handle arrays 352 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 353 354 if None != required: 355 if verbose: print ' required:',required 356 required=int(required) 357 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n') 358 if verbose: o.write('\n') 359 return 360 361 if None==unavailable: 362 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n') 363 else: # Have a default value that can be filled in 364 #assert type(unavailable)== 365 int(unavailable) # Make sure unavailable is a number object 366 o.write("\tif '"+name+"' in params:\n") 367 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n') 368 o.write('\telse:\n') 369 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n') 370 371 if verbose: o.write('\n')
372 373
374 -def encodeFloat(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
375 ''' 376 Build the encoder for IEEE float variables 377 378 @type o: file like obj 379 @param o: where write the code 380 @type name: str 381 @param name: field name 382 @type type: str 383 @param type: uint, bool, etc. 384 @type numbits: int >= 1 385 @param numbits: How many bits per unit datum (must be 1..32) 386 @type required: bool or None 387 @param required: If not None, then the value must be set to this. 388 @type arraylen: int >= 1 389 @param arraylen: many unsigned ints will there be? FIX: handle variable 390 @type unavailable: bool or None 391 @param unavailable: the default value to use if none given (if not None) 392 @return: None 393 ''' 394 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 395 396 assert numbits==32 # Force by the IEEE spec 397 if arraylen != 1: assert False # FIX... handle arrays 398 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 399 400 if None != required: 401 if verbose: print ' required:',required 402 required=int(required) 403 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n') 404 if verbose: o.write('\n') 405 return 406 407 if None==unavailable: 408 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n') 409 else: # Have a default value that can be filled in 410 #assert type(unavailable)== 411 int(unavailable) # Make sure unavailable is a number object 412 o.write("\tif '"+name+"' in params:\n") 413 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n') 414 o.write('\telse:\n') 415 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n') 416 417 if verbose: o.write('\n')
418 419
420 -def encodeAisstr6(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
421 ''' 422 Build the encoder for aisstr6 variables. Generally are arrays. 423 @bug: do we need to optionally check for a valid string? 424 425 @type o: file like obj 426 @param o: where write the code 427 @type name: str 428 @param name: field name 429 @type type: str 430 @param type: uint, bool, etc. 431 @type numbits: int >= 1 432 @param numbits: How many bits per unit datum (must be 1..32) 433 @type required: bool or None 434 @param required: If not None, then the value must be set to this. 435 @type arraylen: int >= 1 436 @param arraylen: many unsigned ints will there be? FIX: handle variable 437 @type unavailable: bool or None 438 @param unavailable: the default value to use if none given (if not None) 439 @return: None 440 ''' 441 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 442 443 assert numbits==6 # Each character must be 6 bits 444 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 445 446 if None != required: 447 if verbose: print ' required:',required 448 required=int(required) 449 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+str(numbits*arraylen)+'))\n') 450 if verbose: o.write('\n') 451 return 452 453 if None==unavailable: 454 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+str(numbits*arraylen)+'))\n') 455 else: # Have a default value that can be filled in 456 o.write("\tif '"+name+"' in params:\n") 457 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+str(numbits*arraylen)+'))\n') 458 o.write('\telse:\n') 459 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+str(numbits*arraylen)+'))\n') 460 461 if verbose: o.write('\n')
462 463
464 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
465 ''' 466 Build the encoder for signed integer variables 467 468 @type o: file like obj 469 @param o: where write the code 470 @type name: str 471 @param name: field name 472 @type type: str 473 @param type: uint, bool, etc. 474 @type numbits: int >= 1 475 @param numbits: How many bits per unit datum (must be 1..32) 476 @type required: bool or None 477 @param required: If not None, then the value must be set to this. 478 @type arraylen: int >= 1 479 @param arraylen: many signed ints will there be? FIX: handle variable 480 @type unavailable: number or None 481 @param unavailable: the default value to use if none given (if not None) 482 @return: None 483 ''' 484 if verbose: print ' encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 485 486 assert numbits>=1 and numbits<=32 487 if arraylen != 1: assert False # FIX... handle arrays 488 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 489 490 if None != required: 491 if verbose: print ' required:',required 492 required=int(required) 493 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n') 494 if verbose: o.write('\n') 495 return 496 497 498 if None==unavailable: 499 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n') 500 else: # Have a default value that can be filled in 501 #assert type(unavailable)== 502 int(unavailable) # Make sure unavailable is a number object 503 o.write("\tif '"+name+"' in params:\n") 504 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n') 505 o.write('\telse:\n') 506 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n') 507 508 if verbose: o.write('\n')
509 510 511 512 # FIX: Ummm... why am I passing the type? I guess it makes the one print statement easier
513 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
514 ''' 515 Build the encoder for signed decimal variables 516 517 @type o: file like obj 518 @param o: where write the code 519 @type name: str 520 @param name: field name 521 @type type: str 522 @param type: decimal 523 @type numbits: int >= 1 524 @param numbits: How many bits per unit datum (must be 1..32) 525 @type required: bool or None 526 @param required: If not None, then the value must be set to this. 527 @type arraylen: int >= 1 528 @param arraylen: many decimals will there be? FIX: handle variable 529 @type unavailable: Decimal or None 530 @param unavailable: the default value to use if none given (if not None) 531 @return: None 532 ''' 533 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 534 535 assert numbits>=1 and numbits<=32 536 if arraylen != 1: assert False # FIX... handle arrays 537 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 538 539 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 540 if None == scale: 541 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 542 print 'Beware canadians bearing travel videos' 543 scale='1' 544 545 if None != required: 546 if verbose: print ' required:',required 547 required=int(required) 548 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n') 549 if verbose: o.write('\n') 550 return 551 552 # FIX: can I get rid of the Decimal around params? 553 if None==unavailable: 554 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 555 else: # Have a default value that can be filled in 556 o.write("\tif '"+name+"' in params:\n") 557 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 558 o.write('\telse:\n') 559 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n') 560 561 if verbose: o.write('\n')
562 563 564
565 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
566 ''' 567 Build the encoder for signed decimal variables 568 569 @type o: file like obj 570 @param o: where write the code 571 @type name: str 572 @param name: field name 573 @type type: str 574 @param type: decimal 575 @type numbits: int >= 1 576 @param numbits: How many bits per unit datum (must be 1..32) 577 @type required: bool or None 578 @param required: If not None, then the value must be set to this. 579 @type arraylen: int >= 1 580 @param arraylen: many decimals will there be? FIX: handle variable 581 @type unavailable: Decimal or None 582 @param unavailable: the default value to use if none given (if not None) 583 @return: None 584 ''' 585 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 586 assert type=='udecimal' 587 assert numbits>=1 and numbits<=32 588 if arraylen != 1: assert False # FIX... handle arrays 589 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 590 591 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 592 if None == scale: 593 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 594 print 'Beware canadians bearing travel videos' 595 scale='1' 596 597 if None != required: 598 if verbose: print ' required:',required 599 required=int(required) 600 assert(0<=required) 601 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n') 602 if verbose: o.write('\n') 603 return 604 605 # FIX: can I get rid of the Decimal around params? 606 if None==unavailable: 607 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 608 else: # Have a default value that can be filled in 609 o.write("\tif '"+name+"' in params:\n") 610 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n') 611 o.write('\telse:\n') 612 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n') 613 614 if verbose: o.write('\n')
615 616 617
618 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
619 ''' 620 Build the encoder for binary variables. This is just a pass through. 621 This is used for the ais binary message wrapper (e.g. msg 8). Do 622 not use it within binary messages. 623 624 @type o: file like obj 625 @param o: where write the code 626 @type name: str 627 @param name: field name 628 @type type: str 629 @param type: binary 630 @type numbits: int >= 1 631 @param numbits: How many bits per unit datum (must be 1..1024 or so) 632 @type required: bool or None 633 @param required: If not None, then the value must be set to this. 634 @type arraylen: int >= 1 635 @param arraylen: many decimals will there be? FIX: handle variable 636 @type unavailable: Decimal or None 637 @param unavailable: the default value to use if none given (if not None) 638 @return: None 639 ''' 640 if verbose: print ' encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable 641 assert type=='binary' 642 assert numbits>=1 and numbits<=1024 643 assert (None == required) # don't allow this 644 assert (None == unavailable) # don't allow this 645 646 if arraylen != 1: assert False # Do not handle arrays. Arrays of bits is just not necessary. 647 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 648 649 # FIX: can I get rid of the Decimal around params? 650 o.write('\tbvList.append(params[\''+name+'\'])\n') # Just pass it through 651 652 if verbose: o.write('\n')
653 654 655 ###################################################################### 656 # DECODERS 657 ###################################################################### 658
659 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 660 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
661 ''' 662 Build the decoder for boolean variables 663 664 @type o: file like obj 665 @param o: where write the code 666 @type name: str 667 @param name: field name 668 @type type: str 669 @param type: uint, bool, etc. 670 @type startindex: int 671 @param startindex: bit that begins the bool(s) 672 @type numbits: int = 1 673 @param numbits: How many bits per unit datum (must be 1 for bools) 674 @type required: bool or None 675 @param required: If not None, then the value must be set to this. 676 @type arraylen: int >= 1 677 @param arraylen: many bools will there be? FIX: handle variable 678 @type unavailable: bool or None 679 @param unavailable: the default value to use if none given (if not None) 680 @type bv: str 681 @param bv: BitVector containing the incoming data 682 @type dataDict: str 683 @param dataDict: dictionary in which to place the results 684 @type decodeOnly: bool 685 @param decodeOnly: Set to true to only get the code for decoding 686 @rtype: int 687 @return: index one past the end of where this read 688 ''' 689 assert(type=='bool') 690 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 691 #int(startindex); int(numbits) # Make sure it is a number 692 assert numbits==1 693 assert arraylen == 1 # FIX... handle arrays 694 695 if None != required: 696 assert type(required)==bool 697 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 698 if required: o.write('True\n') 699 else: o.write('False\n') 700 if not decodeOnly: o.write('\n') 701 return int(startindex)+int(numbits) 702 703 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 704 o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))') 705 if not decodeOnly: o.write('\n') 706 707 return int(startindex)+int(numbits)
708 709
710 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 711 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
712 ''' 713 Build the decoder for unsigned integer variables 714 715 @type o: file like obj 716 @param o: where write the code 717 @type name: str 718 @param name: field name 719 @type type: str 720 @param type: uint, etc. 721 @type startindex: int 722 @param startindex: bit that begins the uint(s) 723 @type numbits: int >= 1 724 @param numbits: How many bits per unit datum 725 @type required: int or None 726 @param required: If not None, then the value must be set to this. 727 @type arraylen: int >= 1 728 @param arraylen: many ints will there be? FIX: handle variable 729 @type unavailable: int or None 730 @param unavailable: the default value to use if none given (if not None) 731 @type bv: str 732 @param bv: BitVector containing the incoming data 733 @type dataDict: str 734 @param dataDict: dictionary in which to place the results 735 @type decodeOnly: bool 736 @param decodeOnly: Set to true to only get the code for decoding 737 @rtype: int 738 @return: index one past the end of where this read 739 ''' 740 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 741 if None==arraylen: arraylen=1 742 assert arraylen == 1 # FIX... handle arrays 743 assert numbits>=1 744 if not decodeOnly: verbose=False 745 746 if None != required: 747 int(required) # Make sure required is a number 748 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 749 o.write(str(required)) 750 if not decodeOnly: o.write('\n') 751 return startindex+numbits 752 753 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 754 o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])') 755 if not decodeOnly: o.write('\n') 756 if verbose: o.write('\n') 757 758 return startindex+numbits
759 760
761 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 762 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
763 ''' 764 Build the decoder for unsigned integer variables 765 766 @type o: file like obj 767 @param o: where write the code 768 @type name: str 769 @param name: field name 770 @type type: str 771 @param type: int 772 @type startindex: int 773 @param startindex: bit that begins the int(s) 774 @type numbits: int >= 1 775 @param numbits: How many bits per unit datum 776 @type required: int or None 777 @param required: If not None, then the value must be set to this. 778 @type arraylen: int >= 1 779 @param arraylen: many ints will there be? FIX: handle variable 780 @type unavailable: int or None 781 @param unavailable: the default value to use if none given (if not None) 782 @type bv: str 783 @param bv: BitVector containing the incoming data 784 @type dataDict: str 785 @param dataDict: dictionary in which to place the results 786 @type decodeOnly: bool 787 @param decodeOnly: Set to true to only get the code for decoding 788 @rtype: int 789 @return: index one past the end of where this read 790 ''' 791 assert type=='int' 792 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 793 if None==arraylen: arraylen=1 794 end = startindex+int(numbits)*int(arraylen) 795 assert arraylen == 1 # FIX... handle arrays 796 assert numbits>=1 797 798 if None != required: 799 int(required) # Make sure required is a number 800 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 801 o.write(str(required)) 802 if not decodeOnly: o.write('\n') 803 return end 804 805 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 806 o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])') 807 if not decodeOnly: o.write('\n') 808 if verbose: o.write('\n') 809 810 return end
811
812 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 813 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
814 ''' 815 Build the decoder for IEEE float variables 816 817 @type o: file like obj 818 @param o: where write the code 819 @type name: str 820 @param name: field name 821 @type type: str 822 @param type: int 823 @type startindex: int 824 @param startindex: bit that begins the int(s) 825 @type numbits: int >= 1 826 @param numbits: How many bits per unit datum 827 @type required: float or None 828 @param required: If not None, then the value must be set to this. 829 @type arraylen: int >= 1 830 @param arraylen: many ints will there be? FIX: handle variable 831 @type unavailable: float or None 832 @param unavailable: the default value to use if none given (if not None) 833 @type bv: str 834 @param bv: BitVector containing the incoming data 835 @type dataDict: str 836 @param dataDict: dictionary in which to place the results 837 @type decodeOnly: bool 838 @param decodeOnly: Set to true to only get the code for decoding 839 @rtype: int 840 @return: index one past the end of where this read 841 ''' 842 assert type=='float' 843 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 844 if None==arraylen: arraylen=1 845 end = startindex+int(numbits)*int(arraylen) 846 assert arraylen == 1 # FIX... handle arrays 847 assert numbits>=1 848 849 if None != required: 850 float(required) # Make sure required is a number 851 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 852 o.write(str(required)) 853 if not decodeOnly: o.write('\n') 854 if verbose: o.write('\n') 855 return end 856 857 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 858 o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])') 859 if not decodeOnly: o.write('\n') 860 861 return end
862 863
864 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 865 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
866 ''' 867 Build the decoder for aisstr6 variables. Generally arrays. 868 @bug: FIX: validate strings?? 869 @type o: file like obj 870 @param o: where write the code 871 @type name: str 872 @param name: field name 873 @type type: str 874 @param type: 'aisstr6' 875 @type startindex: int 876 @param startindex: bit that begins the int(s) 877 @type numbits: int >= 1 878 @param numbits: How many bits per unit datum 879 @type required: restricted str or None 880 @param required: If not None, then the value must be set to this. 881 @type arraylen: int >= 1 882 @param arraylen: many ints will there be? FIX: handle variable 883 @type unavailable: restricted str or None 884 @param unavailable: the default value to use if none given (if not None) 885 @type bv: str 886 @param bv: BitVector containing the incoming data 887 @type dataDict: str 888 @param dataDict: dictionary in which to place the results 889 @type decodeOnly: bool 890 @param decodeOnly: Set to true to only get the code for decoding 891 @rtype: int 892 @return: index one past the end of where this read 893 ''' 894 assert type=='aisstr6' 895 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 896 if None==arraylen: arraylen=1 897 end = startindex+int(numbits)*int(arraylen) 898 assert arraylen >= 1 # FIX... handle arrays 899 assert numbits>=1 900 901 if None != required: 902 float(required) # Make sure required is a number 903 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 904 o.write(required) 905 if not decodeOnly: o.write('\n') 906 return end 907 908 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 909 o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])') 910 if not decodeOnly: o.write('\n') 911 912 return end
913 914
915 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 916 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
917 ''' 918 Build the decoder for signed decimal variables 919 920 @type o: file like obj 921 @param o: where write the code 922 @type name: str 923 @param name: field name 924 @type type: str 925 @param type: 'decimal' 926 @type startindex: int 927 @param startindex: bit that begins the int(s) 928 @type numbits: int >= 1 929 @param numbits: How many bits per unit datum 930 @type required: Decimal or None 931 @param required: If not None, then the value must be set to this. 932 @type arraylen: int >= 1 933 @param arraylen: many ints will there be? FIX: handle variable 934 @type unavailable: Decimal or None 935 @param unavailable: the default value to use if none given (if not None) 936 @type bv: str 937 @param bv: BitVector containing the incoming data 938 @type dataDict: str 939 @param dataDict: dictionary in which to place the results 940 @type decodeOnly: bool 941 @param decodeOnly: Set to true to only get the code for decoding 942 @rtype: int 943 @return: index one past the end of where this read 944 ''' 945 assert type=='decimal' 946 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 947 if None==arraylen: arraylen=1 948 end = startindex+int(numbits)*int(arraylen) 949 assert arraylen == 1 # FIX... handle arrays 950 assert numbits>=1 and numbits <= 32 951 952 if None == scale: scale='1' # Warning about this was in the encode section 953 954 if None != required: 955 Decimal(required) # Make sure required is a number 956 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 957 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 958 if not decodeOnly: o.write('\n') 959 return end 960 961 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 962 o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')') 963 if not decodeOnly: o.write('\n') 964 965 return end
966 967 968 969
970 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 971 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
972 ''' 973 Build the decoder for unsigned decimal variables 974 975 @type o: file like obj 976 @param o: where write the code 977 @type name: str 978 @param name: field name 979 @type type: str 980 @param type: 'udecimal' 981 @type startindex: int 982 @param startindex: bit that begins the int(s) 983 @type numbits: int >= 1 984 @param numbits: How many bits per unit datum 985 @type required: Decimal or None 986 @param required: If not None, then the value must be set to this. 987 @type arraylen: int >= 1 988 @param arraylen: many ints will there be? FIX: handle variable 989 @type unavailable: Decimal or None 990 @param unavailable: the default value to use if none given (if not None) 991 @type bv: str 992 @param bv: BitVector containing the incoming data 993 @type dataDict: str 994 @param dataDict: dictionary in which to place the results 995 @type decodeOnly: bool 996 @param decodeOnly: Set to true to only get the code for decoding 997 @rtype: int 998 @return: index one past the end of where this read 999 ''' 1000 assert type=='udecimal' 1001 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1002 if None==arraylen: arraylen=1 1003 end = startindex+int(numbits)*int(arraylen) 1004 assert arraylen == 1 # FIX... handle arrays 1005 assert numbits>=1 and numbits <= 32 1006 1007 if None == scale: scale='1' # Warning about this was in the encode section 1008 1009 if None != required: 1010 assert (Decimal(required)>=0.) # Make sure required is a number and not negative 1011 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1012 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')') 1013 if not decodeOnly: o.write('\n') 1014 return end 1015 1016 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1017 o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')') 1018 if not decodeOnly: o.write('\n') 1019 1020 return end
1021 1022
1023 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 1024 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1025 ''' 1026 Build the decoder for unsigned decimal variables 1027 1028 @type o: file like obj 1029 @param o: where write the code 1030 @type name: str 1031 @param name: field name 1032 @type type: str 1033 @param type: 'udecimal' 1034 @type startindex: int 1035 @param startindex: bit that begins the int(s) 1036 @type numbits: int >= 1 1037 @param numbits: How many bits per unit datum 1038 @type required: Decimal or None 1039 @param required: If not None, then the value must be set to this. 1040 @type arraylen: int >= 1 1041 @param arraylen: many ints will there be? FIX: handle variable 1042 @type unavailable: Decimal or None 1043 @param unavailable: the default value to use if none given (if not None) 1044 @type bv: str 1045 @param bv: BitVector containing the incoming data 1046 @type dataDict: str 1047 @param dataDict: dictionary in which to place the results 1048 @type decodeOnly: bool 1049 @param decodeOnly: Set to true to only get the code for decoding 1050 @rtype: int 1051 @return: index one past the end of where this read 1052 ''' 1053 assert type=='binary' 1054 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 1055 if None==arraylen: arraylen=1 1056 end = startindex+int(numbits)*int(arraylen) 1057 assert arraylen == 1 # FIX... handle arrays 1058 assert numbits>=1 and numbits <= 1024 # What is good max? 1059 # FIX: assert not required and not an array an not unavailable 1060 1061 1062 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=') 1063 o.write(bv+'['+str(startindex)+':'+str(end)+']') 1064 if not decodeOnly: o.write('\n') 1065 1066 return end
1067 1068 1069 1070 ###################################################################### 1071 # THE REST 1072 ###################################################################### 1073 1074 1075
1076 -def buildTestParamFunc(o,msgET, verbose=False, prefixName=False):
1077 '''Scrape the testvalues to make a basic param 1078 1079 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies? 1080 ''' 1081 name = msgET.attrib['name'] 1082 1083 if prefixName: o.write('def '+name+'TestParams():\n') 1084 else: o.write('def testParams():\n') 1085 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") 1086 o.write('\tparams = {}\n') 1087 for field in msgET.xpath('field'): 1088 name = field.attrib['name'] 1089 type = field.attrib['type'] 1090 if verbose: print 'buildTestParamFunc ...',name,type 1091 val = None 1092 if hasSubtag(field,'testvalue') and hasSubtag(field,'required'): 1093 print 'ERROR: can not have both test value and required tags in the same field' 1094 assert(False) 1095 if hasSubtag(field,'testvalue'): 1096 val = field.xpath('testvalue')[0].text 1097 else: 1098 if not hasSubtag(field,'required'): 1099 sys.exit("ERROR: missing required or testvalue for field: "+name) 1100 val = field.xpath('required')[0].text 1101 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val 1102 o.write('\tparams[\''+name+'\'] = ') 1103 if type=='bool': 1104 if val=='1' or val.lower=='true': val = 'True' 1105 else: val = 'False' 1106 o.write(val) 1107 elif type in ('uint','int','float'): 1108 o.write(val) 1109 elif type in ('decimal','udecimal'): 1110 o.write('Decimal(\''+val+'\')') 1111 1112 elif type in ('aisstr6'): 1113 o.write('\''+val+'\'') 1114 elif type in ('binary'): 1115 o.write('BitVector(bitstring=\''+val+'\')') 1116 else: 1117 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee' 1118 suggestType(name,type) 1119 assert(False) 1120 1121 o.write('\n') 1122 1123 1124 o.write('\n\treturn params\n\n')
1125
1126 -def buildUnitTest(o,msgET, verbose=False, prefixName=False):
1127 ''' 1128 Write the unittests for a message 1129 1130 param o: open file where resulting code will be written 1131 param msgET: Element Tree starting at a message node 1132 ''' 1133 assert(msgET.tag=='message') 1134 name = msgET.attrib['name'] 1135 1136 buildTestParamFunc(o,msgET, prefixName=prefixName) 1137 1138 o.write('class Test'+name+'(unittest.TestCase):\n') 1139 o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n") 1140 o.write('\tdef testEncodeDecode(self):\n\n') 1141 if prefixName: 1142 o.write('\t\tparams = '+name+'TestParams()\n') 1143 o.write('\t\tbits = '+name+'Encode(params)\n') 1144 o.write('\t\tr = '+name+'Decode(bits)\n\n') 1145 else: 1146 o.write('\t\tparams = testParams()\n') 1147 o.write('\t\tbits = encode(params)\n') 1148 o.write('\t\tr = decode(bits)\n\n') 1149 1150 o.write('\t\t# Check that each parameter came through ok.\n') 1151 for field in msgET.xpath('field'): 1152 name = field.attrib['name'] 1153 type = field.attrib['type'] 1154 if type in ('bool','uint','int','aisstr6','binary'): 1155 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n') 1156 else: 1157 # float, decimal, udecimal 1158 # FIX: look up the decimal places if decimal 1159 places = '7' 1160 if hasSubtag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text 1161 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
1162 1163 1164
1165 -def buildEncode(o,msgET, verbose=False, prefixName=False):
1166 ''' 1167 Write the encoder/decoder for a message 1168 1169 http://jaynes.colorado.edu/PythonIdioms.html 1170 1171 param o: open file where resulting code will be written 1172 param msgET: Element Tree starting at a message node 1173 ''' 1174 assert(msgET.tag=='message') 1175 name = msgET.attrib['name'] 1176 1177 print 'Generating encoder for',name # FIX: verbose? 1178 funcName = 'encode' 1179 if prefixName: funcName = name+'Encode' 1180 o.write('def '+funcName+'(params, validate=False):\n') 1181 1182 ######################################## 1183 # doc string 1184 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n") 1185 o.write('\tFields in params:\n') 1186 for field in msgET.xpath('field'): 1187 desc = field[0].text.replace('\n',' ') # get ride of new lines 1188 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1189 if len(field.xpath("required")) == 1: 1190 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1191 1192 o.write('\n') 1193 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n') 1194 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1195 1196 o.write("\t@rtype: BitVector\n") 1197 o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n") 1198 o.write("\t@note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.\n") 1199 o.write("\t'''\n\n") 1200 1201 ######################################## 1202 # Actually build the code 1203 1204 o.write('\tbvList = []\n') 1205 1206 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1207 1208 dynamicArrays = False # Set to true when positioning must be calculated 1209 1210 for field in msgET.xpath('field'): 1211 name = field.attrib['name'] 1212 type = field.attrib['type'] 1213 numbits = int(field.attrib['numberofbits']) 1214 required = None; 1215 if hasSubtag(field,'required'): 1216 required = field.xpath('required')[0].text 1217 #print 'required set for',name,'to',required 1218 unavailable=None; 1219 if hasSubtag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1220 arraylen=1 1221 if 'arraylength' in field.attrib: 1222 arraylen=int(field.attrib['arraylength']) 1223 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1224 else: 1225 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 1226 1227 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable) 1228 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable) 1229 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable) 1230 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable) 1231 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable) 1232 elif type=='decimal': 1233 scale = None 1234 if hasSubtag(field,'scale'): scale = field.xpath('scale')[0].text 1235 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale) 1236 elif type=='udecimal': 1237 scale = None 1238 if hasSubtag(field,'scale'): scale = field.xpath('scale')[0].text 1239 encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale) 1240 elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable) 1241 else: 1242 print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type 1243 suggestType (name,type) 1244 assert False 1245 1246 # o.write('\n\tbv=binary.joinBV(bvList)\n') 1247 # o.write('\n\tbvLen=len(bv)\n') 1248 # o.write('\n\tif bvLen%6!=0:\n') 1249 # o.write('\n\t bv = bv + BitVector(size=bvLen%6) # \n') 1250 # o.write('\n\treturn bv\n\n') 1251 o.write('\n\treturn binary.joinBV(bvList)\n\n')
1252 1253 1254 ###################################################################### 1255 # DECODER ONE PART AT A TIME - if only using a small part, save some cycles! 1256 1257 1258 #, msgDict=None):
1259 -def buildDecodeParts(o,msgET, verbose=False, prefixName=False):
1260 1261 ''' 1262 Write the decoder for a message 1263 1264 param o: open file where resulting code will be written 1265 type msgET: elementtree 1266 param prefixName: if True, put the name of the message on the functions. 1267 param msgET: Element Tree starting at a message node 1268 return: None 1269 1270 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 1271 @todo: May want to take a dictionary of already decoded fields to speed things that need prior info 1272 for things like variable length arrays 1273 ''' 1274 1275 assert(msgET.tag=='message') 1276 name = msgET.attrib['name'] 1277 1278 print 'Generating partial decode functions for',name 1279 1280 baseName = name+'Decode' 1281 if not prefixName: baseName = 'decode' 1282 1283 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 1284 1285 for field in msgET.xpath('field'): 1286 name = field.attrib['name'] 1287 type = field.attrib['type'] 1288 1289 o.write('def '+baseName+name+'(bv, validate=False):\n') 1290 # Follow the same convention of decoding into a dict so that code is the same 1291 #o.write('\tr={};') 1292 o.write('\treturn ') 1293 1294 numbits = int(field.attrib['numberofbits']) 1295 required = None; 1296 if hasSubtag(field,'required'): 1297 required = field.xpath('required')[0].text 1298 unavailable=None; 1299 if hasSubtag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1300 arraylen=1 1301 if 'arraylength' in field.attrib: 1302 arraylen=int(field.attrib['arraylength']) 1303 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1304 1305 assert None!=startindex 1306 if verbose: print 'startindex',startindex 1307 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1308 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1309 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1310 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1311 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1312 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True) 1313 1314 elif type=='decimal': 1315 scale = None 1316 if hasSubtag(field,'scale'): scale = field.xpath('scale')[0].text 1317 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True) 1318 elif type=='udecimal': 1319 scale = None 1320 if hasSubtag(field,'scale'): scale = field.xpath('scale')[0].text 1321 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True) 1322 1323 else: 1324 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 1325 suggestType (name,type) 1326 assert False 1327 1328 #o.write('\treturn(r[\''+name+'\'])\n\n') 1329 o.write('\n\n')
1330 1331 1332 ###################################################################### 1333 # DECODER RING 1334
1335 -def buildDecode(o,msgET, verbose=False, prefixName=False):
1336 ''' 1337 Write the decoder for a message 1338 1339 param o: open file where resulting code will be written 1340 type msgET: elementtree 1341 param msgET: Element Tree starting at a message node 1342 return: None 1343 1344 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload 1345 ''' 1346 1347 assert(msgET.tag=='message') 1348 name = msgET.attrib['name'] 1349 1350 print 'Generating decoder for',name 1351 funcName = 'decode' 1352 if prefixName: funcName = name+'Decode' 1353 o.write('def '+funcName+'(bv, validate=False):\n') 1354 1355 ######################################## 1356 # doc string 1357 o.write("\t'''Unpack a "+name+" message \n\n") 1358 o.write('\tFields in params:\n') 1359 for field in msgET.xpath('field'): 1360 desc = field[0].text.replace('\n',' ') # get ride of new lines 1361 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 1362 if len(field.xpath("required")) == 1: 1363 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 1364 1365 o.write('\n') 1366 o.write('\t@type bv: BitVector\n') 1367 o.write('\t@param bv: Bits defining a message\n') 1368 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 1369 1370 o.write("\t@rtype: dict\n") 1371 o.write("\t@return: params\n") 1372 o.write("\t'''\n\n") 1373 1374 1375 o.write('\t#Would be nice to check the bit count here..\n') 1376 o.write('\t#if validate:\n') 1377 o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n') 1378 1379 1380 ######################################## 1381 # Actually build the code 1382 1383 o.write('\tr = {}\n') 1384 1385 1386 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 1387 1388 dynamicArrays = False # Set to true when positioning must be calculated 1389 1390 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 1391 1392 for field in msgET.xpath('field'): 1393 name = field.attrib['name'] 1394 type = field.attrib['type'] 1395 numbits = int(field.attrib['numberofbits']) 1396 required = None; 1397 if hasSubtag(field,'required'): 1398 required = field.xpath('required')[0].text 1399 #print 'required set for',name,'to',required 1400 unavailable=None; 1401 if hasSubtag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 1402 arraylen=1 1403 if 'arraylength' in field.attrib: 1404 arraylen=int(field.attrib['arraylength']) 1405 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 1406 else: 1407 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 1408 1409 assert None!=startindex 1410 if verbose: print 'startindex',startindex 1411 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable) 1412 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 1413 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 1414 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable) 1415 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable) 1416 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable) 1417 1418 elif type=='decimal': 1419 scale = None 1420 if hasSubtag(field,'scale'): scale = field.xpath('scale')[0].text 1421 #print 'pre call...' 1422 #print ' required: "'+str(required)+'" scale: "'+str(scale)+'" unavail:', unavailable 1423 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale) 1424 elif type=='udecimal': 1425 scale = None 1426 if hasSubtag(field,'scale'): scale = field.xpath('scale')[0].text 1427 #print 'pre call...' 1428 #print ' required: "'+str(required)+'" scale: "'+str(scale)+'" unavail:', unavailable 1429 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale) 1430 1431 else: 1432 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 1433 suggestType (name,type) 1434 assert False 1435 1436 1437 if None==startindex: print 'FIX: here. drat. treat me right' 1438 assert None!=startindex 1439 1440 1441 o.write('\treturn r\n\n')
1442 1443 1444 1445 #def getPythonType(aType): 1446 # ''' 1447 # Translate a 1448 # @rtype: str 1449 # @return: name of the python type 1450 # ''' 1451 1452 aisType2pythonType={ 1453 'bool':'Bool', 1454 'uint':'int', 1455 'int':'int', 1456 'udecimal':'Decimal', 1457 'decimal':'Decimal', 1458 'aisstr6':'str', 1459 'binary':'str', 1460 'float':'Float', 1461 } 1462 1463 import copy 1464 aisType2optParseType=copy.deepcopy(aisType2pythonType) 1465 aisType2optParseType['bool']='int' # FIX: make these appear as a flag variable 1466 aisType2optParseType['aisstr6']='string' 1467 aisType2optParseType['aisstr6']='string' 1468 aisType2optParseType['binary']='string' 1469 aisType2optParseType['udecimal']='string' # FIX: Is this a good choice? 1470 aisType2optParseType['decimal']='string' 1471 aisType2optParseType['float']='float' 1472 1473
1474 -def buildMain(o, msgET, prefixName=False):
1475 assert None != msgET 1476 assert msgET.tag=='message' 1477 msgName = msgET.attrib['name'] 1478 1479 prefix='' 1480 if prefixName: prefix=msgName 1481 1482 o.write(''' 1483 ############################################################ 1484 if __name__=='__main__': 1485 1486 from optparse import OptionParser 1487 myparser = OptionParser(usage="%prog [options]", 1488 version="%prog "+__version__) 1489 1490 #sys.exit('EARLY EXIT - FIX: remove') 1491 myparser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 1492 help='run the documentation tests') 1493 myparser.add_option('--unit-test',dest='unittest',default=False,action='store_true', 1494 help='run the unit tests') 1495 myparser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 1496 help='Make the test output verbose') 1497 1498 1499 myparser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true', 1500 help='decode a "'''+msgName+'''" AIS message') 1501 myparser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true', 1502 help='encode a "'''+msgName+'''" AIS message') 1503 1504 1505 # FIX: remove nmea from binary messages. No way to build the whole packet? 1506 # FIX: or build the surrounding msg 8 for a broadcast? 1507 typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message? 1508 myparser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType' 1509 ,default='nmeapayload' 1510 ,help='What kind of string to expect ('+', '.join(typeChoices)+') [default: %default]') 1511 1512 ''') 1513 1514 #print 'here...', msgName, prefixName 1515 for field in msgET.xpath('field'): 1516 name = field.attrib['name'] 1517 fieldType = field.attrib['type'] 1518 if hasSubtag(field,'required'): 1519 print 'skipping required field ...',name,fieldType 1520 continue 1521 #print 'there',name,fieldType 1522 o.write(' myparser.add_option(\'--') 1523 if prefixName: o.write(msgName+'-') 1524 o.write(name+'-field\', dest=\''+name+'Field\'') 1525 if hasSubtag(field,'unavailable'): 1526 val = field.xpath('unavailable')[0].text 1527 o.write(',default=') 1528 if fieldType in ('uint','int','float'): 1529 o.write(val) 1530 elif fieldType in ('decimal','udecimal'): 1531 o.write('Decimal(\''+val+'\')') 1532 elif fieldType in ('aisstr6','bitvector'): 1533 o.write('\''+val+'\'') 1534 o.write('\n\t,help=\'Field parameter value [default: %default]\'') 1535 o.write('\n\t,metavar=\''+fieldType+'\'') 1536 o.write('\n\t,type=\''+aisType2optParseType[fieldType]+'\'') 1537 o.write('\n\t)\n') 1538 1539 1540 o.write(''' 1541 (options,args) = myparser.parse_args() 1542 success=True 1543 1544 if options.doctest: 1545 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 1546 sys.argv= [sys.argv[0]] 1547 if options.verbose: sys.argv.append('-v') 1548 import doctest 1549 numfail,numtests=doctest.testmod() 1550 if numfail==0: print 'ok' 1551 else: 1552 print 'FAILED' 1553 success=False 1554 1555 if not success: 1556 sys.exit('Something Failed') 1557 1558 del success # Hide success from epydoc 1559 1560 if options.unittest: 1561 sys.argv = [sys.argv[0]] 1562 if options.verbose: sys.argv.append('-v') 1563 unittest.main() 1564 ''') 1565 1566 1567 ############################## 1568 # encode 1569 ############################## 1570 # FIX: make everything tabs. Grr. 1571 o.write('\n if options.doEncode:\n') 1572 o.write('\t\t# First make sure all non required options are specified\n') 1573 for field in msgET.xpath('field'): 1574 name = field.attrib['name'] 1575 fieldType = field.attrib['type'] 1576 varName = prefix+name+'Field' 1577 if not hasSubtag(field,'required'): 1578 o.write('\t\tif None==options.'+varName+': myparser.error("missing value for '+varName+'")\n') 1579 1580 # Build dict 1581 o.write('\t\tmsgDict={\n') 1582 for field in msgET.xpath('field'): 1583 name = field.attrib['name'] 1584 varName = prefix+name+'Field' 1585 if hasSubtag(field,'required'): 1586 o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n') 1587 else: 1588 o.write('\t\t\t\''+name+'\': options.'+varName+',\n') 1589 o.write('\t\t}\n') 1590 1591 encodeFunction = 'encode' 1592 if prefixName: encodeFunction = msgName+'Encode' 1593 o.write('\t\tbits = '+encodeFunction+'(msgDict)\n') 1594 o.write('\t\tif \'binary\'==options.ioType: print str(bits)\n') 1595 o.write('\t\telif \'nmeapayload\'==options.ioType:\n') 1596 # FIX: figure out if this might be necessary at compile time 1597 o.write('\t\t\tprint "bitLen",len(bits)\n') 1598 o.write('\t\t\tbitLen=len(bits)\n') 1599 o.write('\t\t\tif bitLen%6!=0:\n') 1600 #o.write('\t\t\t print "padding...",bitLen%6\n') 1601 o.write('\t\t\t bits = bits + BitVector(size=(6 - (bitLen%6))) # Pad out to multiple of 6\n') 1602 #o.write('\t\t\tprint "bitLen",len(bits)\n') 1603 o.write('\t\t\tprint "result:",binary.bitvectoais6(bits)[0]\n') 1604 1605 1606 # FIX: Do not emit this option for the binary message payloads. Does not make sense. 1607 #o.write('\t\tif \'nmea\'==options.ioType: print binarybits\n') 1608 o.write('\t\telif \'nmea\'==options.ioType: sys.exit("FIX: need to implement this capability")\n') 1609 o.write('\t\telse: sys.exit(\'ERROR: unknown ioType. Help!\')\n') 1610 1611 1612 ############################## 1613 # decode all 1614 ############################## 1615 decodeFunction = 'decode' 1616 printFields='printFields' 1617 if prefixName: 1618 decodeFunction = msgName+'Decode' 1619 printFields = msgName+'PrintFields' 1620 o.write('\n if options.doDecode:\n') 1621 o.write('\t\tfor msg in args:\n') 1622 o.write('\t\t\tif \'binary\'==options.ioType:\n') 1623 o.write('\t\t\t bv = BitVector(bitstring=msg)\n') 1624 o.write('\t\t\t '+printFields+'('+decodeFunction+'(bv))\n') 1625 1626 o.write('\t\t\telif \'nmeapayload\'==options.ioType:\n') 1627 o.write('\t\t\t bv = binary.ais6tobitvec(msg)\n') 1628 o.write('\t\t\t '+printFields+'('+decodeFunction+'(bv))\n') 1629 1630 o.write('\t\t\telif \'nmea\'==options.ioType:\n') 1631 o.write('\t\t\t bv = binary.ais6tobitvec(msg.split(\',\')[5])\n') 1632 o.write('\t\t\t '+printFields+'('+decodeFunction+'(bv))\n') 1633 o.write('\t\t\telse: sys.exit(\'ERROR: unknown ioType. Help!\')\n') 1634 o.write('\n')
1635 1636 1637 1638 1639 ###################################################################### 1640 if __name__=='__main__': 1641 from optparse import OptionParser 1642 myparser = OptionParser(usage="%prog [options]", 1643 version="%prog "+__version__) 1644 1645 myparser.add_option('-o','--output',dest='outputFileName',default=None, 1646 help='Name of the python file to write') 1647 # help='Name of the python file to write [default: stdout]') 1648 1649 myparser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None, 1650 help='XML definition file for the msg to use') 1651 # help='XML definition file for the msg to use [default: stdin]') 1652 1653 # myparser.add_option('-m','--message',dest='message',default=None, 1654 # help='Which message to write code for [default: all]') 1655 1656 myparser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 1657 help='run the documentation tests') 1658 1659 myparser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true', 1660 help='put the field name in front of all function names.' 1661 +' Allows multiple messages in one file') 1662 1663 myparser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 1664 help='run the tests run in verbose mode') 1665 1666 (options,args) = myparser.parse_args() 1667 1668 success=True 1669 1670 if options.doctest: 1671 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 1672 argvOrig = sys.argv 1673 sys.argv= [sys.argv[0]] 1674 if options.verbose: sys.argv.append('-v') 1675 import doctest 1676 numfail,numtests=doctest.testmod() 1677 if numfail==0: print 'ok' 1678 else: 1679 print 'FAILED' 1680 success=False 1681 sys.argv = argvOrig # Restore the original args 1682 del argvOrig # hide from epydoc 1683 sys.exit() # FIX: Will this exit success? 1684 1685 #infile=sys.stdin 1686 #if options.xmlFileName: infile = file(options.xmlFileName,'r') 1687 1688 #outfile=sys.stdout 1689 #if options.outputFile: outfile = file(options.xmlDefinition,'w') 1690 1691 # FIX: down the road, it would be good to allow either filenames of std{in/out} 1692 1693 if None==options.xmlFileName: 1694 sys.exit('ERROR: must specify an xml definition file.') 1695 if None==options.outputFileName: 1696 sys.exit('ERROR: must specify an python file to write to.') 1697 generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix) 1698 1699 print '\nrecommend running pychecker like this:' 1700 print ' pychecker -q',options.outputFileName 1701