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