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