Module aisxmlbinmsg2py
[hide private]
[frames] | no frames]

Source Code for Module 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  ''' 
  37   
  38  import sys, os 
  39  from decimal import Decimal 
  40  from lxml import etree  
  41   
42 -def hasSubtag(et,subtag):
43 ''' 44 @return: true if the tag a sub tag with name subtag 45 ''' 46 if 0<len(et.xpath(subtag)): return True 47 return False
48 49 50 #def writeBeginning(o,aisBinMsgET):
51 -def writeBeginning(o):
52 ''' 53 Write the doc string header for the message file 54 55 param o: Open output file to write code to. 56 param msgET: element tree for the ais message definition. 57 Must be pre-expanded with the expandais.py command. 58 ''' 59 import datetime 60 d = datetime.datetime.utcnow() 61 dateStr = str(d.year)+'-'+("%02d" %d.month)+'-'+("%02d"%d.day) 62 63 # FIX: what to do for __version__, @since, etc? 64 # Need to pass in info about the source file, etc. 65 66 # Minor trickery to get svn to ignore the keywords in the next few lines 67 o.write('''#!/usr/bin/env python 68 69 __version__ = '$Revision: 4791 $'.split()[1] 70 __date__ = '$Da'''+'''te: '''+dateStr+''' $'.split()[1] 71 __author__ = 'xmlbinmsg' 72 73 __doc__=\'\'\' 74 75 Autogenerated python functions to serialize/deserialize binary messages. 76 77 Generated by: '''+__file__+''' 78 79 Need to then wrap these functions with the outer AIS packet and then 80 convert the whole binary blob to a NMEA string. Those functions are 81 not currently provided in this file. 82 83 serialize: python to ais binary 84 deserialize: ais binary to python 85 86 The generated code uses translators.py, binary.py, and aisstring.py 87 which should be packaged with the resulting files. 88 89 ''') 90 91 o.write(''' 92 @requires: U{epydoc<http://epydoc.sourceforge.net/>} > 3.0alpha3 93 @requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>} 94 95 @author: \'\'\'+__author__+\'\'\' 96 @version: \'\'\' + __version__ +\'\'\' 97 @var __date__: Date of last svn commit 98 @undocumented: __version__ __author__ __doc__ myparser 99 @status: under development 100 @license: Generated code has no license 101 \'\'\' 102 103 import sys 104 from decimal import Decimal 105 from BitVector import BitVector 106 107 import binary, aisstring 108 109 TrueBV = BitVector(bitstring="1") 110 "Why always rebuild the True bit? This should speed things up a bunch" 111 FalseBV = BitVector(bitstring="0") 112 "Why always rebuild the False bit? This should speed things up a bunch" 113 114 115 ''') 116 return
117
118 -def generatePython(infile,outfile):
119 ''' 120 @param infile: xml ais binary message definition file 121 @param outfile: where to dump the python code 122 ''' 123 124 aisMsgsET = etree.parse(infile).getroot() 125 126 o = file(outfile,'w') 127 os.chmod(outfile,0755) 128 129 writeBeginning(o) 130 131 for msgET in aisMsgsET: 132 if msgET.tag != 'message': continue 133 print msgET.tag, msgET.attrib['name'] 134 135 buildEncode(o,msgET) 136 buildDecode(o,msgET) 137 138 print 'FIX: Building unittests... will not actually do anything' 139 o.write('\n\n######################################################################\n') 140 o.write('# UNIT TESTING\n') 141 o.write('######################################################################\n') 142 o.write('import unittest\n') 143 144 for msgET in aisMsgsET: 145 if msgET.tag != 'message': continue 146 print 'Building unit ttests for message ...', msgET.attrib['name'] 147 148 buildUnitTest(o,msgET) 149 150 buildMain(o) 151 return
152 153 154 155 ###################################################################### 156 # ENCODERS 157 ######################################################################
158 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
159 ''' 160 Build the encoder for boolean variables 161 @type o: file like obj 162 @param o: where write the code 163 @type name: str 164 @param name: field name 165 @type type: str 166 @param type: uint, bool, etc. 167 @type numbits: int = 1 168 @param numbits: How many bits per unit datum (must be 1 for bools) 169 @type required: bool or None 170 @param required: If not None, then the value must be set to this. 171 @type arraylen: int >= 1 172 @param arraylen: many bools will there be? FIX: handle variable 173 @type unavailable: bool or None 174 @param unavailable: the default value to use if none given (if not None) 175 @return: None 176 ''' 177 178 if verbose: print 'bool encode',name,': unvail=',unavailable 179 180 assert numbits==1 181 if arraylen != 1: assert False # FIX... handle arrays 182 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n') 183 if None != required: 184 assert type(required)==bool 185 if required: o.write('\t\tbvList.append(TrueBV)\n') 186 else: o.write('\t\tbvList.append(FalseBV)\n') 187 if verbose: o.write('\n') 188 return 189 190 if None==unavailable: 191 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n') 192 o.write('\telse: bvList.append(FalseBV)\n') 193 else: # Have a default value that can be filled in 194 assert type(unavailable)==bool 195 o.write("\tif '"+name+"' in params:\n") 196 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n') 197 o.write('\t\telse: bvList.append(FalseBV)\n') 198 o.write('\telse:\n') 199 if unavailable: o.write('\t\tbvList.append(TrueBV)\n') 200 else: o.write('\t\tbvList.append(FalseBV)\n') 201 if verbose: o.write('\n')
202
203 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
204 ''' 205 Build the encoder for unsigned integer variables 206 207 @type o: file like obj 208 @param o: where write the code 209 @type name: str 210 @param name: field name 211 @type type: str 212 @param type: uint, bool, etc. 213 @type numbits: int >= 1 214 @param numbits: How many bits per unit datum (must be 1..32) 215 @type required: bool or None 216 @param required: If not None, then the value must be set to this. 217 @type arraylen: int >= 1 218 @param arraylen: many unsigned ints will there be? FIX: handle variable 219 @type unavailable: bool or None 220 @param unavailable: the default value to use if none given (if not None) 221 @return: None 222 ''' 223 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 224 225 assert numbits>=1 and numbits<=32 226 if arraylen != 1: assert False # FIX... handle arrays 227 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 228 229 if None != required: 230 if verbose: print ' required:',required 231 required=int(required) 232 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n') 233 if verbose: o.write('\n') 234 return 235 236 if None==unavailable: 237 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n') 238 else: # Have a default value that can be filled in 239 #assert type(unavailable)== 240 int(unavailable) # Make sure unavailable is a number object 241 o.write("\tif '"+name+"' in params:\n") 242 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n') 243 o.write('\telse:\n') 244 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n') 245 246 if verbose: o.write('\n')
247 248
249 -def encodeFloat(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
250 ''' 251 Build the encoder for IEEE float variables 252 253 @type o: file like obj 254 @param o: where write the code 255 @type name: str 256 @param name: field name 257 @type type: str 258 @param type: uint, bool, etc. 259 @type numbits: int >= 1 260 @param numbits: How many bits per unit datum (must be 1..32) 261 @type required: bool or None 262 @param required: If not None, then the value must be set to this. 263 @type arraylen: int >= 1 264 @param arraylen: many unsigned ints will there be? FIX: handle variable 265 @type unavailable: bool or None 266 @param unavailable: the default value to use if none given (if not None) 267 @return: None 268 ''' 269 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 270 271 assert numbits==32 # Force by the IEEE spec 272 if arraylen != 1: assert False # FIX... handle arrays 273 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 274 275 if None != required: 276 if verbose: print ' required:',required 277 required=int(required) 278 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n') 279 if verbose: o.write('\n') 280 return 281 282 if None==unavailable: 283 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n') 284 else: # Have a default value that can be filled in 285 #assert type(unavailable)== 286 int(unavailable) # Make sure unavailable is a number object 287 o.write("\tif '"+name+"' in params:\n") 288 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n') 289 o.write('\telse:\n') 290 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n') 291 292 if verbose: o.write('\n')
293 294
295 -def encodeAisstr6(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
296 ''' 297 Build the encoder for aisstr6 variables. Generally are arrays. 298 @bug: do we need to optionally check for a valid string? 299 300 @type o: file like obj 301 @param o: where write the code 302 @type name: str 303 @param name: field name 304 @type type: str 305 @param type: uint, bool, etc. 306 @type numbits: int >= 1 307 @param numbits: How many bits per unit datum (must be 1..32) 308 @type required: bool or None 309 @param required: If not None, then the value must be set to this. 310 @type arraylen: int >= 1 311 @param arraylen: many unsigned ints will there be? FIX: handle variable 312 @type unavailable: bool or None 313 @param unavailable: the default value to use if none given (if not None) 314 @return: None 315 ''' 316 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 317 318 assert numbits==6 # Force by the IEEE spec 319 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 320 321 if None != required: 322 if verbose: print ' required:',required 323 required=int(required) 324 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+str(numbits*arraylen)+'))\n') 325 if verbose: o.write('\n') 326 return 327 328 if None==unavailable: 329 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+str(numbits*arraylen)+'))\n') 330 else: # Have a default value that can be filled in 331 o.write("\tif '"+name+"' in params:\n") 332 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+str(numbits*arraylen)+'))\n') 333 o.write('\telse:\n') 334 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+str(numbits*arraylen)+'))\n') 335 336 if verbose: o.write('\n')
337 338
339 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
340 ''' 341 Build the encoder for signed integer variables 342 343 @type o: file like obj 344 @param o: where write the code 345 @type name: str 346 @param name: field name 347 @type type: str 348 @param type: uint, bool, etc. 349 @type numbits: int >= 1 350 @param numbits: How many bits per unit datum (must be 1..32) 351 @type required: bool or None 352 @param required: If not None, then the value must be set to this. 353 @type arraylen: int >= 1 354 @param arraylen: many signed ints will there be? FIX: handle variable 355 @type unavailable: number or None 356 @param unavailable: the default value to use if none given (if not None) 357 @return: None 358 ''' 359 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 360 361 assert numbits>=1 and numbits<=32 362 if arraylen != 1: assert False # FIX... handle arrays 363 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 364 365 if None != required: 366 if verbose: print ' required:',required 367 required=int(required) 368 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n') 369 if verbose: o.write('\n') 370 return 371 372 373 if None==unavailable: 374 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n') 375 else: # Have a default value that can be filled in 376 #assert type(unavailable)== 377 int(unavailable) # Make sure unavailable is a number object 378 o.write("\tif '"+name+"' in params:\n") 379 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n') 380 o.write('\telse:\n') 381 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n') 382 383 if verbose: o.write('\n')
384 385 386 387 # FIX: Ummm... why am I passing the type? I guess it makes the one print statement easier
388 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
389 ''' 390 Build the encoder for signed decimal variables 391 392 @type o: file like obj 393 @param o: where write the code 394 @type name: str 395 @param name: field name 396 @type type: str 397 @param type: decimal 398 @type numbits: int >= 1 399 @param numbits: How many bits per unit datum (must be 1..32) 400 @type required: bool or None 401 @param required: If not None, then the value must be set to this. 402 @type arraylen: int >= 1 403 @param arraylen: many decimals will there be? FIX: handle variable 404 @type unavailable: Decimal or None 405 @param unavailable: the default value to use if none given (if not None) 406 @return: None 407 ''' 408 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable 409 410 assert numbits>=1 and numbits<=32 411 if arraylen != 1: assert False # FIX... handle arrays 412 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 413 414 # FIX: optimize to not emit the scaling when it is not needed... or tell the user to switch to an int! 415 if None == scale: 416 print 'WARNING: if you are not scaling, then you probably want to use an int instead!' 417 print 'Beware canadians bearing travel videos' 418 scale='1' 419 420 if None != required: 421 if verbose: print ' required:',required 422 required=int(required) 423 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n') 424 if verbose: o.write('\n') 425 return 426 427 # FIX: can I get rid of the Decimal around params? 428 if None==unavailable: 429 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 430 else: # Have a default value that can be filled in 431 o.write("\tif '"+name+"' in params:\n") 432 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n') 433 o.write('\telse:\n') 434 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n') 435 436 if verbose: o.write('\n')
437 438 439 ###################################################################### 440 # DECODERS 441 ###################################################################### 442
443 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 444 bv='bv',dataDict='r',verbose=False):
445 ''' 446 Build the decoder for boolean variables 447 448 @type o: file like obj 449 @param o: where write the code 450 @type name: str 451 @param name: field name 452 @type type: str 453 @param type: uint, bool, etc. 454 @type startindex: int 455 @param startindex: bit that begins the bool(s) 456 @type numbits: int = 1 457 @param numbits: How many bits per unit datum (must be 1 for bools) 458 @type required: bool or None 459 @param required: If not None, then the value must be set to this. 460 @type arraylen: int >= 1 461 @param arraylen: many bools will there be? FIX: handle variable 462 @type unavailable: bool or None 463 @param unavailable: the default value to use if none given (if not None) 464 @type bv: str 465 @param bv: BitVector containing the incoming data 466 @type dataDict: str 467 @param dataDict: dictionary in which to place the results 468 @rtype: int 469 @return: index one past the end of where this read 470 ''' 471 assert(type=='bool') 472 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 473 #int(startindex); int(numbits) # Make sure it is a number 474 assert numbits==1 475 assert arraylen == 1 # FIX... handle arrays 476 if verbose: o.write('\t### FIELD foo: '+name+' (type='+type+')\n') 477 478 if None != required: 479 assert type(required)==bool 480 if required: o.write('\t\t'+dataDict+'[\''+name+'\']=True\n') 481 else: o.write('\t\t'+dataDict+'[\''+name+'\']=False\n') 482 if verbose: o.write('\n') 483 return int(startindex)+int(numbits) 484 485 o.write('\t'+dataDict+'[\''+name+'\']=bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))\n') 486 if verbose: o.write('\n') 487 488 return int(startindex)+int(numbits)
489 490
491 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 492 bv='bv',dataDict='r',verbose=False):
493 ''' 494 Build the decoder for unsigned integer variables 495 496 @type o: file like obj 497 @param o: where write the code 498 @type name: str 499 @param name: field name 500 @type type: str 501 @param type: uint, etc. 502 @type startindex: int 503 @param startindex: bit that begins the uint(s) 504 @type numbits: int >= 1 505 @param numbits: How many bits per unit datum 506 @type required: int or None 507 @param required: If not None, then the value must be set to this. 508 @type arraylen: int >= 1 509 @param arraylen: many ints will there be? FIX: handle variable 510 @type unavailable: int or None 511 @param unavailable: the default value to use if none given (if not None) 512 @type bv: str 513 @param bv: BitVector containing the incoming data 514 @type dataDict: str 515 @param dataDict: dictionary in which to place the results 516 @rtype: int 517 @return: index one past the end of where this read 518 ''' 519 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 520 if None==arraylen: arraylen=1 521 assert arraylen == 1 # FIX... handle arrays 522 assert numbits>=1 523 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 524 525 if None != required: 526 int(required) # Make sure required is a number 527 o.write('\t'+dataDict+'[\''+name+'\']='+str(required)+'\n') 528 if verbose: o.write('\n') 529 return startindex+numbits 530 531 o.write('\t'+dataDict+'[\''+name+'\']=int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])\n') 532 if verbose: o.write('\n') 533 534 return startindex+numbits
535 536
537 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 538 bv='bv',dataDict='r',verbose=False):
539 ''' 540 Build the decoder for unsigned integer variables 541 542 @type o: file like obj 543 @param o: where write the code 544 @type name: str 545 @param name: field name 546 @type type: str 547 @param type: int 548 @type startindex: int 549 @param startindex: bit that begins the int(s) 550 @type numbits: int >= 1 551 @param numbits: How many bits per unit datum 552 @type required: int or None 553 @param required: If not None, then the value must be set to this. 554 @type arraylen: int >= 1 555 @param arraylen: many ints will there be? FIX: handle variable 556 @type unavailable: int or None 557 @param unavailable: the default value to use if none given (if not None) 558 @type bv: str 559 @param bv: BitVector containing the incoming data 560 @type dataDict: str 561 @param dataDict: dictionary in which to place the results 562 @rtype: int 563 @return: index one past the end of where this read 564 ''' 565 assert type=='int' 566 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 567 if None==arraylen: arraylen=1 568 end = startindex+int(numbits)*int(arraylen) 569 assert arraylen == 1 # FIX... handle arrays 570 assert numbits>=1 571 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 572 573 if None != required: 574 int(required) # Make sure required is a number 575 o.write('\t'+dataDict+'[\''+name+'\']='+str(required)+'\n') 576 if verbose: o.write('\n') 577 return end 578 579 o.write('\t'+dataDict+'[\''+name+'\']=binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])\n') 580 if verbose: o.write('\n') 581 582 return end
583
584 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 585 bv='bv',dataDict='r',verbose=False):
586 ''' 587 Build the decoder for IEEE float variables 588 589 @type o: file like obj 590 @param o: where write the code 591 @type name: str 592 @param name: field name 593 @type type: str 594 @param type: int 595 @type startindex: int 596 @param startindex: bit that begins the int(s) 597 @type numbits: int >= 1 598 @param numbits: How many bits per unit datum 599 @type required: float or None 600 @param required: If not None, then the value must be set to this. 601 @type arraylen: int >= 1 602 @param arraylen: many ints will there be? FIX: handle variable 603 @type unavailable: float or None 604 @param unavailable: the default value to use if none given (if not None) 605 @type bv: str 606 @param bv: BitVector containing the incoming data 607 @type dataDict: str 608 @param dataDict: dictionary in which to place the results 609 @rtype: int 610 @return: index one past the end of where this read 611 ''' 612 assert type=='float' 613 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 614 if None==arraylen: arraylen=1 615 end = startindex+int(numbits)*int(arraylen) 616 assert arraylen == 1 # FIX... handle arrays 617 assert numbits>=1 618 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 619 620 if None != required: 621 float(required) # Make sure required is a number 622 o.write('\t'+dataDict+'[\''+name+'\']='+str(required)+'\n') 623 if verbose: o.write('\n') 624 return end 625 626 o.write('\t'+dataDict+'[\''+name+'\']=binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])\n') 627 if verbose: o.write('\n') 628 629 return end
630 631
632 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 633 bv='bv',dataDict='r',verbose=False):
634 ''' 635 Build the decoder for aisstr6 variables. Generally arrays. 636 @bug: FIX: validate strings?? 637 @type o: file like obj 638 @param o: where write the code 639 @type name: str 640 @param name: field name 641 @type type: str 642 @param type: 'aisstr6' 643 @type startindex: int 644 @param startindex: bit that begins the int(s) 645 @type numbits: int >= 1 646 @param numbits: How many bits per unit datum 647 @type required: restricted str or None 648 @param required: If not None, then the value must be set to this. 649 @type arraylen: int >= 1 650 @param arraylen: many ints will there be? FIX: handle variable 651 @type unavailable: restricted str or None 652 @param unavailable: the default value to use if none given (if not None) 653 @type bv: str 654 @param bv: BitVector containing the incoming data 655 @type dataDict: str 656 @param dataDict: dictionary in which to place the results 657 @rtype: int 658 @return: index one past the end of where this read 659 ''' 660 assert type=='aisstr6' 661 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 662 if None==arraylen: arraylen=1 663 end = startindex+int(numbits)*int(arraylen) 664 assert arraylen >= 1 # FIX... handle arrays 665 assert numbits>=1 666 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 667 668 if None != required: 669 float(required) # Make sure required is a number 670 o.write('\t'+dataDict+'[\''+name+'\']='+required+'\n') 671 if verbose: o.write('\n') 672 return end 673 674 o.write('\t'+dataDict+'[\''+name+'\']=aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])\n') 675 if verbose: o.write('\n') 676 677 return end
678 679
680 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None, 681 bv='bv',dataDict='r',verbose=False,scale=None):
682 ''' 683 Build the decoder for signed decimal variables 684 685 @type o: file like obj 686 @param o: where write the code 687 @type name: str 688 @param name: field name 689 @type type: str 690 @param type: 'decimal' 691 @type startindex: int 692 @param startindex: bit that begins the int(s) 693 @type numbits: int >= 1 694 @param numbits: How many bits per unit datum 695 @type required: Decimal or None 696 @param required: If not None, then the value must be set to this. 697 @type arraylen: int >= 1 698 @param arraylen: many ints will there be? FIX: handle variable 699 @type unavailable: Decimal or None 700 @param unavailable: the default value to use if none given (if not None) 701 @type bv: str 702 @param bv: BitVector containing the incoming data 703 @type dataDict: str 704 @param dataDict: dictionary in which to place the results 705 @rtype: int 706 @return: index one past the end of where this read 707 ''' 708 assert type=='decimal' 709 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex 710 if None==arraylen: arraylen=1 711 end = startindex+int(numbits)*int(arraylen) 712 assert arraylen == 1 # FIX... handle arrays 713 assert numbits>=1 and numbits <= 32 714 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n') 715 716 if None == scale: scale='1' # Warning about this was in the encode section 717 718 if None != required: 719 Decimal(required) # Make sure required is a number 720 o.write('\t'+dataDict+'[\''+name+'\']='+str(Decimal(required))+'/Decimal(\''+scale+'\')\n') 721 if verbose: o.write('\n') 722 return end 723 724 o.write('\t'+dataDict+'[\''+name+'\']=Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')\n') 725 if verbose: o.write('\n') 726 727 return end
728 729 730 731 ###################################################################### 732 # THE REST 733 ###################################################################### 734 735 736
737 -def buildTestParamFunc(o,msgET, verbose=False):
738 '''Scrape the testvalues to make a basic param 739 740 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies? 741 ''' 742 name = msgET.attrib['name'] 743 744 o.write('def '+name+'TestParams():\n') 745 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") 746 o.write('\tparams = {}\n') 747 for field in msgET.xpath('field'): 748 name = field.attrib['name'] 749 type = field.attrib['type'] 750 if verbose: print 'buildTestParamFunc ...',name,type 751 val = None 752 if hasSubtag(field,'testvalue') and hasSubtag(field,'required'): 753 print 'ERROR: can not have both test value and required tags in the same field' 754 assert(False) 755 if hasSubtag(field,'testvalue'): 756 val = field.xpath('testvalue')[0].text 757 else: 758 if not hasSubtag(field,'required'): 759 sys.exit("ERROR: missing required or testvalue for field: "+name) 760 val = field.xpath('required')[0].text 761 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val 762 o.write('\tparams[\''+name+'\'] = ') 763 if type=='bool': 764 if val=='1' or val.lower=='true': val = 'True' 765 else: val = 'False' 766 o.write(val) 767 elif type in ('uint','int','float'): 768 o.write(val) 769 elif type in ('decimal','udecimal'): 770 o.write('Decimal(\''+val+'\')') 771 elif type in ('aisstr6'): 772 o.write('\''+val+'\'') 773 else: 774 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee' 775 assert(False) 776 777 o.write('\n') 778 779 780 o.write('\n\treturn params\n\n')
781
782 -def buildUnitTest(o,msgET, verbose=False):
783 ''' 784 Write the unittests for a message 785 786 param o: open file where resulting code will be written 787 param msgET: Element Tree starting at a message node 788 ''' 789 assert(msgET.tag=='message') 790 name = msgET.attrib['name'] 791 792 buildTestParamFunc(o,msgET) 793 794 o.write('class Test'+name+'(unittest.TestCase):\n') 795 o.write("\t'''Uses the testvalue tag text from each type to build a test case for the "+name+" message'''\n") 796 o.write('\tdef testEncodeDecode(self):\n') 797 # FIX: what is a good docstring to put here? 798 o.write('\t\n') 799 o.write('\t\tparams = '+name+'TestParams()\n') 800 801 #o.write('\t\tprint "params",params\n') 802 o.write('\t\tbits = '+name+'Encode(params)\n') 803 #o.write('\t\tprint "bits:",str(bits)\n') 804 o.write('\t\tr = '+name+'Decode(bits)\n') 805 #o.write('\t\tprint "decoded msg",r\n') 806 o.write('\n') 807 o.write('\t\t# Check that each parameter came through ok.\n') 808 809 810 for field in msgET.xpath('field'): 811 name = field.attrib['name'] 812 type = field.attrib['type'] 813 if type in ('bool','uint','int','aisstr6'): 814 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n') 815 else: 816 # float, decimal, udecimal 817 # FIX: look up the decimal places if decimal 818 places = '7' 819 if hasSubtag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text 820 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
821 822 823
824 -def buildEncode(o,msgET, verbose=False):
825 ''' 826 Write the encoder/decoder for a message 827 828 http://jaynes.colorado.edu/PythonIdioms.html 829 830 param o: open file where resulting code will be written 831 param msgET: Element Tree starting at a message node 832 ''' 833 assert(msgET.tag=='message') 834 name = msgET.attrib['name'] 835 836 print 'Generating encoder for',name # FIX: verbose? 837 funcName = name+'Encode' 838 o.write('def '+funcName+'(params, validate=False):\n') 839 840 ######################################## 841 # doc string 842 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg 8 (binary message).\n\n") 843 o.write('\tFields in params:\n') 844 for field in msgET.xpath('field'): 845 desc = field[0].text.replace('\n',' ') # get ride of new lines 846 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 847 if len(field.xpath("required")) == 1: 848 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 849 850 o.write('\n') 851 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n') 852 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 853 854 o.write("\t@rtype: BitVector\n") 855 o.write("\t@return: encoded binary message ready for wrapping in an AIS Msg 8\n") 856 o.write("\t'''\n\n") 857 858 ######################################## 859 # Actually build the code 860 861 o.write('\tbvList = []\n') 862 863 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 864 865 dynamicArrays = False # Set to true when positioning must be calculated 866 867 for field in msgET.xpath('field'): 868 name = field.attrib['name'] 869 type = field.attrib['type'] 870 numbits = int(field.attrib['numberofbits']) 871 required = None; 872 if hasSubtag(field,'required'): 873 required = field.xpath('required')[0].text 874 #print 'required set for',name,'to',required 875 unavailable=None; 876 if hasSubtag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 877 arraylen=1 878 if 'arraylength' in field.attrib: 879 arraylen=int(field.attrib['arraylength']) 880 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 881 else: 882 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 883 884 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable) 885 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable) 886 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable) 887 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable) 888 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable) 889 elif type=='decimal': 890 scale = None 891 if hasSubtag(field,'scale'): scale = field.xpath('scale')[0].text 892 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale) 893 else: print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type 894 895 o.write('\n\treturn binary.joinBV(bvList)\n\n')
896 897 898 ###################################################################### 899 # DECODER RING 900
901 -def buildDecode(o,msgET, verbose=False):
902 ''' 903 Write the decoder for a message 904 905 param o: open file where resulting code will be written 906 type msgET: elementtree 907 param msgET: Element Tree starting at a message node 908 return: None 909 ''' 910 assert(msgET.tag=='message') 911 name = msgET.attrib['name'] 912 913 print 'Generating decoder for',name 914 funcName = name+'Decode' 915 o.write('def '+funcName+'(bv, validate=False):\n') 916 917 ######################################## 918 # doc string 919 o.write("\t'''Unpack a "+name+" binary message bit payload from an AIS Msg 8 (binary message)\n\n") 920 o.write('\tFields in params:\n') 921 for field in msgET.xpath('field'): 922 desc = field[0].text.replace('\n',' ') # get ride of new lines 923 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc) # give the description 924 if len(field.xpath("required")) == 1: 925 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")') 926 927 o.write('\n') 928 o.write('\t@type bv: BitVector\n') 929 o.write('\t@param bv: Bits defining a message\n') 930 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n') 931 932 o.write("\t@rtype: dict\n") 933 o.write("\t@return: params\n") 934 o.write("\t'''\n\n") 935 936 ######################################## 937 # Actually build the code 938 939 o.write('\tr = {}\n') 940 941 if verbose: print 'number of fields = ', len(msgET.xpath('field')) 942 943 dynamicArrays = False # Set to true when positioning must be calculated 944 945 startindex = 0 # Where we are in the bitvector... FIX: what about variable length jobs? 946 947 for field in msgET.xpath('field'): 948 name = field.attrib['name'] 949 type = field.attrib['type'] 950 numbits = int(field.attrib['numberofbits']) 951 required = None; 952 if hasSubtag(field,'required'): 953 required = field.xpath('required')[0].text 954 print 'required set for',name,'to',required 955 unavailable=None; 956 if hasSubtag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text 957 arraylen=1 958 if 'arraylength' in field.attrib: 959 arraylen=int(field.attrib['arraylength']) 960 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )' 961 else: 962 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')' 963 964 assert None!=startindex 965 if verbose: print 'startindex',startindex 966 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable) 967 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 968 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable) 969 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable) 970 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable) 971 elif type=='decimal': 972 scale = None 973 if hasSubtag(field,'scale'): scale = field.xpath('scale')[0].text 974 #print 'pre call...' 975 #print ' required: "'+str(required)+'" scale: "'+str(scale)+'" unavail:', unavailable 976 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale) 977 else: print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type 978 979 if None==startindex: print 'FIX: here. drat. treat me right' 980 assert None!=startindex 981 982 983 o.write('\treturn r\n')
984 985 986 987
988 -def buildMain(o):
989 o.write(''' 990 ############################################################ 991 if __name__=='__main__': 992 993 from optparse import OptionParser 994 myparser = OptionParser(usage="%prog [options]", 995 version="%prog "+__version__) 996 997 #sys.exit('EARLY EXIT - FIX: remove') 998 myparser.add_option('--doc-test',dest='doctest',default=False,action='store_true', 999 help='run the documentation tests') 1000 myparser.add_option('--unit-test',dest='unittest',default=False,action='store_true', 1001 help='run the unit tests') 1002 myparser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true', 1003 help='Make the test output verbose') 1004 1005 (options,args) = myparser.parse_args() 1006 success=True 1007 1008 if options.doctest: 1009 import os; print os.path.basename(sys.argv[0]), 'doctests ...', 1010 sys.argv= [sys.argv[0]] 1011 if options.verbose: sys.argv.append('-v') 1012 import doctest 1013 numfail,numtests=doctest.testmod() 1014 if numfail==0: print 'ok' 1015 else: 1016 print 'FAILED' 1017 success=False 1018 1019 if not success: 1020 sys.exit('Something Failed') 1021 1022 del success # Hide success from epydoc 1023 1024 if options.unittest: 1025 sys.argv = [sys.argv[0]] 1026 if options.verbose: sys.argv.append('-v') 1027 unittest.main() 1028 ''')
1029 1030 ###################################################################### 1031 if __name__=='__main__': 1032 from optparse import OptionParser 1033 myparser = OptionParser(usage="%prog [options]", 1034 version="%prog "+__version__) 1035 1036 myparser.add_option('-o','--output',dest='outputFileName',default=None, 1037 help='Name of the python file to write') 1038 # help='Name of the python file to write [default: stdout]') 1039 1040 myparser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None, 1041 help='XML definition file for the msg to use') 1042 # help='XML definition file for the msg to use [default: stdin]') 1043 1044 # myparser.add_option('-m','--message',dest='message',default=None, 1045 # help='Which message to write code for [default: all]') 1046 1047 1048 (options,args) = myparser.parse_args() 1049 1050 #infile=sys.stdin 1051 #if options.xmlFileName: infile = file(options.xmlFileName,'r') 1052 1053 #outfile=sys.stdout 1054 #if options.outputFile: outfile = file(options.xmlDefinition,'w') 1055 1056 # FIX: down the road, it would be good to allow either filenames of std{in/out} 1057 1058 if None==options.xmlFileName: 1059 sys.exit('ERROR: must specify an xml definition file.') 1060 if None==options.outputFileName: 1061 sys.exit('ERROR: must specify an python file to write to.') 1062 generatePython(options.xmlFileName,options.outputFileName) 1063 1064 print '\nrecommend running pychecker like this:' 1065 print ' pychecker -q',options.outputFileName 1066