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