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 @todo: handle entry ranges in lookup tables
38 @todo: arraylength of -1 for messages 12 and 14
39 @bug: NOT complete
40 '''
41
42 import sys, os
43 from decimal import Decimal
44 from lxml import etree
45
47 '''
48 Try to suggest a type name if one did not work.
49
50 @param printout: if true, write a suggestion to stdout.
51
52 >>> suggestType('myFieldName','unsigned int')
53 Recommend switching "unsigned int" to "uint" for field "myFieldName"
54 'uint'
55
56 >>> suggestType('JohnWarfon','yoyodyne')
57 Sorry! No recommendation available for bad type "yoyodyne" for field "JohnWarfon"
58 '''
59 newType = None
60 if curType.lower()=='unsigned int':
61 newType = 'uint'
62 elif curType.lower()=='unsigned decimal':
63 newType = 'udecimal'
64
65 if printout:
66 if None != newType:
67 print 'Recommend switching "'+curType+'" to "'+newType+'" for field "'+name+'"'
68 else:
69 print 'Sorry! No recommendation available for bad type "'+curType+'" for field "'+name+'"'
70 return newType
71
72
74 '''
75 @return: true if the tag a sub tag with name subtag
76 '''
77 if 0<len(et.xpath(subtag)): return True
78 return False
79
80
82 '''
83 Write the doc string header for the message file
84
85 @param o: Open output file to write code to.
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 @todo: FIX: put in a description of the message here with fields and types.
131 \'\'\'
132
133 import sys
134 from decimal import Decimal
135 from BitVector import BitVector
136
137 import binary, aisstring
138
139 # FIX: check to see if these will be needed
140 TrueBV = BitVector(bitstring="1")
141 "Why always rebuild the True bit? This should speed things up a bunch"
142 FalseBV = BitVector(bitstring="0")
143 "Why always rebuild the False bit? This should speed things up a bunch"
144
145
146 ''')
147 return
148
150 '''
151 @param infile: xml ais binary message definition file
152 @param outfile: where to dump the python code
153 '''
154
155 aisMsgsET = etree.parse(infile).getroot()
156
157 o = file(outfile,'w')
158 os.chmod(outfile,0755)
159
160 writeBeginning(o)
161
162 for msgET in aisMsgsET:
163 if msgET.tag != 'message': continue
164 print msgET.tag, msgET.attrib['name']
165
166 if len(msgET.xpath('include-struct')) > 0:
167 sys.exit("ERROR: cannot handle xml that still has include-struct tags.\n Please use expandais.py.")
168 buildHelpers(o,msgET,prefixName=prefixName,verbose=verbose)
169 buildEncode(o,msgET,prefixName=prefixName,verbose=verbose)
170 buildDecode(o,msgET,prefixName=prefixName)
171 buildDecodeParts(o,msgET,prefixName=prefixName)
172 buildPrint(o,msgET,prefixName=prefixName)
173 buildLUT(o,msgET,prefixName=prefixName)
174 buildSQL(o,msgET,prefixName=prefixName)
175 buildLaTeX(o,msgET,prefixName=prefixName)
176
177 o.write('\n\n######################################################################\n')
178 o.write('# UNIT TESTING\n')
179 o.write('######################################################################\n')
180 o.write('import unittest\n')
181
182 for msgET in aisMsgsET:
183 if msgET.tag != 'message': continue
184 print 'Building unit tests for message ...', msgET.attrib['name']
185
186 buildUnitTest(o,msgET, prefixName=prefixName)
187
188 for msgET in aisMsgsET:
189 if msgET.tag != 'message': continue
190 buildMain(o,msgET, prefixName=prefixName)
191
192 return
193
194
195
196
197
199 '''
200 emit the fieldList and other things???
201
202 @param o: open file where resulting code will be written
203 @param msgET: Element Tree starting at a message node
204
205 @todo: for lookuptable/entry values, make it also print the decoded value.
206 @todo: use a different name for message and field
207 @todo: put in comments with the python and sql data type for each field
208 '''
209
210 if verbose:
211 msgname = msgET.attrib['name']
212 print 'Building helpers for',msgname
213
214 if prefixName:
215 o.write(prefixName,'FieldList = (\n')
216 else:
217 o.write('fieldList = (\n')
218
219 for field in msgET.xpath('field'):
220 name = field.attrib['name']
221 o.write('\t\''+name+'\',\n')
222
223 o.write(')\n\n')
224
225
226
227
228 if prefixName:
229 o.write(prefixName,'FieldListPostgres = (\n')
230 else:
231 o.write('fieldListPostgres = (\n')
232 finished=[]
233 for field in msgET.xpath('field'):
234 name = field.attrib['name']
235 if 'postgisName' in field.attrib:
236 name = field.attrib['postgisName']
237 if name in finished: continue
238 finished.append(name)
239 o.write('\t\''+name+'\',\t# PostGIS data type\n')
240 else:
241 o.write('\t\''+name+'\',\n')
242
243 o.write(')\n\n')
244
245 del finished
246
247
248
249 if prefixName:
250 o.write(prefixName,'ToPgFields = {\n')
251 else:
252 o.write('toPgFields = {\n')
253 for field in msgET.xpath('field'):
254 if 'postgisName' not in field.attrib: continue
255 name = field.attrib['name']
256 pgName = field.attrib['postgisName']
257 o.write('\t\''+name+'\':\''+pgName+'\',\n')
258 o.write('}\n')
259 o.write('''\'\'\'
260 Go to the Postgis field names from the straight field name
261 \'\'\'
262
263 ''')
264
265
266 if prefixName:
267 o.write(prefixName,'FromPgFields = {\n')
268 else:
269 o.write('fromPgFields = {\n')
270 finished=[]
271 for field in msgET.xpath('field'):
272 if 'postgisName' not in field.attrib: continue
273 name = field.attrib['name']
274 pgName = field.attrib['postgisName']
275
276 if pgName in finished: continue
277 finished.append(pgName)
278 o.write('\t\''+pgName+'\':(')
279 xp = 'field[@postgisName=\''+pgName+'\']'
280 parts = ['\''+part.attrib['name']+'\'' for part in msgET.xpath(xp)]
281 o.write(','.join(parts)+',),\n')
282 o.write('}\n')
283 o.write('''\'\'\'
284 Go from the Postgis field names to the straight field name
285 \'\'\'
286
287 ''')
288
289
290 if prefixName:
291 o.write(prefixName,'PgTypes = {\n')
292 else:
293 o.write('pgTypes = {\n')
294 finished=[]
295 for field in msgET.xpath('field'):
296 if 'postgisName' not in field.attrib: continue
297 pgType = field.attrib['postgisType']
298 pgName = field.attrib['postgisName']
299 if pgName in finished: continue
300 finished.append(pgName)
301 o.write('\t\''+pgName+'\':\''+pgType+'\',\n')
302 o.write('}\n')
303 o.write('''\'\'\'
304 Lookup table for each postgis field name to get its type.
305 \'\'\'
306
307 ''')
308
309
310
311
312
313
314
315
316
317
318
320 '''Get the maximum string length of any field name'''
321 maxStrLen=0
322 for field in msgET.xpath('field'):
323 fieldLen = len(field.attrib['name'])
324 if fieldLen>maxStrLen: maxStrLen = fieldLen
325 return maxStrLen
326
328 '''Pad a string out to the length requested with spaces out to the right'''
329 return aStr + ' '*(strlen-len(aStr))
330
332 '''Make sure this message has both long/x and lat/y fields.
333 @rtype: bool
334 '''
335
336
337 if 0==len(msgET.xpath('field[contains(@name, "longitude")]')): return False
338 if 0==len(msgET.xpath('field[contains(@name, "latitude")]')): return False
339 return True
340
342 '''
343 Dig up the first field name that include longitude and return it
344 @todo: might want to allow a search for a special tag to mark this
345 '''
346 return msgET.xpath('field[contains(@name, "longitude")]')[0].attrib['name']
347
349 '''
350 Dig up the first field name that include longitude and return it
351 @todo: might want to allow a search for a special tag to mark this
352 '''
353 return msgET.xpath('field[contains(@name, "latitude")]')[0].attrib['name']
354
355 -def buildPrint(o,msgET, verbose=False, prefixName=False):
356 '''
357 Write a simple in order print for the resulting dictionary.
358
359 @param o: open file where resulting code will be written
360 @param msgET: Element Tree starting at a message node
361
362 @todo: for lookuptable/entry values, make it also print the decoded value.
363 @todo: use a different name for message and field
364 '''
365 assert(msgET.tag=='message')
366 msgName = msgET.attrib['name']
367
368
369 print 'Generating print for',msgName
370
371 maxFieldLen = 1 + getMaxFieldNameLen(msgET)
372
373
374
375
376
377
378 printHtmlName = 'printHtml'
379 if prefixName: printHtmlName = name+'printHtml'
380
381
382
383
384
385
386
387
388 o.write('\n')
389 o.write('def '+printHtmlName+'(params, out=sys.stdout):\n')
390 o.write('\t\tout.write("<h3>'+msgName+'<h3>\\n")\n')
391 o.write('\t\tout.write("<table border=\\"1\\">\\n")\n')
392
393 o.write('\t\tout.write("<tr bgcolor=\\"orange\\">\\n")\n')
394 o.write('\t\tout.write("<th align=\\"left\\">Field Name</th>\\n")\n')
395 o.write('\t\tout.write("<th align=\\"left\\">Type</th>\\n")\n')
396 o.write('\t\tout.write("<th align=\\"left\\">Value</th>\\n")\n')
397 o.write('\t\tout.write("<th align=\\"left\\">Value in Lookup Table</th>\\n")\n')
398 o.write('\t\tout.write("<th align=\\"left\\">Units</th>\\n")\n')
399
400
401 for field in msgET.xpath('field'):
402 o.write('\t\tout.write("\\n")\n')
403 o.write('\t\tout.write("<tr>\\n")\n')
404 fieldname = field.attrib['name']
405 fieldtype = field.attrib['type']
406 o.write('\t\tout.write("<td>'+fieldname+'</td>\\n")\n')
407 o.write('\t\tout.write("<td>'+fieldtype+'</td>\\n")\n')
408
409 numbits = int(field.attrib['numberofbits'])
410 required = None;
411 if hasSubTag(field,'required'):
412 required = field.xpath('required')[0].text
413 unavailable=None;
414 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
415 arraylen=1
416 if 'arraylength' in field.attrib: arraylen=int(field.attrib['arraylength'])
417
418 if 1==arraylen or fieldtype=='aisstr6':
419 o.write('\t\tif \''+fieldname+'\' in params:\n\t\t\tout.write("\t<td>"+str(params[\''+fieldname+'\'])+"</td>\\n")\n')
420 if not hasSubTag(field,'lookuptable'):
421
422 o.write('\t\t\tout.write("\t<td>"+str(params[\''+fieldname+'\'])+"</td>\\n")\n')
423 else:
424 lutName = fieldname+'DecodeLut'
425 if prefixName: lutName = msgname+fieldname.capitalize()+'DecodeLut'
426
427 o.write('\t\t\tif str(params[\''+fieldname+'\']) in '+lutName+':\n')
428 o.write('\t\t\t\tout.write("<td>"+'+lutName+'[str(params[\''+fieldname+'\'])]+"</td>")\n')
429 o.write('\t\t\telse:\n')
430 o.write('\t\t\t\tout.write("<td><i>Missing LUT entry</i></td>")\n')
431 else: sys.exit ('FIX: handle arrays in the buildPrint func')
432
433 if hasSubTag(field,'units'):
434 o.write('\t\tout.write("<td>'+field.xpath('units')[0].text+'</td>\\n")\n')
435
436 o.write('\t\tout.write("</tr>\\n")\n')
437
438 o.write('\t\tout.write("</table>\\n")\n')
439 o.write('\n')
440
441
442
443
444
445 printKmlName = 'printKml'
446 if prefixName: printKmlName = msgName+'printKml'
447 if not haveLocatableMessage(msgET): printKmlName=None
448 else:
449 o.write('\n')
450 o.write('def printKml(params, out=sys.stdout):\n')
451 o.write('\t\'\'\'KML (Keyhole Markup Language) for Google Earth, but without the header/footer\'\'\'\n')
452 o.write('\tout.write("\\\t<Placemark>\\n")\n')
453 o.write('\tout.write("\\t\t<name>"+str(params[\''+msgET.attrib['titlefield']+'\'])+"</name>\\n")\n')
454 o.write('\tout.write("\\t\\t<description>\\n")\n')
455
456 o.write('\timport StringIO\n')
457 o.write('\tbuf = StringIO.StringIO()\n')
458 o.write('\tprintHtml(params,buf)\n')
459 o.write('\timport cgi\n')
460 o.write('\tout.write(cgi.escape(buf.getvalue()))\n')
461
462 o.write('\tout.write("\\t\\t</description>\\n")\n')
463 o.write('\tout.write("\\t\\t<styleUrl>#m_ylw-pushpin_copy0</styleUrl>\\n")\n')
464 o.write('\tout.write("\\t\\t<Point>\\n")\n')
465 o.write('\tout.write("\\t\\t\\t<coordinates>")\n')
466 o.write('\tout.write(str(params[\''+getLongitudeFieldName(msgET)+'\']))\n')
467 o.write('\tout.write(\',\')\n')
468 o.write('\tout.write(str(params[\''+getLatitudeFieldName(msgET)+'\']))\n')
469 o.write('\tout.write(",0</coordinates>\\n")\n')
470 o.write('\tout.write("\\t\\t</Point>\\n")\n')
471 o.write('\tout.write("\\t</Placemark>\\n")\n')
472 o.write('\n')
473
474
475
476
477
478
479 funcName = 'printFields'
480 if prefixName: funcName = msgName+'PrintFields'
481
482 o.write('def '+funcName+'(params, out=sys.stdout, format=\'std\', fieldList=None, dbType=\'postgres\'):\n')
483
484
485
486 o.write("\t'''Print a "+msgName+" message to stdout.\n\n")
487 o.write('\tFields in params:\n')
488 for field in msgET.xpath('field'):
489 desc = field[0].text.replace('\n',' ')
490 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)
491 if len(field.xpath("required")) == 1:
492 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")')
493
494 o.write('\n')
495 o.write('\t@param params: Dictionary of field names/values. \n')
496 o.write('\t@param out: File like object to write to\n')
497
498 o.write("\t@rtype: stdout\n")
499 o.write("\t@return: text to out\n")
500 o.write("\t'''\n\n")
501
502
503
504
505 o.write('\tif \'std\'==format:\n')
506 o.write('\t\tout.write("'+msgName+':\\n")\n')
507
508 if verbose: print 'number of fields = ', len(msgET.xpath('field'))
509
510
511 for field in msgET.xpath('field'):
512 fieldname = field.attrib['name']
513 fieldtype = field.attrib['type']
514 numbits = int(field.attrib['numberofbits'])
515 required = None;
516 if hasSubTag(field,'required'):
517 required = field.xpath('required')[0].text
518 unavailable=None;
519 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
520 arraylen=1
521 if 'arraylength' in field.attrib:
522 arraylen=int(field.attrib['arraylength'])
523 if verbose: print 'Processing field ...',fieldname,'('+fieldtype+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
524 else:
525 if verbose: print 'Processing field ...',fieldname,'(',fieldtype+' ',numbits,')'
526
527 if 1==arraylen or fieldtype=='aisstr6':
528 o.write('\t\tif \''+fieldname+'\' in params: out.write("\t'+padStrRight(fieldname+':',maxFieldLen)+' "+str(params[\''+fieldname+'\'])+"\\n")\n')
529
530 else:
531 print '\n','ERROR:',fieldname,fieldtype
532 sys.exit ('ERROR: FIX: handle arrays in the buildPrint func')
533
534
535
536
537 o.write(''' elif 'csv'==format:
538 if None == options.fieldList:
539 options.fieldList = fieldList
540 needComma = False;
541 for field in fieldList:
542 if needComma: out.write(',')
543 needComma = True
544 if field in params:
545 out.write(str(params[field]))
546 # else: leave it empty
547 out.write("\\n")
548 ''')
549
550
551
552
553 o.write('\telif \'html\'==format:\n')
554 o.write('\t\t'+printHtmlName+'(params,out)\n')
555
556 o.write(''' elif 'sql'==format:
557 sqlInsertStr(params,out,dbType=dbType)
558 ''')
559
560 if haveLocatableMessage(msgET):
561
562 o.write('\telif \'kml\'==format:\n')
563 o.write('\t\t'+printKmlName+'(params,out)\n')
564
565 o.write('\telif \'kml-full\'==format:\n')
566
567 o.write('\t\tout.write(\"<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>\\n")\n')
568 o.write('\t\tout.write("<kml xmlns=\\"http://earth.google.com/kml/2.1\\">\\n")\n')
569 o.write('\t\tout.write("<Document>\\n")\n')
570
571 o.write('\t\tout.write("\t<name>'+msgName+'</name>\\n")\n')
572
573 o.write('\t\t'+printKmlName+'(params,out)\n')
574
575 o.write('\t\tout.write("</Document>\\n")\n')
576 o.write('\t\tout.write("</kml>\\n")\n')
577
578
579
580
581
582
583 o.write('\telse: \n')
584 o.write('\t\tprint "ERROR: unknown format:",format\n')
585 o.write('\t\tassert False\n')
586
587 o.write('\n\treturn # Nothing to return\n\n')
588
589
590
591
592
593
594
595 -def buildLaTeX(o,msgET, verbose=False, prefixName=False):
596 '''
597 Write functions for LaTeX output
598
599 @param o: open file where resulting code will be written
600 @param msgET: Element Tree starting at a message node
601 @param verbose: talk lots in the process
602 @param prefixName: set to a string to have the commands prefixed by that character.
603
604 @todo: should this be a style sheet thing instead?
605 '''
606 assert(msgET.tag=='message')
607 msgName = msgET.attrib['name']
608 print 'Generating SQL commands for', msgName
609
610 createFuncName = 'latexDefinitionTable'
611 if prefixName: createFuncName = msgName+'LaTeXDefinitionTable'
612
613 o.write('''\n######################################################################
614 # LATEX SUPPORT
615 ######################################################################
616
617 ''')
618
619
620 o.write('def '+createFuncName+'''(outfile=sys.stdout
621 ):
622 \'\'\'
623 Return the LaTeX definition table for this message type
624 @param outfile: file like object to print to.
625 @type outfile: file obj
626 @return: LaTeX table string via the outfile
627 @rtype: str
628
629 \'\'\'
630 o = outfile
631 ''')
632
633
634 o.write('''
635 o.write(\'\'\'
636 /begin{table}%[htb]
637 /centering
638 /begin{tabular}{|l|c|l|}
639 /hline
640 Parameter & Number of bits & Description
641 // /hline/hline
642 '''.replace('/','\\\\'))
643
644 lines = []
645 totalBits = 0
646 for field in msgET.xpath('field'):
647 fieldName = field.attrib['name'].replace('_','\_')
648 fieldType = field.attrib['type']
649 fieldBits = field.attrib['numberofbits']
650 if 'arraylength' in field.attrib:
651 fieldBits = str(int(fieldBits) * int(field.attrib['arraylength']))
652 totalBits += int(fieldBits)
653
654 fieldDescription = field.xpath('description')[0].text.replace('\n',' ')
655 lines.append(fieldName+' & '+fieldBits+' & '+fieldDescription)
656
657 o.write(' \\\\\\\\ \\hline \n'.join(lines))
658
659 from math import ceil
660 numSlots = 1 + int( ceil((totalBits-168)/256.) )
661 emptySlotBits = ( 168 + (numSlots-1)*256 ) - totalBits
662 s = ''
663 if numSlots>1: s = 's'
664 o.write(('// /hline /hline\nTotal bits & '+str(totalBits)+' & Appears to take '+str(numSlots)+' slot'+s).replace('/','\\\\'))
665 if emptySlotBits > 0:
666 o.write(' with '+str(emptySlotBits)+' pad bits to fill the last slot')
667
668 o.write( (''' // /hline
669 /end{tabular}
670 /caption{AIS message number '''+msgET.attrib['aismsgnum']+': '+msgET.xpath('description')[0].text.replace('\n',' ')+'''}
671 /label{tab:'''+msgET.attrib['name']+'''}
672 /end{table}
673 \'\'\')
674 ''').replace('/','\\\\'))
675
676
677
678 -def buildSQL(o,msgET, verbose=False, prefixName=False):
679 '''
680 Write SQL code
681
682 @param o: open file where resulting code will be written
683 @param msgET: Element Tree starting at a message node
684 @param verbose: talk lots in the process
685 @param prefixName: set to a string to have the commands prefixed by that character.
686 '''
687 assert(msgET.tag=='message')
688 msgName = msgET.attrib['name']
689 print 'Generating SQL commands for', msgName
690
691 createFuncName = 'sqlCreate'
692 if prefixName: createFuncName = msgName+'SqlCreate'
693 insertFuncName = 'sqlInsert'
694 if prefixName: insertFuncName = msgName+'SqlInsert'
695
696 o.write('''######################################################################
697 # SQL SUPPORT
698 ######################################################################
699
700 ''')
701
702
703 o.write('def '+createFuncName+'''Str(outfile=sys.stdout, fields=None, extraFields=None
704 ,addCoastGuardFields=True
705 ,dbType='postgres'
706 ):
707 \'\'\'
708 Return the SQL CREATE command for this message type
709 @param outfile: file like object to print to.
710 @param fields: which fields to put in the create. Defaults to all.
711 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
712 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
713 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
714 @type addCoastGuardFields: bool
715 @return: sql create string
716 @rtype: str
717
718 @see: sqlCreate
719 \'\'\'
720 # FIX: should this sqlCreate be the same as in LaTeX (createFuncName) rather than hard coded?
721 outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType)))
722
723 ''')
724
725 o.write('def '+createFuncName+'''(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'):
726 \'\'\'
727 Return the sqlhelp object to create the table.
728
729 @param fields: which fields to put in the create. Defaults to all.
730 @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
731 @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
732 @type addCoastGuardFields: bool
733 @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
734 @return: An object that can be used to generate a return
735 @rtype: sqlhelp.create
736 \'\'\'
737 if None == fields: fields = fieldList
738 import sqlhelp
739 ''')
740
741 o.write('\tc = sqlhelp.create(\''+msgName+'\',dbType=dbType)\n')
742 o.write('\tc.addPrimaryKey()\n');
743
744 for field in msgET.xpath('field'):
745 fieldName = field.attrib['name']
746 fieldType = field.attrib['type']
747
748 postgis = False
749 if 'postgisType' in field.attrib:
750 postgis=True
751 o.write('\tif dbType != \'postgres\':\n\t')
752 o.write('\tif \''+fieldName+'\' in fields: c.add')
753
754 if fieldType in ('int','uint'): o.write('Int (\''+fieldName+'\')')
755 elif fieldType in ('float' ): o.write('Real(\''+fieldName+'\')')
756 elif fieldType == 'bool': o.write('Bool(\''+fieldName+'\')')
757 elif fieldType in ('decimal','udecimal'):
758 if not hasSubTag(field,'decimalplaces'):
759 print '\n ** ERROR: missing decimalplaces field for ',fieldName,'\n'
760 assert (False)
761 scaleSQL = int(field.xpath('decimalplaces')[0].text)
762 numBits = int(field.attrib['numberofbits'])
763 if not hasSubTag(field,'scale'):
764 print 'ERROR: missing required <scale> for',fieldName,'of type',fieldType
765 scaleVal = float(field.xpath('scale')[0].text)
766 precision = scaleSQL + len(str(int((2**numBits)/scaleVal)))
767
768 o.write('Decimal(\''+fieldName+'\','+str(precision)+','+str(scaleSQL)+')')
769 elif fieldType == 'binary':
770 numBits = int(field.attrib['numberofbits'])
771 if -1 == numBits: numBits=1024
772 o.write('BitVarying(\''+fieldName+'\','+str(numBits)+')')
773 elif fieldType == 'aisstr6':
774 arrayLength = int(field.attrib['arraylength'])
775 o.write('VarChar(\''+fieldName+'\','+str(arrayLength)+')')
776 else:
777 print '\n\n *** Unknown type in SQL code generation for field',fieldName+':',fieldType,'\n\n'
778 assert(False)
779 o.write('\n')
780
781 o.write('''
782 if addCoastGuardFields:
783 # c.addInt('cg_rssi') # Relative signal strength indicator
784 # c.addInt('cg_d') # dBm receive strength
785 # c.addInt('cg_T') # Receive timestamp from the AIS equipment
786 # c.addInt('cg_S') # Slot received in
787 # c.addVarChar('cg_x',10) # Idonno
788 c.addVarChar('cg_r',15) # Receiver station ID - should usually be an MMSI, but sometimes is a string
789 c.addInt('cg_sec') # UTC seconds since the epoch
790
791 c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream
792 ''')
793
794
795
796
797
798 postgisFields = msgET.xpath('field[@postgisType]')
799 if len(postgisFields)<1:
800 if verbose: print 'No postgis fields'
801 else:
802 o.write('\n\tif dbType == \'postgres\':\n')
803 finished=[]
804 print 'processing postgis fields for create:'
805 for field in postgisFields:
806
807 pgName = field.attrib['postgisName']
808 if pgName in finished:
809 print 'already handled',pgName
810 continue
811 finished.append(pgName)
812 pgType = field.attrib['postgisType']
813
814 components = msgET.xpath('field[@postgisName=\''+pgName+'\']')
815
816
817
818 o.write('\t\tc.addPostGIS(\''+pgName+'\',\''+pgType+'\','+str(len(components))+');\n')
819
820 o.write('\n\treturn c\n\n')
821
822
823
824 o.write('''def '''+insertFuncName+'''Str(params, outfile=sys.stdout, extraParams=None, dbType='postgres'):
825 \'\'\'
826 Return the SQL INSERT command for this message type
827 @param params: dictionary of values keyed by field name
828 @param outfile: file like object to print to.
829 @param extraParams: A sequence of tuples containing (name,sql type) for additional fields
830 @return: sql create string
831 @rtype: str
832
833 @see: sqlCreate
834 \'\'\'
835 outfile.write(str(sqlInsert(params,extraParams,dbType=dbType)))
836
837
838 ''')
839
840
841 o.write('''def '''+insertFuncName+'''(params,extraParams=None,dbType='postgres'):
842 \'\'\'
843 Give the SQL INSERT statement
844 @param params: dict keyed by field name of values
845 @param extraParams: any extra fields that you have created beyond the normal ais message fields
846 @rtype: sqlhelp.insert
847 @return: insert class instance
848 @todo: allow optional type checking of params?
849 @warning: this will take invalid keys happily and do what???
850 \'\'\'
851 import sqlhelp
852 i = sqlhelp.insert(\'''' + msgName + '''\',dbType=dbType)
853
854 if dbType=='postgres':
855 finished = []
856 for key in params:
857 if key in finished:
858 continue
859
860 if key not in toPgFields and key not in fromPgFields:
861 if type(params[key])==Decimal: i.add(key,float(params[key]))
862 else: i.add(key,params[key])
863 else:
864 if key in fromPgFields:
865 val = params[key]
866 # Had better be a WKT type like POINT(-88.1 30.321)
867 i.addPostGIS(key,val)
868 finished.append(key)
869 else:
870 # Need to construct the type.
871 pgName = toPgFields[key]
872 #valStr='GeomFromText(\\''+pgTypes[pgName]+'('
873 valStr=pgTypes[pgName]+'('
874 vals = []
875 for nonPgKey in fromPgFields[pgName]:
876 vals.append(str(params[nonPgKey]))
877 finished.append(nonPgKey)
878 valStr+=' '.join(vals)+')'
879 i.addPostGIS(pgName,valStr)
880 else:
881 for key in params:
882 if type(params[key])==Decimal: i.add(key,float(params[key]))
883 else: i.add(key,params[key])
884
885 if None != extraParams:
886 for key in extraParams:
887 i.add(key,extraParams[key])
888
889 return i
890 ''')
891
892
893
894
895
896
897
898
899 -def buildLUT(o,msgET, verbose=False, prefixName=False):
900 '''
901 Write lookup tables for enumerated types (uint or int, maybe bool too).
902
903 @todo: FIX: what to do about multiple entries with the same text? Need to ban that kind of thing
904 @todo: Make doc strings for each LUT.
905
906 @param o: open file where resulting code will be written
907 @param msgET: Element Tree starting at a message node
908 '''
909 assert(msgET.tag=='message')
910 msgname = msgET.attrib['name']
911
912 print 'Generating lookup tables for',msgname
913
914 for field in msgET.xpath('field'):
915 name = field.attrib['name']
916 if not hasSubTag(field,'lookuptable'): continue
917 lut = field.xpath('lookuptable')[0]
918
919 lutName = name
920 if prefixName: lutName = msgname+name.capitalize()
921
922 o.write(lutName+'EncodeLut = {\n')
923 for entry in lut.xpath('entry'):
924 o.write('\t\''+entry.text+'\':\''+entry.attrib['key']+'\',\n')
925 o.write('\t} #'+lutName+'EncodeLut\n')
926 o.write('\n')
927
928
929
930 o.write(lutName+'DecodeLut = {\n')
931 for entry in lut.xpath('entry'):
932 o.write('\t\''+entry.attrib['key']+'\':\''+entry.text+'\',\n')
933 o.write('\t} # '+lutName+'EncodeLut\n')
934 o.write('\n')
935
936
937
938
939
940
941
942
943
944 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
945 '''
946 Build the encoder for boolean variables
947 @type o: file like obj
948 @param o: where write the code
949 @type name: str
950 @param name: field name
951 @type type: str
952 @param type: bool, etc.
953 @type numbits: int = 1
954 @param numbits: How many bits per unit datum (must be 1 for bools)
955 @type required: bool or None
956 @param required: If not None, then the value must be set to this.
957 @type arraylen: int >= 1
958 @param arraylen: many bools will there be? FIX: handle variable
959 @type unavailable: bool or None
960 @param unavailable: the default value to use if none given (if not None)
961 @return: None
962 '''
963
964 if verbose: print 'bool encode',name,': unvail=',unavailable
965
966 assert type.lower()=='bool'
967 assert numbits==1
968 if arraylen != 1: assert False
969 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n')
970 if None != required:
971 assert type(required)==bool
972 if required: o.write('\t\tbvList.append(TrueBV)\n')
973 else: o.write('\t\tbvList.append(FalseBV)\n')
974 if verbose: o.write('\n')
975 return
976
977 if None==unavailable:
978 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n')
979 o.write('\telse: bvList.append(FalseBV)\n')
980 else:
981 assert type(unavailable)==bool
982 o.write("\tif '"+name+"' in params:\n")
983 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n')
984 o.write('\t\telse: bvList.append(FalseBV)\n')
985 o.write('\telse:\n')
986 if unavailable: o.write('\t\tbvList.append(TrueBV)\n')
987 else: o.write('\t\tbvList.append(FalseBV)\n')
988 if verbose: o.write('\n')
989
990 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
991 '''
992 Build the encoder for unsigned integer variables
993
994 @type o: file like obj
995 @param o: where write the code
996 @type name: str
997 @param name: field name
998 @type type: str
999 @param type: uint, bool, etc.
1000 @type numbits: int >= 1
1001 @param numbits: How many bits per unit datum (must be 1..32)
1002 @type required: bool or None
1003 @param required: If not None, then the value must be set to this.
1004 @type arraylen: int >= 1
1005 @param arraylen: many unsigned ints will there be? FIX: handle variable
1006 @type unavailable: bool or None
1007 @param unavailable: the default value to use if none given (if not None)
1008 @return: None
1009 '''
1010 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1011
1012 assert type=='uint'
1013 assert numbits>=1 and numbits<=32
1014 if arraylen != 1: assert False
1015 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1016
1017 if None != required:
1018 if verbose: print ' required:',required
1019 required=int(required)
1020 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n')
1021 if verbose: o.write('\n')
1022 return
1023
1024 if None==unavailable:
1025 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n')
1026 else:
1027
1028 int(unavailable)
1029 o.write("\tif '"+name+"' in params:\n")
1030 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n')
1031 o.write('\telse:\n')
1032 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n')
1033
1034 if verbose: o.write('\n')
1035
1036
1037 -def encodeFloat(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1038 '''
1039 Build the encoder for IEEE float variables
1040
1041 @type o: file like obj
1042 @param o: where write the code
1043 @type name: str
1044 @param name: field name
1045 @type fieldType: str
1046 @param fieldType: uint, bool, etc.
1047 @type numbits: int >= 1
1048 @param numbits: How many bits per unit datum (must be 1..32)
1049 @type required: bool or None
1050 @param required: If not None, then the value must be set to this.
1051 @type arraylen: int >= 1
1052 @param arraylen: many unsigned ints will there be? FIX: handle variable
1053 @type unavailable: bool or None
1054 @param unavailable: the default value to use if none given (if not None)
1055 @return: None
1056 '''
1057 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable
1058
1059 assert numbits==32
1060 if arraylen != 1: assert False
1061 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n')
1062
1063 if None != required:
1064 if verbose: print ' required:',required
1065 required=int(required)
1066 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n')
1067 if verbose: o.write('\n')
1068 return
1069
1070 if None==unavailable:
1071 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n')
1072 else:
1073 int(unavailable)
1074 o.write("\tif '"+name+"' in params:\n")
1075 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n')
1076 o.write('\telse:\n')
1077 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n')
1078
1079 if verbose: o.write('\n')
1080
1081
1082 -def encodeAisstr6(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1083 '''
1084 Build the encoder for aisstr6 variables. Generally are arrays.
1085 @bug: do we need to optionally check for a valid string?
1086
1087 @type o: file like obj
1088 @param o: where write the code
1089 @type name: str
1090 @param name: field name
1091 @type fieldType: str
1092 @param fieldType: uint, bool, etc.
1093 @type numbits: int >= 1
1094 @param numbits: How many bits per unit datum (must be 1..32)
1095 @type required: bool or None
1096 @param required: If not None, then the value must be set to this.
1097 @type arraylen: int >= 1
1098 @param arraylen: many unsigned ints will there be? FIX: handle variable
1099 @type unavailable: bool or None
1100 @param unavailable: the default value to use if none given (if not None)
1101 @return: None
1102 '''
1103 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable
1104 totLen = str(numbits*arraylen)
1105 assert numbits==6
1106 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n')
1107
1108 if None != required:
1109 if verbose: print ' required:',required
1110 required=int(required)
1111 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+totLen+'))\n')
1112 if verbose: o.write('\n')
1113 return
1114
1115 if None==unavailable:
1116 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n')
1117 else:
1118 o.write("\tif '"+name+"' in params:\n")
1119 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n')
1120 o.write('\telse:\n')
1121 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+totLen+'))\n')
1122
1123 if verbose: o.write('\n')
1124
1125
1126 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1127 '''
1128 Build the encoder for signed integer variables
1129
1130 @type o: file like obj
1131 @param o: where write the code
1132 @type name: str
1133 @param name: field name
1134 @type type: str
1135 @param type: uint, bool, etc.
1136 @type numbits: int >= 1
1137 @param numbits: How many bits per unit datum (must be 1..32)
1138 @type required: bool or None
1139 @param required: If not None, then the value must be set to this.
1140 @type arraylen: int >= 1
1141 @param arraylen: many signed ints will there be? FIX: handle variable
1142 @type unavailable: number or None
1143 @param unavailable: the default value to use if none given (if not None)
1144 @return: None
1145 '''
1146 if verbose: print ' encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1147
1148 assert numbits>=1 and numbits<=32
1149 if arraylen != 1: assert False
1150 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1151
1152 if None != required:
1153 if verbose: print ' required:',required
1154 required=int(required)
1155 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n')
1156 if verbose: o.write('\n')
1157 return
1158
1159
1160 if None==unavailable:
1161 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n')
1162 else:
1163
1164 int(unavailable)
1165 o.write("\tif '"+name+"' in params:\n")
1166 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n')
1167 o.write('\telse:\n')
1168 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n')
1169
1170 if verbose: o.write('\n')
1171
1172
1173
1174
1175 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1176 '''
1177 Build the encoder for signed decimal variables
1178
1179 @type o: file like obj
1180 @param o: where write the code
1181 @type name: str
1182 @param name: field name
1183 @type type: str
1184 @param type: decimal
1185 @type numbits: int >= 1
1186 @param numbits: How many bits per unit datum (must be 1..32)
1187 @type required: bool or None
1188 @param required: If not None, then the value must be set to this.
1189 @type arraylen: int >= 1
1190 @param arraylen: many decimals will there be? FIX: handle variable
1191 @type unavailable: Decimal or None
1192 @param unavailable: the default value to use if none given (if not None)
1193 @return: None
1194 '''
1195 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1196
1197 assert numbits>=1 and numbits<=32
1198 if arraylen != 1: assert False
1199 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1200
1201
1202 if None == scale:
1203 print 'WARNING: if you are not scaling, then you probably want to use an int instead!'
1204 print 'Beware canadians bearing travel videos'
1205 scale='1'
1206
1207 if None != required:
1208 if verbose: print ' required:',required
1209 required=int(required)
1210 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n')
1211 if verbose: o.write('\n')
1212 return
1213
1214
1215 if None==unavailable:
1216 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n')
1217 else:
1218 o.write("\tif '"+name+"' in params:\n")
1219 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n')
1220 o.write('\telse:\n')
1221 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n')
1222
1223 if verbose: o.write('\n')
1224
1225
1226
1227 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1228 '''
1229 Build the encoder for signed decimal variables
1230
1231 @type o: file like obj
1232 @param o: where write the code
1233 @type name: str
1234 @param name: field name
1235 @type type: str
1236 @param type: decimal
1237 @type numbits: int >= 1
1238 @param numbits: How many bits per unit datum (must be 1..32)
1239 @type required: bool or None
1240 @param required: If not None, then the value must be set to this.
1241 @type arraylen: int >= 1
1242 @param arraylen: many decimals will there be? FIX: handle variable
1243 @type unavailable: Decimal or None
1244 @param unavailable: the default value to use if none given (if not None)
1245 @return: None
1246 '''
1247 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1248 assert type=='udecimal'
1249 assert numbits>=1 and numbits<=32
1250 if arraylen != 1: assert False
1251 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1252
1253
1254 if None == scale:
1255 print 'WARNING: if you are not scaling, then you probably want to use an int instead!'
1256 print 'Beware canadians bearing travel videos'
1257 scale='1'
1258
1259 if None != required:
1260 if verbose: print ' required:',required
1261 required=int(required)
1262 assert(0<=required)
1263 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n')
1264 if verbose: o.write('\n')
1265 return
1266
1267
1268 if None==unavailable:
1269 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n')
1270 else:
1271 o.write("\tif '"+name+"' in params:\n")
1272 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n')
1273 o.write('\telse:\n')
1274 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n')
1275
1276 if verbose: o.write('\n')
1277
1278
1279
1280 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1281 '''
1282 Build the encoder for binary variables. This is just a pass through.
1283 This is used for the ais binary message wrapper (e.g. msg 8). Do
1284 not use it within binary messages.
1285
1286 @type o: file like obj
1287 @param o: where write the code
1288 @type name: str
1289 @param name: field name
1290 @type type: str
1291 @param type: binary
1292 @type numbits: int >= 1
1293 @param numbits: How many bits per unit datum (must be 1..1024 or so)
1294 @type required: bool or None
1295 @param required: If not None, then the value must be set to this.
1296 @type arraylen: int >= 1
1297 @param arraylen: many decimals will there be? FIX: handle variable
1298 @type unavailable: Decimal or None
1299 @param unavailable: the default value to use if none given (if not None)
1300 @return: None
1301 '''
1302 if verbose: print ' encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable
1303 assert type=='binary'
1304 assert (numbits>=1 and numbits<=1024) or numbits==-1
1305 assert (None == required)
1306 assert (None == unavailable)
1307
1308 if arraylen != 1: assert False
1309 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1310
1311
1312 o.write('\tbvList.append(params[\''+name+'\'])\n')
1313
1314 if verbose: o.write('\n')
1315
1316
1317
1318
1319
1320
1321 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1322 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1323 '''
1324 Build the decoder for boolean variables
1325
1326 @type o: file like obj
1327 @param o: where write the code
1328 @type name: str
1329 @param name: field name
1330 @type type: str
1331 @param type: uint, bool, etc.
1332 @type startindex: int
1333 @param startindex: bit that begins the bool(s)
1334 @type numbits: int = 1
1335 @param numbits: How many bits per unit datum (must be 1 for bools)
1336 @type required: bool or None
1337 @param required: If not None, then the value must be set to this.
1338 @type arraylen: int >= 1
1339 @param arraylen: many bools will there be? FIX: handle variable
1340 @type unavailable: bool or None
1341 @param unavailable: the default value to use if none given (if not None)
1342 @type bv: str
1343 @param bv: BitVector containing the incoming data
1344 @type dataDict: str
1345 @param dataDict: dictionary in which to place the results
1346 @type decodeOnly: bool
1347 @param decodeOnly: Set to true to only get the code for decoding
1348 @rtype: int
1349 @return: index one past the end of where this read
1350 '''
1351 assert(type=='bool')
1352 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1353
1354 assert numbits==1
1355 assert arraylen == 1
1356
1357 if None != required:
1358 assert type(required)==bool
1359 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1360 if required: o.write('True\n')
1361 else: o.write('False\n')
1362 if not decodeOnly: o.write('\n')
1363 return int(startindex)+int(numbits)
1364
1365 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1366 o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))')
1367 if not decodeOnly: o.write('\n')
1368
1369 return int(startindex)+int(numbits)
1370
1371
1372 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1373 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1374 '''
1375 Build the decoder for unsigned integer variables
1376
1377 @type o: file like obj
1378 @param o: where write the code
1379 @type name: str
1380 @param name: field name
1381 @type type: str
1382 @param type: uint, etc.
1383 @type startindex: int
1384 @param startindex: bit that begins the uint(s)
1385 @type numbits: int >= 1
1386 @param numbits: How many bits per unit datum
1387 @type required: int or None
1388 @param required: If not None, then the value must be set to this.
1389 @type arraylen: int >= 1
1390 @param arraylen: many ints will there be? FIX: handle variable
1391 @type unavailable: int or None
1392 @param unavailable: the default value to use if none given (if not None)
1393 @type bv: str
1394 @param bv: BitVector containing the incoming data
1395 @type dataDict: str
1396 @param dataDict: dictionary in which to place the results
1397 @type decodeOnly: bool
1398 @param decodeOnly: Set to true to only get the code for decoding
1399 @rtype: int
1400 @return: index one past the end of where this read
1401 '''
1402 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1403 if None==arraylen: arraylen=1
1404 assert arraylen == 1
1405 assert numbits>=1
1406 if not decodeOnly: verbose=False
1407
1408 if None != required:
1409 int(required)
1410 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1411 o.write(str(required))
1412 if not decodeOnly: o.write('\n')
1413 return startindex+numbits
1414
1415 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1416 o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])')
1417 if not decodeOnly: o.write('\n')
1418 if verbose: o.write('\n')
1419
1420 return startindex+numbits
1421
1422
1423 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1424 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1425 '''
1426 Build the decoder for unsigned integer variables
1427
1428 @type o: file like obj
1429 @param o: where write the code
1430 @type name: str
1431 @param name: field name
1432 @type type: str
1433 @param type: int
1434 @type startindex: int
1435 @param startindex: bit that begins the int(s)
1436 @type numbits: int >= 1
1437 @param numbits: How many bits per unit datum
1438 @type required: int or None
1439 @param required: If not None, then the value must be set to this.
1440 @type arraylen: int >= 1
1441 @param arraylen: many ints will there be? FIX: handle variable
1442 @type unavailable: int or None
1443 @param unavailable: the default value to use if none given (if not None)
1444 @type bv: str
1445 @param bv: BitVector containing the incoming data
1446 @type dataDict: str
1447 @param dataDict: dictionary in which to place the results
1448 @type decodeOnly: bool
1449 @param decodeOnly: Set to true to only get the code for decoding
1450 @rtype: int
1451 @return: index one past the end of where this read
1452 '''
1453 assert type=='int'
1454 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1455 if None==arraylen: arraylen=1
1456 end = startindex+int(numbits)*int(arraylen)
1457 assert arraylen == 1
1458 assert numbits>=1
1459
1460 if None != required:
1461 int(required)
1462 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1463 o.write(str(required))
1464 if not decodeOnly: o.write('\n')
1465 return end
1466
1467 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1468 o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])')
1469 if not decodeOnly: o.write('\n')
1470 if verbose: o.write('\n')
1471
1472 return end
1473
1474 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1475 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1476 '''
1477 Build the decoder for IEEE float variables
1478
1479 @type o: file like obj
1480 @param o: where write the code
1481 @type name: str
1482 @param name: field name
1483 @type type: str
1484 @param type: int
1485 @type startindex: int
1486 @param startindex: bit that begins the int(s)
1487 @type numbits: int >= 1
1488 @param numbits: How many bits per unit datum
1489 @type required: float or None
1490 @param required: If not None, then the value must be set to this.
1491 @type arraylen: int >= 1
1492 @param arraylen: many ints will there be? FIX: handle variable
1493 @type unavailable: float or None
1494 @param unavailable: the default value to use if none given (if not None)
1495 @type bv: str
1496 @param bv: BitVector containing the incoming data
1497 @type dataDict: str
1498 @param dataDict: dictionary in which to place the results
1499 @type decodeOnly: bool
1500 @param decodeOnly: Set to true to only get the code for decoding
1501 @rtype: int
1502 @return: index one past the end of where this read
1503 '''
1504 assert type=='float'
1505 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1506 if None==arraylen: arraylen=1
1507 end = startindex+int(numbits)*int(arraylen)
1508 assert arraylen == 1
1509 assert numbits>=1
1510
1511 if None != required:
1512 float(required)
1513 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1514 o.write(str(required))
1515 if not decodeOnly: o.write('\n')
1516 if verbose: o.write('\n')
1517 return end
1518
1519 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1520 o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])')
1521 if not decodeOnly: o.write('\n')
1522
1523 return end
1524
1525
1526 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1527 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1528 '''
1529 Build the decoder for aisstr6 variables. Generally arrays.
1530 @bug: FIX: validate strings??
1531 @type o: file like obj
1532 @param o: where write the code
1533 @type name: str
1534 @param name: field name
1535 @type type: str
1536 @param type: 'aisstr6'
1537 @type startindex: int
1538 @param startindex: bit that begins the int(s)
1539 @type numbits: int >= 1
1540 @param numbits: How many bits per unit datum
1541 @type required: restricted str or None
1542 @param required: If not None, then the value must be set to this.
1543 @type arraylen: int >= 1
1544 @param arraylen: many ints will there be? FIX: handle variable
1545 @type unavailable: restricted str or None
1546 @param unavailable: the default value to use if none given (if not None)
1547 @type bv: str
1548 @param bv: BitVector containing the incoming data
1549 @type dataDict: str
1550 @param dataDict: dictionary in which to place the results
1551 @type decodeOnly: bool
1552 @param decodeOnly: Set to true to only get the code for decoding
1553 @rtype: int
1554 @return: index one past the end of where this read
1555 '''
1556 assert type=='aisstr6'
1557 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1558 if None==arraylen: arraylen=1
1559 end = startindex+int(numbits)*int(arraylen)
1560 assert arraylen >= 1
1561 assert numbits>=1
1562
1563 if None != required:
1564 float(required)
1565 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1566 o.write(required)
1567 if not decodeOnly: o.write('\n')
1568 return end
1569
1570 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1571 o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])')
1572 if not decodeOnly: o.write('\n')
1573
1574 return end
1575
1576
1577 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1578 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1579 '''
1580 Build the decoder for signed decimal variables
1581
1582 @type o: file like obj
1583 @param o: where write the code
1584 @type name: str
1585 @param name: field name
1586 @type type: str
1587 @param type: 'decimal'
1588 @type startindex: int
1589 @param startindex: bit that begins the int(s)
1590 @type numbits: int >= 1
1591 @param numbits: How many bits per unit datum
1592 @type required: Decimal or None
1593 @param required: If not None, then the value must be set to this.
1594 @type arraylen: int >= 1
1595 @param arraylen: many ints will there be? FIX: handle variable
1596 @type unavailable: Decimal or None
1597 @param unavailable: the default value to use if none given (if not None)
1598 @type bv: str
1599 @param bv: BitVector containing the incoming data
1600 @type dataDict: str
1601 @param dataDict: dictionary in which to place the results
1602 @type decodeOnly: bool
1603 @param decodeOnly: Set to true to only get the code for decoding
1604 @rtype: int
1605 @return: index one past the end of where this read
1606 '''
1607 assert type=='decimal'
1608 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1609 if None==arraylen: arraylen=1
1610 end = startindex+int(numbits)*int(arraylen)
1611 assert arraylen == 1
1612 assert numbits>=1 and numbits <= 32
1613
1614 if None == scale: scale='1'
1615
1616 if None != required:
1617 Decimal(required)
1618 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1619 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')')
1620 if not decodeOnly: o.write('\n')
1621 return end
1622
1623 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1624 o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')')
1625 if not decodeOnly: o.write('\n')
1626
1627 return end
1628
1629
1630
1631
1632 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1633 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1634 '''
1635 Build the decoder for unsigned decimal variables
1636
1637 @type o: file like obj
1638 @param o: where write the code
1639 @type name: str
1640 @param name: field name
1641 @type type: str
1642 @param type: 'udecimal'
1643 @type startindex: int
1644 @param startindex: bit that begins the int(s)
1645 @type numbits: int >= 1
1646 @param numbits: How many bits per unit datum
1647 @type required: Decimal or None
1648 @param required: If not None, then the value must be set to this.
1649 @type arraylen: int >= 1
1650 @param arraylen: many ints will there be? FIX: handle variable
1651 @type unavailable: Decimal or None
1652 @param unavailable: the default value to use if none given (if not None)
1653 @type bv: str
1654 @param bv: BitVector containing the incoming data
1655 @type dataDict: str
1656 @param dataDict: dictionary in which to place the results
1657 @type decodeOnly: bool
1658 @param decodeOnly: Set to true to only get the code for decoding
1659 @rtype: int
1660 @return: index one past the end of where this read
1661 '''
1662 assert type=='udecimal'
1663 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1664 if None==arraylen: arraylen=1
1665 end = startindex+int(numbits)*int(arraylen)
1666 assert arraylen == 1
1667 assert numbits>=1 and numbits <= 32
1668
1669 if None == scale: scale='1'
1670
1671 if None != required:
1672 assert (Decimal(required)>=0.)
1673 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1674 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')')
1675 if not decodeOnly: o.write('\n')
1676 return end
1677
1678 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1679 o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')')
1680 if not decodeOnly: o.write('\n')
1681
1682 return end
1683
1684
1685 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1686 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1687 '''
1688 Build the decoder for unsigned decimal variables
1689
1690 @type o: file like obj
1691 @param o: where write the code
1692 @type name: str
1693 @param name: field name
1694 @type type: str
1695 @param type: 'udecimal'
1696 @type startindex: int
1697 @param startindex: bit that begins the int(s)
1698 @type numbits: int >= 1
1699 @param numbits: How many bits per unit datum. If -1, then read to the end of the message
1700 @type required: Decimal or None
1701 @param required: If not None, then the value must be set to this.
1702 @type arraylen: int >= 1
1703 @param arraylen: many ints will there be? FIX: handle variable
1704 @type unavailable: Decimal or None
1705 @param unavailable: the default value to use if none given (if not None)
1706 @type bv: str
1707 @param bv: BitVector containing the incoming data
1708 @type dataDict: str
1709 @param dataDict: dictionary in which to place the results
1710 @type decodeOnly: bool
1711 @param decodeOnly: Set to true to only get the code for decoding
1712 @rtype: int
1713 @return: index one past the end of where this read
1714 '''
1715 assert type=='binary'
1716 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1717 if None==arraylen: arraylen=1
1718 end = startindex+int(numbits)*int(arraylen)
1719 assert arraylen == 1
1720 assert (numbits>=1 and numbits <= 1024) or -1==numbits
1721
1722
1723
1724 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1725 o.write(bv+'['+str(startindex)+':')
1726 if int(numbits) != -1: o.write(str(end))
1727 o.write(']')
1728 if not decodeOnly: o.write('\n')
1729
1730 return end
1731
1732
1733
1734
1735
1736
1737
1738
1739
1741 '''Scrape the testvalues to make a basic param
1742
1743 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies?
1744 '''
1745 name = msgET.attrib['name']
1746
1747 if prefixName: o.write('def '+name+'TestParams():\n')
1748 else: o.write('def testParams():\n')
1749 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")
1750 o.write('\tparams = {}\n')
1751 for field in msgET.xpath('field'):
1752 name = field.attrib['name']
1753 type = field.attrib['type']
1754 if verbose: print 'buildTestParamFunc ...',name,type
1755 val = None
1756 if hasSubTag(field,'testvalue') and hasSubTag(field,'required'):
1757 print 'ERROR: can not have both test value and required tags in the same field'
1758 assert(False)
1759 if hasSubTag(field,'testvalue'):
1760 val = field.xpath('testvalue')[0].text
1761 else:
1762 if not hasSubTag(field,'required'):
1763 sys.exit("ERROR: missing required or testvalue for field: "+name)
1764 val = field.xpath('required')[0].text
1765 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val
1766 o.write('\tparams[\''+name+'\'] = ')
1767 if type=='bool':
1768 if val=='1' or val.lower=='true': val = 'True'
1769 else: val = 'False'
1770 o.write(val)
1771 elif type in ('uint','int','float'):
1772 o.write(val)
1773 elif type in ('decimal','udecimal'):
1774 o.write('Decimal(\''+val+'\')')
1775
1776 elif type in ('aisstr6'):
1777 o.write('\''+val+'\'')
1778 elif type in ('binary'):
1779 o.write('BitVector(bitstring=\''+val+'\')')
1780 else:
1781 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee'
1782 suggestType(name,type)
1783 assert(False)
1784
1785 o.write('\n')
1786
1787
1788 o.write('\n\treturn params\n\n')
1789
1791 '''
1792 Write the unittests for a message
1793
1794 @param o: open file where resulting code will be written
1795 @param msgET: Element Tree starting at a message node
1796 '''
1797 assert(msgET.tag=='message')
1798 name = msgET.attrib['name']
1799
1800 buildTestParamFunc(o,msgET, prefixName=prefixName)
1801
1802 o.write('class Test'+name+'(unittest.TestCase):\n')
1803 o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n")
1804 o.write('\tdef testEncodeDecode(self):\n\n')
1805 if prefixName:
1806 o.write('\t\tparams = '+name+'TestParams()\n')
1807 o.write('\t\tbits = '+name+'Encode(params)\n')
1808 o.write('\t\tr = '+name+'Decode(bits)\n\n')
1809 else:
1810 o.write('\t\tparams = testParams()\n')
1811 o.write('\t\tbits = encode(params)\n')
1812 o.write('\t\tr = decode(bits)\n\n')
1813
1814 o.write('\t\t# Check that each parameter came through ok.\n')
1815 for field in msgET.xpath('field'):
1816 name = field.attrib['name']
1817 type = field.attrib['type']
1818 if type in ('bool','uint','int','aisstr6','binary'):
1819 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n')
1820 else:
1821
1822
1823 places = '3'
1824 if hasSubTag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text
1825 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
1826
1827
1828
1829 -def buildEncode(o,msgET, verbose=False, prefixName=False):
1830 '''
1831 Write the encoder/decoder for a message
1832
1833 http://jaynes.colorado.edu/PythonIdioms.html
1834
1835 @param o: open file where resulting code will be written
1836 @param msgET: Element Tree starting at a message node
1837 @todo: handle ais message 20 optional. very troublesome
1838 '''
1839 assert(msgET.tag=='message')
1840 name = msgET.attrib['name']
1841
1842 print 'Generating encoder for',name
1843 funcName = 'encode'
1844 if prefixName: funcName = name+'Encode'
1845 o.write('def '+funcName+'(params, validate=False):\n')
1846
1847
1848
1849 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n")
1850 o.write('\tFields in params:\n')
1851 for field in msgET.xpath('field'):
1852 if verbose:
1853 print field.tag,field.attrib['name']
1854 print field.attrib
1855 desc = field[0].text.replace('\n',' ')
1856 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)
1857 if len(field.xpath("required")) == 1:
1858 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")')
1859
1860 o.write('\n')
1861 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n')
1862 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n')
1863
1864 o.write("\t@rtype: BitVector\n")
1865 o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n")
1866 o.write("\t@note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.\n")
1867 o.write("\t'''\n\n")
1868
1869
1870
1871
1872 o.write('\tbvList = []\n')
1873
1874 if verbose: print 'number of fields = ', len(msgET.xpath('field'))
1875
1876 dynamicArrays = False
1877
1878 for field in msgET.xpath('field'):
1879 name = field.attrib['name']
1880 type = field.attrib['type']
1881 numbits = int(field.attrib['numberofbits'])
1882 required = None;
1883 if hasSubTag(field,'required'):
1884 required = field.xpath('required')[0].text
1885
1886 unavailable=None;
1887 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
1888 arraylen=1
1889 if 'arraylength' in field.attrib:
1890 arraylen=int(field.attrib['arraylength'])
1891 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
1892 else:
1893 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')'
1894
1895 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable)
1896 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable)
1897 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable)
1898 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable)
1899 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable)
1900 elif type=='decimal':
1901 scale = None
1902 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
1903 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale)
1904 elif type=='udecimal':
1905 scale = None
1906 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
1907 encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale)
1908 elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable)
1909 else:
1910 print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type
1911 suggestType (name,type)
1912 assert False
1913
1914
1915
1916
1917
1918
1919 o.write('\n\treturn binary.joinBV(bvList)\n\n')
1920
1921
1922
1923
1924
1925
1926
1928
1929 '''
1930 Write the decoder for a message
1931
1932 @param o: open file where resulting code will be written
1933 @type msgET: elementtree
1934 @param prefixName: if True, put the name of the message on the functions.
1935 @param msgET: Element Tree starting at a message node
1936 @return: None
1937
1938 @todo: FIX: doc strings for each decode!
1939 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload
1940 @todo: May want to take a dictionary of already decoded fields to speed things that need prior info
1941 for things like variable length arrays
1942 '''
1943
1944 assert(msgET.tag=='message')
1945 name = msgET.attrib['name']
1946
1947 print 'Generating partial decode functions for',name
1948
1949 baseName = name+'Decode'
1950 if not prefixName: baseName = 'decode'
1951
1952 startindex = 0
1953
1954 for field in msgET.xpath('field'):
1955 name = field.attrib['name']
1956 type = field.attrib['type']
1957
1958 o.write('def '+baseName+name+'(bv, validate=False):\n')
1959
1960
1961 o.write('\treturn ')
1962
1963 numbits = int(field.attrib['numberofbits'])
1964 required = None;
1965 if hasSubTag(field,'required'):
1966 required = field.xpath('required')[0].text
1967 unavailable=None;
1968 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
1969 arraylen=1
1970 if 'arraylength' in field.attrib:
1971 arraylen=int(field.attrib['arraylength'])
1972 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
1973
1974 assert None!=startindex
1975 if verbose: print 'startindex',startindex
1976 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1977 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1978 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1979 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1980 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1981 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1982
1983 elif type=='decimal':
1984 scale = None
1985 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
1986 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True)
1987 elif type=='udecimal':
1988 scale = None
1989 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
1990 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True)
1991
1992 else:
1993 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type
1994 suggestType (name,type)
1995 assert False
1996
1997
1998 o.write('\n\n')
1999
2000
2001
2002
2003
2004 -def buildDecode(o,msgET, verbose=False, prefixName=False):
2005 '''
2006 Write the decoder for a message
2007
2008 @param o: open file where resulting code will be written
2009 @type msgET: elementtree
2010 @param msgET: Element Tree starting at a message node
2011 @return: None
2012
2013 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload
2014 '''
2015
2016 assert(msgET.tag=='message')
2017 name = msgET.attrib['name']
2018
2019 print 'Generating decoder for',name
2020 funcName = 'decode'
2021 if prefixName: funcName = name+'Decode'
2022 o.write('def '+funcName+'(bv, validate=False):\n')
2023
2024
2025
2026 o.write("\t'''Unpack a "+name+" message \n\n")
2027 o.write('\tFields in params:\n')
2028 for field in msgET.xpath('field'):
2029 desc = field[0].text.replace('\n',' ')
2030 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)
2031 if len(field.xpath("required")) == 1:
2032 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")')
2033
2034 o.write('\n')
2035 o.write('\t@type bv: BitVector\n')
2036 o.write('\t@param bv: Bits defining a message\n')
2037 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n')
2038
2039 o.write("\t@rtype: dict\n")
2040 o.write("\t@return: params\n")
2041 o.write("\t'''\n\n")
2042
2043
2044 o.write('\t#Would be nice to check the bit count here..\n')
2045 o.write('\t#if validate:\n')
2046 o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n')
2047
2048
2049
2050
2051
2052 o.write('\tr = {}\n')
2053
2054
2055 if verbose: print 'number of fields = ', len(msgET.xpath('field'))
2056
2057 dynamicArrays = False
2058
2059 startindex = 0
2060
2061 for field in msgET.xpath('field'):
2062 name = field.attrib['name']
2063 type = field.attrib['type']
2064 numbits = int(field.attrib['numberofbits'])
2065 required = None;
2066 if hasSubTag(field,'required'):
2067 required = field.xpath('required')[0].text
2068
2069 unavailable=None;
2070 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
2071 arraylen=1
2072 if 'arraylength' in field.attrib:
2073 arraylen=int(field.attrib['arraylength'])
2074 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
2075 else:
2076 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')'
2077
2078 assert None!=startindex
2079 if verbose: print 'startindex',startindex
2080
2081 if hasSubTag(field,'optional'):
2082
2083
2084 if msgET.attrib['aismsgnum']!='20':
2085 sys.exit('optional is only allow on AIS msg 20. Do NOT use it on new messages!!!')
2086 text = field.xpath('optional')[0].text
2087 if None==field.xpath('optional')[0].text:
2088
2089 pad = 8-(startindex%8)
2090 print 'not found: noreturn. pad=',pad
2091 assert(pad<=6)
2092 o.write('\tif len(bv)<='+str(startindex+pad)+': return r; # All fields below are optional\n')
2093 elif text != 'NoReturn':
2094 sys.exit ('ERROR: optional text must be NoReturn or empty')
2095
2096 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable)
2097 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable)
2098 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable)
2099 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable)
2100 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable)
2101 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable)
2102
2103 elif type=='decimal':
2104 scale = None
2105 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2106
2107
2108 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale)
2109 elif type=='udecimal':
2110 scale = None
2111 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2112
2113
2114 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale)
2115
2116 else:
2117 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type
2118 suggestType (name,type)
2119 assert False
2120
2121
2122 if None==startindex: print 'FIX: here. drat. treat me right'
2123 assert None!=startindex
2124
2125
2126 o.write('\treturn r\n\n')
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137 aisType2pythonType={
2138 'bool':'Bool',
2139 'uint':'int',
2140 'int':'int',
2141 'udecimal':'Decimal',
2142 'decimal':'Decimal',
2143 'aisstr6':'str',
2144 'binary':'str',
2145 'float':'Float',
2146 }
2147
2148 import copy
2149 aisType2optParseType=copy.deepcopy(aisType2pythonType)
2150 aisType2optParseType['bool']='int'
2151 aisType2optParseType['aisstr6']='string'
2152 aisType2optParseType['aisstr6']='string'
2153 aisType2optParseType['binary']='string'
2154 aisType2optParseType['udecimal']='string'
2155 aisType2optParseType['decimal']='string'
2156 aisType2optParseType['float']='float'
2157
2158
2160 '''Create a function that adds the options to a parse object'''
2161
2162 assert None != msgET
2163 assert msgET.tag=='message'
2164 msgName = msgET.attrib['name']
2165
2166 prefix=''
2167 if prefixName: prefix=msgName
2168
2169 funcName = 'addMsgOptions'
2170 if prefixName: funcName = msgName + 'AddMsgOptions'
2171 o.write('\ndef '+funcName+'(parser):')
2172
2173
2174
2175 o.write('''
2176 parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true',
2177 help='decode a "'''+msgName+'''" AIS message')
2178 parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true',
2179 help='encode a "'''+msgName+'''" AIS message')
2180 ''')
2181
2182
2183 for field in msgET.xpath('field'):
2184 name = field.attrib['name']
2185 fieldType = field.attrib['type']
2186 if hasSubTag(field,'required'):
2187 print 'skipping required field ...',name,fieldType
2188 continue
2189
2190 o.write('\tparser.add_option(\'--')
2191 if prefixName: o.write(msgName+'-')
2192 o.write(name+'-field\', dest=\''+name+'Field\'')
2193 if hasSubTag(field,'unavailable'):
2194 val = field.xpath('unavailable')[0].text
2195 o.write(',default=')
2196 if fieldType in ('uint','int','float'):
2197 o.write(val)
2198 elif fieldType in ('decimal','udecimal'):
2199 o.write('Decimal(\''+val+'\')')
2200 elif fieldType in ('aisstr6','bitvector'):
2201 o.write('\''+val+'\'')
2202 o.write(',metavar=\''+fieldType+'\',type=\''+aisType2optParseType[fieldType]+'\'')
2203 o.write('\n\t\t,help=\'Field parameter value [default: %default]\')\n')
2204
2205
2206
2207 -def buildMain(o, msgET, prefixName=False):
2208 assert None != msgET
2209 assert msgET.tag=='message'
2210 msgName = msgET.attrib['name']
2211
2212 prefix=''
2213 if prefixName: prefix=msgName
2214
2215 buildOptParse(o, msgET, prefixName)
2216
2217
2218 o.write('''
2219 ############################################################
2220 if __name__=='__main__':
2221
2222 from optparse import OptionParser
2223 parser = OptionParser(usage="%prog [options]",
2224 version="%prog "+__version__)
2225
2226 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
2227 help='run the documentation tests')
2228 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true',
2229 help='run the unit tests')
2230 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
2231 help='Make the test output verbose')
2232
2233 # FIX: remove nmea from binary messages. No way to build the whole packet?
2234 # FIX: or build the surrounding msg 8 for a broadcast?
2235 typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message?
2236 parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType'
2237 ,default='nmeapayload'
2238 ,help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]')
2239
2240
2241 outputChoices = ('std','html','csv','sql' ''')
2242
2243 if haveLocatableMessage(msgET): o.write(', \'kml\',\'kml-full\'')
2244 o.write(''')
2245 parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType'
2246 ,default='std'
2247 ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]')
2248
2249 parser.add_option('-o','--output',dest='outputFileName',default=None,
2250 help='Name of the python file to write [default: stdout]')
2251
2252 parser.add_option('-f','--fields',dest='fieldList',default=None, action='append',
2253 choices=fieldList,
2254 help='Which fields to include in the output. Currently only for csv output [default: all]')
2255
2256 parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true',
2257 help='Print the field name for csv')
2258
2259 parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true',
2260 help='Print out an sql create command for the table.')
2261
2262 parser.add_option('--latex-table',dest='latexDefinitionTable',default=False,action='store_true',
2263 help='Print a LaTeX table of the type')
2264
2265 dbChoices = ('sqlite','postgres')
2266 parser.add_option('-D','--db-type',dest='dbType',default='postgres'
2267 ,choices=dbChoices,type='choice'
2268 ,help='What kind of database ('+', '.join(dbChoices)+') [default: %default]')
2269
2270 ''')
2271
2272 o.write('''\taddMsgOptions(parser)\n''')
2273
2274 o.write('''
2275 (options,args) = parser.parse_args()
2276 success=True
2277
2278 if options.doctest:
2279 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
2280 sys.argv= [sys.argv[0]]
2281 if options.verbose: sys.argv.append('-v')
2282 import doctest
2283 numfail,numtests=doctest.testmod()
2284 if numfail==0: print 'ok'
2285 else:
2286 print 'FAILED'
2287 success=False
2288
2289 if not success: sys.exit('Something Failed')
2290 del success # Hide success from epydoc
2291
2292 if options.unittest:
2293 sys.argv = [sys.argv[0]]
2294 if options.verbose: sys.argv.append('-v')
2295 unittest.main()
2296
2297 outfile = sys.stdout
2298 if None!=options.outputFileName:
2299 outfile = file(options.outputFileName,'w')
2300
2301 ''')
2302
2303
2304
2305
2306
2307
2308
2309 o.write('\n\tif options.doEncode:\n')
2310 o.write('\t\t# First make sure all non required options are specified\n')
2311 for field in msgET.xpath('field'):
2312 name = field.attrib['name']
2313 fieldType = field.attrib['type']
2314 varName = prefix+name+'Field'
2315 if not hasSubTag(field,'required'):
2316 o.write('\t\tif None==options.'+varName+': parser.error("missing value for '+varName+'")\n')
2317
2318
2319 o.write('\t\tmsgDict={\n')
2320 for field in msgET.xpath('field'):
2321 name = field.attrib['name']
2322 varName = prefix+name+'Field'
2323 if hasSubTag(field,'required'):
2324 o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n')
2325 else:
2326 o.write('\t\t\t\''+name+'\': options.'+varName+',\n')
2327 o.write('\t\t}\n')
2328
2329 encodeFunction = 'encode'
2330 if prefixName: encodeFunction = msgName+'Encode'
2331 o.write('''
2332 bits = '''+encodeFunction+'''(msgDict)
2333 if 'binary'==options.ioType: print str(bits)
2334 elif 'nmeapayload'==options.ioType:
2335 # FIX: figure out if this might be necessary at compile time
2336 print "bitLen",len(bits)
2337 bitLen=len(bits)
2338 if bitLen%6!=0:
2339 bits = bits + BitVector(size=(6 - (bitLen%6))) # Pad out to multiple of 6
2340 print "result:",binary.bitvectoais6(bits)[0]
2341
2342
2343 # FIX: Do not emit this option for the binary message payloads. Does not make sense.
2344 elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability")
2345 else: sys.exit('ERROR: unknown ioType. Help!')
2346 ''')
2347
2348
2349
2350
2351
2352 decodeFunction = 'decode'
2353 printFields='printFields'
2354 if prefixName:
2355 decodeFunction = msgName+'Decode'
2356 printFields = msgName+'PrintFields'
2357 o.write('''
2358
2359 if options.sqlCreate:
2360 sqlCreateStr(outfile,options.fieldList,dbType=options.dbType)
2361
2362 if options.latexDefinitionTable:
2363 latexDefinitionTable(outfile)
2364
2365 if options.printCsvfieldList:
2366 # Make a csv separated list of fields that will be displayed for csv
2367 if None == options.fieldList: options.fieldList = fieldList
2368 import StringIO
2369 buf = StringIO.StringIO()
2370 for field in options.fieldList:
2371 buf.write(field+',')
2372 result = buf.getvalue()
2373 if result[-1] == ',': print result[:-1]
2374 else: print result
2375
2376 if options.doDecode:
2377 for msg in args:
2378 bv = None
2379
2380 if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'):
2381 # Found nmea
2382 # FIX: do checksum
2383 bv = binary.ais6tobitvec(msg.split(',')[5])
2384 else: # either binary or nmeapayload... expect mostly nmeapayloads
2385 # assumes that an all 0 and 1 string can not be a nmeapayload
2386 binaryMsg=True
2387 for c in msg:
2388 if c not in ('0','1'):
2389 binaryMsg=False
2390 break
2391 if binaryMsg:
2392 bv = BitVector(bitstring=msg)
2393 else: # nmeapayload
2394 bv = binary.ais6tobitvec(msg)
2395
2396 '''+printFields+'''('''+decodeFunction+'''(bv)
2397 ,out=outfile
2398 ,format=options.outputType
2399 ,fieldList=options.fieldList
2400 ,dbType=options.dbType
2401 )
2402
2403 ''')
2404
2405
2406
2407 if __name__=='__main__':
2408 from optparse import OptionParser
2409 parser = OptionParser(usage="%prog [options]",
2410 version="%prog "+__version__)
2411
2412 parser.add_option('-o','--output',dest='outputFileName',default=None,
2413 help='Name of the python file to write')
2414
2415
2416 parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None,
2417 help='XML definition file for the msg to use')
2418
2419
2420
2421
2422
2423 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
2424 help='run the documentation tests')
2425
2426 parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true',
2427 help='put the field name in front of all function names.'
2428 +' Allows multiple messages in one file')
2429
2430 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
2431 help='run the tests run in verbose mode')
2432
2433 (options,args) = parser.parse_args()
2434
2435 success=True
2436
2437 if options.doctest:
2438 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
2439 argvOrig = sys.argv
2440 sys.argv= [sys.argv[0]]
2441 if options.verbose: sys.argv.append('-v')
2442 import doctest
2443 numfail,numtests=doctest.testmod()
2444 if numfail==0: print 'ok'
2445 else:
2446 print 'FAILED'
2447 success=False
2448 sys.argv = argvOrig
2449 del argvOrig
2450 sys.exit()
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460 if None==options.xmlFileName:
2461 sys.exit('ERROR: must specify an xml definition file.')
2462 if None==options.outputFileName:
2463 sys.exit('ERROR: must specify an python file to write to.')
2464 generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix, verbose=options.verbose)
2465
2466 print '\nrecommend running pychecker like this:'
2467 print ' pychecker -q',options.outputFileName
2468