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