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