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

Source Code for Module ais.aisxmlbinmsg2py

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