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