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