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 if not hasSubTag(field,'scale'):
676 print 'ERROR: missing required <scale> for',fieldName,'of type',fieldType
677 scaleVal = float(field.xpath('scale')[0].text)
678 precision = scaleSQL + len(str(int((2**numBits)/scaleVal)))
679
680 o.write('Decimal(\''+fieldName+'\','+str(precision)+','+str(scaleSQL)+')')
681 elif fieldType == 'binary':
682 numBits = int(field.attrib['numberofbits'])
683 if -1 == numBits: numBits=1024
684 o.write('BitVarying(\''+fieldName+'\','+str(numBits)+')')
685 elif fieldType == 'aisstr6':
686 arrayLength = int(field.attrib['arraylength'])
687 o.write('VarChar(\''+fieldName+'\','+str(arrayLength)+')')
688 else:
689 print '\n\n *** Unknown type in SQL code generation for field',fieldName+':',fieldType,'\n\n'
690 assert(False)
691 o.write('\n')
692
693 o.write('''
694 if addCoastGuardFields:
695 # c.addInt('cg_rssi') # Relative signal strength indicator
696 # c.addInt('cg_d') # dBm receive strength
697 # c.addInt('cg_T') # Receive timestamp from the AIS equipment
698 # c.addInt('cg_S') # Slot received in
699 # c.addVarChar('cg_x',10) # Idonno
700 c.addVarChar('cg_r',15) # Receiver station ID - should usually be an MMSI, but sometimes is a string
701 c.addInt('cg_sec') # UTC seconds since the epoch
702
703 c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream
704 ''')
705
706
707
708
709
710 postgisFields = msgET.xpath('field[@postgisType]')
711 if len(postgisFields)<1:
712 if verbose: print 'No postgis fields'
713 else:
714 o.write('\n\tif dbType == \'postgres\':\n')
715 finished=[]
716 print 'processing postgis fields for create:'
717 for field in postgisFields:
718
719 pgName = field.attrib['postgisName']
720 if pgName in finished:
721 print 'already handled',pgName
722 continue
723 finished.append(pgName)
724 pgType = field.attrib['postgisType']
725
726 components = msgET.xpath('field[@postgisName=\''+pgName+'\']')
727
728
729
730 o.write('\t\tc.addPostGIS(\''+pgName+'\',\''+pgType+'\','+str(len(components))+');\n')
731
732 o.write('\n\treturn c\n\n')
733
734
735
736 o.write('''def '''+insertFuncName+'''Str(params, outfile=sys.stdout, extraParams=None, dbType='postgres'):
737 \'\'\'
738 Return the SQL INSERT command for this message type
739 @param params: dictionary of values keyed by field name
740 @param outfile: file like object to print to.
741 @param extraParams: A sequence of tuples containing (name,sql type) for additional fields
742 @return: sql create string
743 @rtype: str
744
745 @see: sqlCreate
746 \'\'\'
747 outfile.write(str(sqlInsert(params,extraParams,dbType=dbType)))
748
749
750 ''')
751
752
753 o.write('''def '''+insertFuncName+'''(params,extraParams=None,dbType='postgres'):
754 \'\'\'
755 Give the SQL INSERT statement
756 @param params: dict keyed by field name of values
757 @param extraParams: any extra fields that you have created beyond the normal ais message fields
758 @rtype: sqlhelp.insert
759 @return: insert class instance
760 @todo: allow optional type checking of params?
761 @warning: this will take invalid keys happily and do what???
762 \'\'\'
763 import sqlhelp
764 i = sqlhelp.insert(\'''' + msgName + '''\',dbType=dbType)
765
766 if dbType=='postgres':
767 finished = []
768 for key in params:
769 if key in finished:
770 continue
771
772 if key not in toPgFields and key not in fromPgFields:
773 if type(params[key])==Decimal: i.add(key,float(params[key]))
774 else: i.add(key,params[key])
775 else:
776 if key in fromPgFields:
777 val = params[key]
778 # Had better be a WKT type like POINT(-88.1 30.321)
779 i.addPostGIS(key,val)
780 finished.append(key)
781 else:
782 # Need to construct the type.
783 pgName = toPgFields[key]
784 #valStr='GeomFromText(\\''+pgTypes[pgName]+'('
785 valStr=pgTypes[pgName]+'('
786 vals = []
787 for nonPgKey in fromPgFields[pgName]:
788 vals.append(str(params[nonPgKey]))
789 finished.append(nonPgKey)
790 valStr+=' '.join(vals)+')'
791 i.addPostGIS(pgName,valStr)
792 else:
793 for key in params:
794 if type(params[key])==Decimal: i.add(key,float(params[key]))
795 else: i.add(key,params[key])
796
797 if None != extraParams:
798 for key in extraParams:
799 i.add(key,extraParams[key])
800
801 return i
802 ''')
803
804
805
806
807
808
809
810
811 -def buildLUT(o,msgET, verbose=False, prefixName=False):
812 '''
813 Write lookup tables for enumerated types (uint or int, maybe bool too).
814
815 @todo: FIX: what to do about multiple entries with the same text? Need to ban that kind of thing
816 @todo: Make doc strings for each LUT.
817
818 @param o: open file where resulting code will be written
819 @param msgET: Element Tree starting at a message node
820 '''
821 assert(msgET.tag=='message')
822 msgname = msgET.attrib['name']
823
824 print 'Generating lookup tables for',msgname
825
826 for field in msgET.xpath('field'):
827 name = field.attrib['name']
828 if not hasSubTag(field,'lookuptable'): continue
829 lut = field.xpath('lookuptable')[0]
830
831 lutName = name
832 if prefixName: lutName = msgname+name.capitalize()
833
834 o.write(lutName+'EncodeLut = {\n')
835 for entry in lut.xpath('entry'):
836 o.write('\t\''+entry.text+'\':\''+entry.attrib['key']+'\',\n')
837 o.write('\t} #'+lutName+'EncodeLut\n')
838 o.write('\n')
839
840
841
842 o.write(lutName+'DecodeLut = {\n')
843 for entry in lut.xpath('entry'):
844 o.write('\t\''+entry.attrib['key']+'\':\''+entry.text+'\',\n')
845 o.write('\t} # '+lutName+'EncodeLut\n')
846 o.write('\n')
847
848
849
850
851
852
853
854
855
856 -def encodeBool(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
857 '''
858 Build the encoder for boolean variables
859 @type o: file like obj
860 @param o: where write the code
861 @type name: str
862 @param name: field name
863 @type type: str
864 @param type: bool, etc.
865 @type numbits: int = 1
866 @param numbits: How many bits per unit datum (must be 1 for bools)
867 @type required: bool or None
868 @param required: If not None, then the value must be set to this.
869 @type arraylen: int >= 1
870 @param arraylen: many bools will there be? FIX: handle variable
871 @type unavailable: bool or None
872 @param unavailable: the default value to use if none given (if not None)
873 @return: None
874 '''
875
876 if verbose: print 'bool encode',name,': unvail=',unavailable
877
878 assert type.lower()=='bool'
879 assert numbits==1
880 if arraylen != 1: assert False
881 if verbose: o.write('\t### FIELD: '+name+' (type=bool)\n')
882 if None != required:
883 assert type(required)==bool
884 if required: o.write('\t\tbvList.append(TrueBV)\n')
885 else: o.write('\t\tbvList.append(FalseBV)\n')
886 if verbose: o.write('\n')
887 return
888
889 if None==unavailable:
890 o.write('\tif params["'+name+'"]: bvList.append(TrueBV)\n')
891 o.write('\telse: bvList.append(FalseBV)\n')
892 else:
893 assert type(unavailable)==bool
894 o.write("\tif '"+name+"' in params:\n")
895 o.write('\t\tif params["'+name+'"]: bvList.append(TrueBV)\n')
896 o.write('\t\telse: bvList.append(FalseBV)\n')
897 o.write('\telse:\n')
898 if unavailable: o.write('\t\tbvList.append(TrueBV)\n')
899 else: o.write('\t\tbvList.append(FalseBV)\n')
900 if verbose: o.write('\n')
901
902 -def encodeUInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
903 '''
904 Build the encoder for unsigned integer variables
905
906 @type o: file like obj
907 @param o: where write the code
908 @type name: str
909 @param name: field name
910 @type type: str
911 @param type: uint, bool, etc.
912 @type numbits: int >= 1
913 @param numbits: How many bits per unit datum (must be 1..32)
914 @type required: bool or None
915 @param required: If not None, then the value must be set to this.
916 @type arraylen: int >= 1
917 @param arraylen: many unsigned ints will there be? FIX: handle variable
918 @type unavailable: bool or None
919 @param unavailable: the default value to use if none given (if not None)
920 @return: None
921 '''
922 if verbose: print ' encodeUInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
923
924 assert type=='uint'
925 assert numbits>=1 and numbits<=32
926 if arraylen != 1: assert False
927 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
928
929 if None != required:
930 if verbose: print ' required:',required
931 required=int(required)
932 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(required)+'),'+str(numbits)+'))\n')
933 if verbose: o.write('\n')
934 return
935
936 if None==unavailable:
937 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']),'+str(numbits)+'))\n')
938 else:
939
940 int(unavailable)
941 o.write("\tif '"+name+"' in params:\n")
942 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=params[\''+name+'\']'+'),'+str(numbits)+'))\n')
943 o.write('\telse:\n')
944 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(unavailable)+'),'+str(numbits)+'))\n')
945
946 if verbose: o.write('\n')
947
948
949 -def encodeFloat(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
950 '''
951 Build the encoder for IEEE float variables
952
953 @type o: file like obj
954 @param o: where write the code
955 @type name: str
956 @param name: field name
957 @type fieldType: str
958 @param fieldType: uint, bool, etc.
959 @type numbits: int >= 1
960 @param numbits: How many bits per unit datum (must be 1..32)
961 @type required: bool or None
962 @param required: If not None, then the value must be set to this.
963 @type arraylen: int >= 1
964 @param arraylen: many unsigned ints will there be? FIX: handle variable
965 @type unavailable: bool or None
966 @param unavailable: the default value to use if none given (if not None)
967 @return: None
968 '''
969 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable
970
971 assert numbits==32
972 if arraylen != 1: assert False
973 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n')
974
975 if None != required:
976 if verbose: print ' required:',required
977 required=int(required)
978 o.write('\tbvList.append(binary.float2bitvec('+str(required)+'))\n')
979 if verbose: o.write('\n')
980 return
981
982 if None==unavailable:
983 o.write('\tbvList.append(binary.float2bitvec(params[\''+name+'\']))\n')
984 else:
985 int(unavailable)
986 o.write("\tif '"+name+"' in params:\n")
987 o.write('\t\tbvList.append(binary.float2bitvec(params[\''+name+'\']'+'))\n')
988 o.write('\telse:\n')
989 o.write('\t\tbvList.append(binary.float2bitvec('+str(unavailable)+'))\n')
990
991 if verbose: o.write('\n')
992
993
994 -def encodeAisstr6(o,name,fieldType,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
995 '''
996 Build the encoder for aisstr6 variables. Generally are arrays.
997 @bug: do we need to optionally check for a valid string?
998
999 @type o: file like obj
1000 @param o: where write the code
1001 @type name: str
1002 @param name: field name
1003 @type fieldType: str
1004 @param fieldType: uint, bool, etc.
1005 @type numbits: int >= 1
1006 @param numbits: How many bits per unit datum (must be 1..32)
1007 @type required: bool or None
1008 @param required: If not None, then the value must be set to this.
1009 @type arraylen: int >= 1
1010 @param arraylen: many unsigned ints will there be? FIX: handle variable
1011 @type unavailable: bool or None
1012 @param unavailable: the default value to use if none given (if not None)
1013 @return: None
1014 '''
1015 if verbose: print ' encodeUInt:',name,fieldType,numbits,'Req:',required,'alen:',arraylen,unavailable
1016 totLen = str(numbits*arraylen)
1017 assert numbits==6
1018 if verbose: o.write('\t### FIELD: '+name+' (type='+fieldType+')\n')
1019
1020 if None != required:
1021 if verbose: print ' required:',required
1022 required=int(required)
1023 o.write('\tbvList.append(aisstring.encode(\''+str(required)+'\','+totLen+'))\n')
1024 if verbose: o.write('\n')
1025 return
1026
1027 if None==unavailable:
1028 o.write('\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n')
1029 else:
1030 o.write("\tif '"+name+"' in params:\n")
1031 o.write('\t\tbvList.append(aisstring.encode(params[\''+name+'\'],'+totLen+'))\n')
1032 o.write('\telse:\n')
1033 o.write('\t\tbvList.append(aisstring.encode(\''+str(unavailable)+'\','+totLen+'))\n')
1034
1035 if verbose: o.write('\n')
1036
1037
1038 -def encodeInt(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False):
1039 '''
1040 Build the encoder for signed integer variables
1041
1042 @type o: file like obj
1043 @param o: where write the code
1044 @type name: str
1045 @param name: field name
1046 @type type: str
1047 @param type: uint, bool, etc.
1048 @type numbits: int >= 1
1049 @param numbits: How many bits per unit datum (must be 1..32)
1050 @type required: bool or None
1051 @param required: If not None, then the value must be set to this.
1052 @type arraylen: int >= 1
1053 @param arraylen: many signed ints will there be? FIX: handle variable
1054 @type unavailable: number or None
1055 @param unavailable: the default value to use if none given (if not None)
1056 @return: None
1057 '''
1058 if verbose: print ' encodeInt:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1059
1060 assert numbits>=1 and numbits<=32
1061 if arraylen != 1: assert False
1062 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1063
1064 if None != required:
1065 if verbose: print ' required:',required
1066 required=int(required)
1067 o.write('\tbvList.append(binary.bvFromSignedInt('+str(required)+','+str(numbits)+'))\n')
1068 if verbose: o.write('\n')
1069 return
1070
1071
1072 if None==unavailable:
1073 o.write('\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\'],'+str(numbits)+'))\n')
1074 else:
1075
1076 int(unavailable)
1077 o.write("\tif '"+name+"' in params:\n")
1078 o.write('\t\tbvList.append(binary.bvFromSignedInt(params[\''+name+'\']'+','+str(numbits)+'))\n')
1079 o.write('\telse:\n')
1080 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(unavailable)+','+str(numbits)+'))\n')
1081
1082 if verbose: o.write('\n')
1083
1084
1085
1086
1087 -def encodeDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1088 '''
1089 Build the encoder for signed decimal variables
1090
1091 @type o: file like obj
1092 @param o: where write the code
1093 @type name: str
1094 @param name: field name
1095 @type type: str
1096 @param type: decimal
1097 @type numbits: int >= 1
1098 @param numbits: How many bits per unit datum (must be 1..32)
1099 @type required: bool or None
1100 @param required: If not None, then the value must be set to this.
1101 @type arraylen: int >= 1
1102 @param arraylen: many decimals will there be? FIX: handle variable
1103 @type unavailable: Decimal or None
1104 @param unavailable: the default value to use if none given (if not None)
1105 @return: None
1106 '''
1107 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1108
1109 assert numbits>=1 and numbits<=32
1110 if arraylen != 1: assert False
1111 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1112
1113
1114 if None == scale:
1115 print 'WARNING: if you are not scaling, then you probably want to use an int instead!'
1116 print 'Beware canadians bearing travel videos'
1117 scale='1'
1118
1119 if None != required:
1120 if verbose: print ' required:',required
1121 required=int(required)
1122 o.write('\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(required)*Decimal(scale)))+','+str(numbits)+'))\n')
1123 if verbose: o.write('\n')
1124 return
1125
1126
1127 if None==unavailable:
1128 o.write('\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n')
1129 else:
1130 o.write("\tif '"+name+"' in params:\n")
1131 o.write('\t\tbvList.append(binary.bvFromSignedInt(int(Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')),'+str(numbits)+'))\n')
1132 o.write('\telse:\n')
1133 o.write('\t\tbvList.append(binary.bvFromSignedInt('+str(int(Decimal(unavailable)*Decimal(scale)))+','+str(numbits)+'))\n')
1134
1135 if verbose: o.write('\n')
1136
1137
1138
1139 -def encodeUDecimal(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1140 '''
1141 Build the encoder for signed decimal variables
1142
1143 @type o: file like obj
1144 @param o: where write the code
1145 @type name: str
1146 @param name: field name
1147 @type type: str
1148 @param type: decimal
1149 @type numbits: int >= 1
1150 @param numbits: How many bits per unit datum (must be 1..32)
1151 @type required: bool or None
1152 @param required: If not None, then the value must be set to this.
1153 @type arraylen: int >= 1
1154 @param arraylen: many decimals will there be? FIX: handle variable
1155 @type unavailable: Decimal or None
1156 @param unavailable: the default value to use if none given (if not None)
1157 @return: None
1158 '''
1159 if verbose: print ' encodeDecimal:',name,type,numbits,'Req:',required,'alen:',arraylen,unavailable
1160 assert type=='udecimal'
1161 assert numbits>=1 and numbits<=32
1162 if arraylen != 1: assert False
1163 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1164
1165
1166 if None == scale:
1167 print 'WARNING: if you are not scaling, then you probably want to use an int instead!'
1168 print 'Beware canadians bearing travel videos'
1169 scale='1'
1170
1171 if None != required:
1172 if verbose: print ' required:',required
1173 required=int(required)
1174 assert(0<=required)
1175 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal='+str(int(Decimal(required)*Decimal(scale)))+'),'+str(numbits)+'))\n')
1176 if verbose: o.write('\n')
1177 return
1178
1179
1180 if None==unavailable:
1181 o.write('\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n')
1182 else:
1183 o.write("\tif '"+name+"' in params:\n")
1184 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int((Decimal(params[\''+name+'\'])*Decimal(\''+scale+'\')))),'+str(numbits)+'))\n')
1185 o.write('\telse:\n')
1186 o.write('\t\tbvList.append(binary.setBitVectorSize(BitVector(intVal=int('+str(int(Decimal(unavailable)*Decimal(scale)))+')),'+str(numbits)+'))\n')
1187
1188 if verbose: o.write('\n')
1189
1190
1191
1192 -def encodeBinary(o,name,type,numbits,required=None,arraylen=1,unavailable=None, verbose=False, scale=None):
1193 '''
1194 Build the encoder for binary variables. This is just a pass through.
1195 This is used for the ais binary message wrapper (e.g. msg 8). Do
1196 not use it within binary messages.
1197
1198 @type o: file like obj
1199 @param o: where write the code
1200 @type name: str
1201 @param name: field name
1202 @type type: str
1203 @param type: binary
1204 @type numbits: int >= 1
1205 @param numbits: How many bits per unit datum (must be 1..1024 or so)
1206 @type required: bool or None
1207 @param required: If not None, then the value must be set to this.
1208 @type arraylen: int >= 1
1209 @param arraylen: many decimals will there be? FIX: handle variable
1210 @type unavailable: Decimal or None
1211 @param unavailable: the default value to use if none given (if not None)
1212 @return: None
1213 '''
1214 if verbose: print ' encode'+name+':',type,numbits,'Req:',required,'alen:',arraylen,unavailable
1215 assert type=='binary'
1216 assert (numbits>=1 and numbits<=1024) or numbits==-1
1217 assert (None == required)
1218 assert (None == unavailable)
1219
1220 if arraylen != 1: assert False
1221 if verbose: o.write('\t### FIELD: '+name+' (type='+type+')\n')
1222
1223
1224 o.write('\tbvList.append(params[\''+name+'\'])\n')
1225
1226 if verbose: o.write('\n')
1227
1228
1229
1230
1231
1232
1233 -def decodeBool(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1234 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1235 '''
1236 Build the decoder for boolean variables
1237
1238 @type o: file like obj
1239 @param o: where write the code
1240 @type name: str
1241 @param name: field name
1242 @type type: str
1243 @param type: uint, bool, etc.
1244 @type startindex: int
1245 @param startindex: bit that begins the bool(s)
1246 @type numbits: int = 1
1247 @param numbits: How many bits per unit datum (must be 1 for bools)
1248 @type required: bool or None
1249 @param required: If not None, then the value must be set to this.
1250 @type arraylen: int >= 1
1251 @param arraylen: many bools will there be? FIX: handle variable
1252 @type unavailable: bool or None
1253 @param unavailable: the default value to use if none given (if not None)
1254 @type bv: str
1255 @param bv: BitVector containing the incoming data
1256 @type dataDict: str
1257 @param dataDict: dictionary in which to place the results
1258 @type decodeOnly: bool
1259 @param decodeOnly: Set to true to only get the code for decoding
1260 @rtype: int
1261 @return: index one past the end of where this read
1262 '''
1263 assert(type=='bool')
1264 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1265
1266 assert numbits==1
1267 assert arraylen == 1
1268
1269 if None != required:
1270 assert type(required)==bool
1271 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1272 if required: o.write('True\n')
1273 else: o.write('False\n')
1274 if not decodeOnly: o.write('\n')
1275 return int(startindex)+int(numbits)
1276
1277 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1278 o.write('bool(int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+']))')
1279 if not decodeOnly: o.write('\n')
1280
1281 return int(startindex)+int(numbits)
1282
1283
1284 -def decodeUInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1285 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1286 '''
1287 Build the decoder for unsigned integer variables
1288
1289 @type o: file like obj
1290 @param o: where write the code
1291 @type name: str
1292 @param name: field name
1293 @type type: str
1294 @param type: uint, etc.
1295 @type startindex: int
1296 @param startindex: bit that begins the uint(s)
1297 @type numbits: int >= 1
1298 @param numbits: How many bits per unit datum
1299 @type required: int or None
1300 @param required: If not None, then the value must be set to this.
1301 @type arraylen: int >= 1
1302 @param arraylen: many ints will there be? FIX: handle variable
1303 @type unavailable: int or None
1304 @param unavailable: the default value to use if none given (if not None)
1305 @type bv: str
1306 @param bv: BitVector containing the incoming data
1307 @type dataDict: str
1308 @param dataDict: dictionary in which to place the results
1309 @type decodeOnly: bool
1310 @param decodeOnly: Set to true to only get the code for decoding
1311 @rtype: int
1312 @return: index one past the end of where this read
1313 '''
1314 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1315 if None==arraylen: arraylen=1
1316 assert arraylen == 1
1317 assert numbits>=1
1318 if not decodeOnly: verbose=False
1319
1320 if None != required:
1321 int(required)
1322 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1323 o.write(str(required))
1324 if not decodeOnly: o.write('\n')
1325 return startindex+numbits
1326
1327 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1328 o.write('int('+bv+'['+str(startindex)+':'+str(startindex+int(numbits)*int(arraylen))+'])')
1329 if not decodeOnly: o.write('\n')
1330 if verbose: o.write('\n')
1331
1332 return startindex+numbits
1333
1334
1335 -def decodeInt(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1336 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1337 '''
1338 Build the decoder for unsigned integer variables
1339
1340 @type o: file like obj
1341 @param o: where write the code
1342 @type name: str
1343 @param name: field name
1344 @type type: str
1345 @param type: int
1346 @type startindex: int
1347 @param startindex: bit that begins the int(s)
1348 @type numbits: int >= 1
1349 @param numbits: How many bits per unit datum
1350 @type required: int or None
1351 @param required: If not None, then the value must be set to this.
1352 @type arraylen: int >= 1
1353 @param arraylen: many ints will there be? FIX: handle variable
1354 @type unavailable: int or None
1355 @param unavailable: the default value to use if none given (if not None)
1356 @type bv: str
1357 @param bv: BitVector containing the incoming data
1358 @type dataDict: str
1359 @param dataDict: dictionary in which to place the results
1360 @type decodeOnly: bool
1361 @param decodeOnly: Set to true to only get the code for decoding
1362 @rtype: int
1363 @return: index one past the end of where this read
1364 '''
1365 assert type=='int'
1366 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1367 if None==arraylen: arraylen=1
1368 end = startindex+int(numbits)*int(arraylen)
1369 assert arraylen == 1
1370 assert numbits>=1
1371
1372 if None != required:
1373 int(required)
1374 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1375 o.write(str(required))
1376 if not decodeOnly: o.write('\n')
1377 return end
1378
1379 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1380 o.write('binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+'])')
1381 if not decodeOnly: o.write('\n')
1382 if verbose: o.write('\n')
1383
1384 return end
1385
1386 -def decodeFloat(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1387 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1388 '''
1389 Build the decoder for IEEE float variables
1390
1391 @type o: file like obj
1392 @param o: where write the code
1393 @type name: str
1394 @param name: field name
1395 @type type: str
1396 @param type: int
1397 @type startindex: int
1398 @param startindex: bit that begins the int(s)
1399 @type numbits: int >= 1
1400 @param numbits: How many bits per unit datum
1401 @type required: float or None
1402 @param required: If not None, then the value must be set to this.
1403 @type arraylen: int >= 1
1404 @param arraylen: many ints will there be? FIX: handle variable
1405 @type unavailable: float or None
1406 @param unavailable: the default value to use if none given (if not None)
1407 @type bv: str
1408 @param bv: BitVector containing the incoming data
1409 @type dataDict: str
1410 @param dataDict: dictionary in which to place the results
1411 @type decodeOnly: bool
1412 @param decodeOnly: Set to true to only get the code for decoding
1413 @rtype: int
1414 @return: index one past the end of where this read
1415 '''
1416 assert type=='float'
1417 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1418 if None==arraylen: arraylen=1
1419 end = startindex+int(numbits)*int(arraylen)
1420 assert arraylen == 1
1421 assert numbits>=1
1422
1423 if None != required:
1424 float(required)
1425 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1426 o.write(str(required))
1427 if not decodeOnly: o.write('\n')
1428 if verbose: o.write('\n')
1429 return end
1430
1431 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1432 o.write('binary.bitvec2float('+bv+'['+str(startindex)+':'+str(end)+'])')
1433 if not decodeOnly: o.write('\n')
1434
1435 return end
1436
1437
1438 -def decodeAisstr6(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1439 bv='bv',dataDict='r',verbose=False, decodeOnly=False):
1440 '''
1441 Build the decoder for aisstr6 variables. Generally arrays.
1442 @bug: FIX: validate strings??
1443 @type o: file like obj
1444 @param o: where write the code
1445 @type name: str
1446 @param name: field name
1447 @type type: str
1448 @param type: 'aisstr6'
1449 @type startindex: int
1450 @param startindex: bit that begins the int(s)
1451 @type numbits: int >= 1
1452 @param numbits: How many bits per unit datum
1453 @type required: restricted str or None
1454 @param required: If not None, then the value must be set to this.
1455 @type arraylen: int >= 1
1456 @param arraylen: many ints will there be? FIX: handle variable
1457 @type unavailable: restricted str or None
1458 @param unavailable: the default value to use if none given (if not None)
1459 @type bv: str
1460 @param bv: BitVector containing the incoming data
1461 @type dataDict: str
1462 @param dataDict: dictionary in which to place the results
1463 @type decodeOnly: bool
1464 @param decodeOnly: Set to true to only get the code for decoding
1465 @rtype: int
1466 @return: index one past the end of where this read
1467 '''
1468 assert type=='aisstr6'
1469 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1470 if None==arraylen: arraylen=1
1471 end = startindex+int(numbits)*int(arraylen)
1472 assert arraylen >= 1
1473 assert numbits>=1
1474
1475 if None != required:
1476 float(required)
1477 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1478 o.write(required)
1479 if not decodeOnly: o.write('\n')
1480 return end
1481
1482 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1483 o.write('aisstring.decode('+bv+'['+str(startindex)+':'+str(end)+'])')
1484 if not decodeOnly: o.write('\n')
1485
1486 return end
1487
1488
1489 -def decodeDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1490 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1491 '''
1492 Build the decoder for signed decimal variables
1493
1494 @type o: file like obj
1495 @param o: where write the code
1496 @type name: str
1497 @param name: field name
1498 @type type: str
1499 @param type: 'decimal'
1500 @type startindex: int
1501 @param startindex: bit that begins the int(s)
1502 @type numbits: int >= 1
1503 @param numbits: How many bits per unit datum
1504 @type required: Decimal or None
1505 @param required: If not None, then the value must be set to this.
1506 @type arraylen: int >= 1
1507 @param arraylen: many ints will there be? FIX: handle variable
1508 @type unavailable: Decimal or None
1509 @param unavailable: the default value to use if none given (if not None)
1510 @type bv: str
1511 @param bv: BitVector containing the incoming data
1512 @type dataDict: str
1513 @param dataDict: dictionary in which to place the results
1514 @type decodeOnly: bool
1515 @param decodeOnly: Set to true to only get the code for decoding
1516 @rtype: int
1517 @return: index one past the end of where this read
1518 '''
1519 assert type=='decimal'
1520 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1521 if None==arraylen: arraylen=1
1522 end = startindex+int(numbits)*int(arraylen)
1523 assert arraylen == 1
1524 assert numbits>=1 and numbits <= 32
1525
1526 if None == scale: scale='1'
1527
1528 if None != required:
1529 Decimal(required)
1530 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1531 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')')
1532 if not decodeOnly: o.write('\n')
1533 return end
1534
1535 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1536 o.write('Decimal(binary.signedIntFromBV('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')')
1537 if not decodeOnly: o.write('\n')
1538
1539 return end
1540
1541
1542
1543
1544 -def decodeUDecimal(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1545 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1546 '''
1547 Build the decoder for unsigned decimal variables
1548
1549 @type o: file like obj
1550 @param o: where write the code
1551 @type name: str
1552 @param name: field name
1553 @type type: str
1554 @param type: 'udecimal'
1555 @type startindex: int
1556 @param startindex: bit that begins the int(s)
1557 @type numbits: int >= 1
1558 @param numbits: How many bits per unit datum
1559 @type required: Decimal or None
1560 @param required: If not None, then the value must be set to this.
1561 @type arraylen: int >= 1
1562 @param arraylen: many ints will there be? FIX: handle variable
1563 @type unavailable: Decimal or None
1564 @param unavailable: the default value to use if none given (if not None)
1565 @type bv: str
1566 @param bv: BitVector containing the incoming data
1567 @type dataDict: str
1568 @param dataDict: dictionary in which to place the results
1569 @type decodeOnly: bool
1570 @param decodeOnly: Set to true to only get the code for decoding
1571 @rtype: int
1572 @return: index one past the end of where this read
1573 '''
1574 assert type=='udecimal'
1575 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1576 if None==arraylen: arraylen=1
1577 end = startindex+int(numbits)*int(arraylen)
1578 assert arraylen == 1
1579 assert numbits>=1 and numbits <= 32
1580
1581 if None == scale: scale='1'
1582
1583 if None != required:
1584 assert (Decimal(required)>=0.)
1585 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1586 o.write(str(Decimal(required))+'/Decimal(\''+scale+'\')')
1587 if not decodeOnly: o.write('\n')
1588 return end
1589
1590 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1591 o.write('Decimal(int('+bv+'['+str(startindex)+':'+str(end)+']))/Decimal(\''+scale+'\')')
1592 if not decodeOnly: o.write('\n')
1593
1594 return end
1595
1596
1597 -def decodeBinary(o,name,type,startindex,numbits,required=None,arraylen=1,unavailable=None,
1598 bv='bv',dataDict='r',verbose=False,scale=None, decodeOnly=False):
1599 '''
1600 Build the decoder for unsigned decimal variables
1601
1602 @type o: file like obj
1603 @param o: where write the code
1604 @type name: str
1605 @param name: field name
1606 @type type: str
1607 @param type: 'udecimal'
1608 @type startindex: int
1609 @param startindex: bit that begins the int(s)
1610 @type numbits: int >= 1
1611 @param numbits: How many bits per unit datum. If -1, then read to the end of the message
1612 @type required: Decimal or None
1613 @param required: If not None, then the value must be set to this.
1614 @type arraylen: int >= 1
1615 @param arraylen: many ints will there be? FIX: handle variable
1616 @type unavailable: Decimal or None
1617 @param unavailable: the default value to use if none given (if not None)
1618 @type bv: str
1619 @param bv: BitVector containing the incoming data
1620 @type dataDict: str
1621 @param dataDict: dictionary in which to place the results
1622 @type decodeOnly: bool
1623 @param decodeOnly: Set to true to only get the code for decoding
1624 @rtype: int
1625 @return: index one past the end of where this read
1626 '''
1627 assert type=='binary'
1628 if verbose: print type,'decode',name,': unvail=',unavailable,' numbits:',numbits, ' startindex=',startindex
1629 if None==arraylen: arraylen=1
1630 end = startindex+int(numbits)*int(arraylen)
1631 assert arraylen == 1
1632 assert (numbits>=1 and numbits <= 1024) or -1==numbits
1633
1634
1635
1636 if not decodeOnly: o.write('\t'+dataDict+'[\''+name+'\']=')
1637 o.write(bv+'['+str(startindex)+':')
1638 if int(numbits) != -1: o.write(str(end))
1639 o.write(']')
1640 if not decodeOnly: o.write('\n')
1641
1642 return end
1643
1644
1645
1646
1647
1648
1649
1650
1651
1653 '''Scrape the testvalues to make a basic param
1654
1655 @bug: FIX: make this create a dictionary that sits in the overall namespace and spit out deep copies?
1656 '''
1657 name = msgET.attrib['name']
1658
1659 if prefixName: o.write('def '+name+'TestParams():\n')
1660 else: o.write('def testParams():\n')
1661 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")
1662 o.write('\tparams = {}\n')
1663 for field in msgET.xpath('field'):
1664 name = field.attrib['name']
1665 type = field.attrib['type']
1666 if verbose: print 'buildTestParamFunc ...',name,type
1667 val = None
1668 if hasSubTag(field,'testvalue') and hasSubTag(field,'required'):
1669 print 'ERROR: can not have both test value and required tags in the same field'
1670 assert(False)
1671 if hasSubTag(field,'testvalue'):
1672 val = field.xpath('testvalue')[0].text
1673 else:
1674 if not hasSubTag(field,'required'):
1675 sys.exit("ERROR: missing required or testvalue for field: "+name)
1676 val = field.xpath('required')[0].text
1677 if verbose: print 'buildTestParamFunc for field '+name+' ...',type,val
1678 o.write('\tparams[\''+name+'\'] = ')
1679 if type=='bool':
1680 if val=='1' or val.lower=='true': val = 'True'
1681 else: val = 'False'
1682 o.write(val)
1683 elif type in ('uint','int','float'):
1684 o.write(val)
1685 elif type in ('decimal','udecimal'):
1686 o.write('Decimal(\''+val+'\')')
1687
1688 elif type in ('aisstr6'):
1689 o.write('\''+val+'\'')
1690 elif type in ('binary'):
1691 o.write('BitVector(bitstring=\''+val+'\')')
1692 else:
1693 print 'ERROR: type not handled ...',type,' (found in the ',name,' field). Time to buy more coffee'
1694 suggestType(name,type)
1695 assert(False)
1696
1697 o.write('\n')
1698
1699
1700 o.write('\n\treturn params\n\n')
1701
1703 '''
1704 Write the unittests for a message
1705
1706 @param o: open file where resulting code will be written
1707 @param msgET: Element Tree starting at a message node
1708 '''
1709 assert(msgET.tag=='message')
1710 name = msgET.attrib['name']
1711
1712 buildTestParamFunc(o,msgET, prefixName=prefixName)
1713
1714 o.write('class Test'+name+'(unittest.TestCase):\n')
1715 o.write("\t'''Use testvalue tag text from each type to build test case the "+name+" message'''\n")
1716 o.write('\tdef testEncodeDecode(self):\n\n')
1717 if prefixName:
1718 o.write('\t\tparams = '+name+'TestParams()\n')
1719 o.write('\t\tbits = '+name+'Encode(params)\n')
1720 o.write('\t\tr = '+name+'Decode(bits)\n\n')
1721 else:
1722 o.write('\t\tparams = testParams()\n')
1723 o.write('\t\tbits = encode(params)\n')
1724 o.write('\t\tr = decode(bits)\n\n')
1725
1726 o.write('\t\t# Check that each parameter came through ok.\n')
1727 for field in msgET.xpath('field'):
1728 name = field.attrib['name']
1729 type = field.attrib['type']
1730 if type in ('bool','uint','int','aisstr6','binary'):
1731 o.write('\t\tself.failUnlessEqual(r[\''+name+'\'],params[\''+name+'\'])\n')
1732 else:
1733
1734
1735 places = '3'
1736 if hasSubTag(field,'decimalplaces'): places = field.xpath('decimalplaces')[0].text
1737 o.write('\t\tself.failUnlessAlmostEqual(r[\''+name+'\'],params[\''+name+'\'],'+places+')\n')
1738
1739
1740
1741 -def buildEncode(o,msgET, verbose=False, prefixName=False):
1742 '''
1743 Write the encoder/decoder for a message
1744
1745 http://jaynes.colorado.edu/PythonIdioms.html
1746
1747 @param o: open file where resulting code will be written
1748 @param msgET: Element Tree starting at a message node
1749 @todo: handle ais message 20 optional. very troublesome
1750 '''
1751 assert(msgET.tag=='message')
1752 name = msgET.attrib['name']
1753
1754 print 'Generating encoder for',name
1755 funcName = 'encode'
1756 if prefixName: funcName = name+'Encode'
1757 o.write('def '+funcName+'(params, validate=False):\n')
1758
1759
1760
1761 o.write("\t'''Create a "+name+" binary message payload to pack into an AIS Msg "+name+".\n\n")
1762 o.write('\tFields in params:\n')
1763 for field in msgET.xpath('field'):
1764 if verbose: print field.tag,field.attrib['name']
1765 desc = field[0].text.replace('\n',' ')
1766 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)
1767 if len(field.xpath("required")) == 1:
1768 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")')
1769
1770 o.write('\n')
1771 o.write('\t@param params: Dictionary of field names/values. Throws a ValueError exception if required is missing\n')
1772 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n')
1773
1774 o.write("\t@rtype: BitVector\n")
1775 o.write("\t@return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8\n")
1776 o.write("\t@note: The returned bits may not be 6 bit aligned. It is up to you to pad out the bits.\n")
1777 o.write("\t'''\n\n")
1778
1779
1780
1781
1782 o.write('\tbvList = []\n')
1783
1784 if verbose: print 'number of fields = ', len(msgET.xpath('field'))
1785
1786 dynamicArrays = False
1787
1788 for field in msgET.xpath('field'):
1789 name = field.attrib['name']
1790 type = field.attrib['type']
1791 numbits = int(field.attrib['numberofbits'])
1792 required = None;
1793 if hasSubTag(field,'required'):
1794 required = field.xpath('required')[0].text
1795
1796 unavailable=None;
1797 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
1798 arraylen=1
1799 if 'arraylength' in field.attrib:
1800 arraylen=int(field.attrib['arraylength'])
1801 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
1802 else:
1803 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')'
1804
1805 if type=='bool' : encodeBool (o,name,type,numbits,required,arraylen,unavailable)
1806 elif type=='uint' : encodeUInt (o,name,type,numbits,required,arraylen,unavailable)
1807 elif type=='int' : encodeInt (o,name,type,numbits,required,arraylen,unavailable)
1808 elif type=='float' : encodeFloat (o,name,type,numbits,required,arraylen,unavailable)
1809 elif type=='aisstr6': encodeAisstr6(o,name,type,numbits,required,arraylen,unavailable)
1810 elif type=='decimal':
1811 scale = None
1812 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
1813 encodeDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale)
1814 elif type=='udecimal':
1815 scale = None
1816 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
1817 encodeUDecimal(o,name,type,numbits,required,arraylen,unavailable,scale=scale)
1818 elif type=='binary': encodeBinary(o,name,type,numbits,required,arraylen,unavailable)
1819 else:
1820 print 'WARNING: In buildEncode - Unhandled field type for',name,'...',type
1821 suggestType (name,type)
1822 assert False
1823
1824
1825
1826
1827
1828
1829 o.write('\n\treturn binary.joinBV(bvList)\n\n')
1830
1831
1832
1833
1834
1835
1836
1838
1839 '''
1840 Write the decoder for a message
1841
1842 @param o: open file where resulting code will be written
1843 @type msgET: elementtree
1844 @param prefixName: if True, put the name of the message on the functions.
1845 @param msgET: Element Tree starting at a message node
1846 @return: None
1847
1848 @todo: FIX: doc strings for each decode!
1849 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload
1850 @todo: May want to take a dictionary of already decoded fields to speed things that need prior info
1851 for things like variable length arrays
1852 '''
1853
1854 assert(msgET.tag=='message')
1855 name = msgET.attrib['name']
1856
1857 print 'Generating partial decode functions for',name
1858
1859 baseName = name+'Decode'
1860 if not prefixName: baseName = 'decode'
1861
1862 startindex = 0
1863
1864 for field in msgET.xpath('field'):
1865 name = field.attrib['name']
1866 type = field.attrib['type']
1867
1868 o.write('def '+baseName+name+'(bv, validate=False):\n')
1869
1870
1871 o.write('\treturn ')
1872
1873 numbits = int(field.attrib['numberofbits'])
1874 required = None;
1875 if hasSubTag(field,'required'):
1876 required = field.xpath('required')[0].text
1877 unavailable=None;
1878 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
1879 arraylen=1
1880 if 'arraylength' in field.attrib:
1881 arraylen=int(field.attrib['arraylength'])
1882 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
1883
1884 assert None!=startindex
1885 if verbose: print 'startindex',startindex
1886 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1887 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1888 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1889 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1890 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1891 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable,decodeOnly=True)
1892
1893 elif type=='decimal':
1894 scale = None
1895 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
1896 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True)
1897 elif type=='udecimal':
1898 scale = None
1899 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
1900 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale,decodeOnly=True)
1901
1902 else:
1903 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type
1904 suggestType (name,type)
1905 assert False
1906
1907
1908 o.write('\n\n')
1909
1910
1911
1912
1913
1914 -def buildDecode(o,msgET, verbose=False, prefixName=False):
1915 '''
1916 Write the decoder for a message
1917
1918 @param o: open file where resulting code will be written
1919 @type msgET: elementtree
1920 @param msgET: Element Tree starting at a message node
1921 @return: None
1922
1923 @todo: FIX: check for a dac,fid, or efid. If exists, then this is an AIS Msg 8 payload
1924 '''
1925
1926 assert(msgET.tag=='message')
1927 name = msgET.attrib['name']
1928
1929 print 'Generating decoder for',name
1930 funcName = 'decode'
1931 if prefixName: funcName = name+'Decode'
1932 o.write('def '+funcName+'(bv, validate=False):\n')
1933
1934
1935
1936 o.write("\t'''Unpack a "+name+" message \n\n")
1937 o.write('\tFields in params:\n')
1938 for field in msgET.xpath('field'):
1939 desc = field[0].text.replace('\n',' ')
1940 o.write('\t - '+field.attrib['name']+'('+field.attrib['type']+'): '+desc)
1941 if len(field.xpath("required")) == 1:
1942 o.write(' (field automatically set to "'+field.xpath("required")[0].text+'")')
1943
1944 o.write('\n')
1945 o.write('\t@type bv: BitVector\n')
1946 o.write('\t@param bv: Bits defining a message\n')
1947 o.write('\t@param validate: Set to true to cause checking to occur. Runs slower. FIX: not implemented.\n')
1948
1949 o.write("\t@rtype: dict\n")
1950 o.write("\t@return: params\n")
1951 o.write("\t'''\n\n")
1952
1953
1954 o.write('\t#Would be nice to check the bit count here..\n')
1955 o.write('\t#if validate:\n')
1956 o.write('\t#\tassert (len(bv)==FIX: SOME NUMBER)\n')
1957
1958
1959
1960
1961
1962 o.write('\tr = {}\n')
1963
1964
1965 if verbose: print 'number of fields = ', len(msgET.xpath('field'))
1966
1967 dynamicArrays = False
1968
1969 startindex = 0
1970
1971 for field in msgET.xpath('field'):
1972 name = field.attrib['name']
1973 type = field.attrib['type']
1974 numbits = int(field.attrib['numberofbits'])
1975 required = None;
1976 if hasSubTag(field,'required'):
1977 required = field.xpath('required')[0].text
1978
1979 unavailable=None;
1980 if hasSubTag(field,'unavailable'): unavailable = field.xpath('unavailable')[0].text
1981 arraylen=1
1982 if 'arraylength' in field.attrib:
1983 arraylen=int(field.attrib['arraylength'])
1984 if verbose: print 'Processing field ...',name,'('+type+' ',numbits,'*',arraylen,'=',numbits*arraylen,'bits )'
1985 else:
1986 if verbose: print 'Processing field ...',name,'(',type+' ',numbits,')'
1987
1988 assert None!=startindex
1989 if verbose: print 'startindex',startindex
1990
1991 if hasSubTag(field,'optional'):
1992
1993
1994 if msgET.attrib['aismsgnum']!='20':
1995 sys.exit('optional is only allow on AIS msg 20. Do NOT use it on new messages!!!')
1996 text = field.xpath('optional')[0].text
1997 if None==field.xpath('optional')[0].text:
1998
1999 pad = 8-(startindex%8)
2000 print 'not found: noreturn. pad=',pad
2001 assert(pad<=6)
2002 o.write('\tif len(bv)<='+str(startindex+pad)+': return r; # All fields below are optional\n')
2003 elif text != 'NoReturn':
2004 sys.exit ('ERROR: optional text must be NoReturn or empty')
2005
2006 if type=='bool' : startindex = decodeBool (o,name,type,startindex,numbits,required,arraylen,unavailable)
2007 elif type=='uint' : startindex = decodeUInt (o,name,type,startindex,numbits,required,arraylen,unavailable)
2008 elif type=='int' : startindex = decodeInt (o,name,type,startindex,numbits,required,arraylen,unavailable)
2009 elif type=='float' : startindex = decodeFloat (o,name,type,startindex,numbits,required,arraylen,unavailable)
2010 elif type=='aisstr6': startindex = decodeAisstr6(o,name,type,startindex,numbits,required,arraylen,unavailable)
2011 elif type=='binary' : startindex = decodeBinary (o,name,type,startindex,numbits,required,arraylen,unavailable)
2012
2013 elif type=='decimal':
2014 scale = None
2015 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2016
2017
2018 startindex = decodeDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale)
2019 elif type=='udecimal':
2020 scale = None
2021 if hasSubTag(field,'scale'): scale = field.xpath('scale')[0].text
2022
2023
2024 startindex = decodeUDecimal(o,name,type,startindex, numbits,required,arraylen,unavailable,scale=scale)
2025
2026 else:
2027 print 'WARNING: In buildDecode - Unhandled field type for',name,'...',type
2028 suggestType (name,type)
2029 assert False
2030
2031
2032 if None==startindex: print 'FIX: here. drat. treat me right'
2033 assert None!=startindex
2034
2035
2036 o.write('\treturn r\n\n')
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047 aisType2pythonType={
2048 'bool':'Bool',
2049 'uint':'int',
2050 'int':'int',
2051 'udecimal':'Decimal',
2052 'decimal':'Decimal',
2053 'aisstr6':'str',
2054 'binary':'str',
2055 'float':'Float',
2056 }
2057
2058 import copy
2059 aisType2optParseType=copy.deepcopy(aisType2pythonType)
2060 aisType2optParseType['bool']='int'
2061 aisType2optParseType['aisstr6']='string'
2062 aisType2optParseType['aisstr6']='string'
2063 aisType2optParseType['binary']='string'
2064 aisType2optParseType['udecimal']='string'
2065 aisType2optParseType['decimal']='string'
2066 aisType2optParseType['float']='float'
2067
2068
2070 '''Create a function that adds the options to a parse object'''
2071
2072 assert None != msgET
2073 assert msgET.tag=='message'
2074 msgName = msgET.attrib['name']
2075
2076 prefix=''
2077 if prefixName: prefix=msgName
2078
2079 funcName = 'addMsgOptions'
2080 if prefixName: funcName = msgName + 'AddMsgOptions'
2081 o.write('\ndef '+funcName+'(parser):')
2082
2083
2084
2085 o.write('''
2086 parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true',
2087 help='decode a "'''+msgName+'''" AIS message')
2088 parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true',
2089 help='encode a "'''+msgName+'''" AIS message')
2090 ''')
2091
2092
2093 for field in msgET.xpath('field'):
2094 name = field.attrib['name']
2095 fieldType = field.attrib['type']
2096 if hasSubTag(field,'required'):
2097 print 'skipping required field ...',name,fieldType
2098 continue
2099
2100 o.write('\tparser.add_option(\'--')
2101 if prefixName: o.write(msgName+'-')
2102 o.write(name+'-field\', dest=\''+name+'Field\'')
2103 if hasSubTag(field,'unavailable'):
2104 val = field.xpath('unavailable')[0].text
2105 o.write(',default=')
2106 if fieldType in ('uint','int','float'):
2107 o.write(val)
2108 elif fieldType in ('decimal','udecimal'):
2109 o.write('Decimal(\''+val+'\')')
2110 elif fieldType in ('aisstr6','bitvector'):
2111 o.write('\''+val+'\'')
2112 o.write(',metavar=\''+fieldType+'\',type=\''+aisType2optParseType[fieldType]+'\'')
2113 o.write('\n\t\t,help=\'Field parameter value [default: %default]\')\n')
2114
2115
2116
2117 -def buildMain(o, msgET, prefixName=False):
2118 assert None != msgET
2119 assert msgET.tag=='message'
2120 msgName = msgET.attrib['name']
2121
2122 prefix=''
2123 if prefixName: prefix=msgName
2124
2125 buildOptParse(o, msgET, prefixName)
2126
2127
2128 o.write('''
2129 ############################################################
2130 if __name__=='__main__':
2131
2132 from optparse import OptionParser
2133 parser = OptionParser(usage="%prog [options]",
2134 version="%prog "+__version__)
2135
2136 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
2137 help='run the documentation tests')
2138 parser.add_option('--unit-test',dest='unittest',default=False,action='store_true',
2139 help='run the unit tests')
2140 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
2141 help='Make the test output verbose')
2142
2143 # FIX: remove nmea from binary messages. No way to build the whole packet?
2144 # FIX: or build the surrounding msg 8 for a broadcast?
2145 typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message?
2146 parser.add_option('-t','--type',choices=typeChoices,type='choice',dest='ioType'
2147 ,default='nmeapayload'
2148 ,help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]')
2149
2150
2151 outputChoices = ('std','html','csv','sql' ''')
2152
2153 if haveLocatableMessage(msgET): o.write(', \'kml\',\'kml-full\'')
2154 o.write(''')
2155 parser.add_option('-T','--output-type',choices=outputChoices,type='choice',dest='outputType'
2156 ,default='std'
2157 ,help='What kind of string to output ('+', '.join(outputChoices)+') [default: %default]')
2158
2159 parser.add_option('-o','--output',dest='outputFileName',default=None,
2160 help='Name of the python file to write [default: stdout]')
2161
2162 parser.add_option('-f','--fields',dest='fieldList',default=None, action='append',
2163 choices=fieldList,
2164 help='Which fields to include in the output. Currently only for csv output [default: all]')
2165
2166 parser.add_option('-p','--print-csv-field-list',dest='printCsvfieldList',default=False,action='store_true',
2167 help='Print the field name for csv')
2168
2169 parser.add_option('-c','--sql-create',dest='sqlCreate',default=False,action='store_true',
2170 help='Print out an sql create command for the table.')
2171
2172 dbChoices = ('sqlite','postgres')
2173 parser.add_option('-D','--db-type',dest='dbType',default='postgres'
2174 ,choices=dbChoices,type='choice'
2175 ,help='What kind of database ('+', '.join(dbChoices)+') [default: %default]')
2176
2177 ''')
2178
2179 o.write('''\taddMsgOptions(parser)\n''')
2180
2181 o.write('''
2182 (options,args) = parser.parse_args()
2183 success=True
2184
2185 if options.doctest:
2186 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
2187 sys.argv= [sys.argv[0]]
2188 if options.verbose: sys.argv.append('-v')
2189 import doctest
2190 numfail,numtests=doctest.testmod()
2191 if numfail==0: print 'ok'
2192 else:
2193 print 'FAILED'
2194 success=False
2195
2196 if not success: sys.exit('Something Failed')
2197 del success # Hide success from epydoc
2198
2199 if options.unittest:
2200 sys.argv = [sys.argv[0]]
2201 if options.verbose: sys.argv.append('-v')
2202 unittest.main()
2203
2204 outfile = sys.stdout
2205 if None!=options.outputFileName:
2206 outfile = file(options.outputFileName,'w')
2207
2208 ''')
2209
2210
2211
2212
2213
2214
2215
2216 o.write('\n\tif options.doEncode:\n')
2217 o.write('\t\t# First make sure all non required options are specified\n')
2218 for field in msgET.xpath('field'):
2219 name = field.attrib['name']
2220 fieldType = field.attrib['type']
2221 varName = prefix+name+'Field'
2222 if not hasSubTag(field,'required'):
2223 o.write('\t\tif None==options.'+varName+': parser.error("missing value for '+varName+'")\n')
2224
2225
2226 o.write('\t\tmsgDict={\n')
2227 for field in msgET.xpath('field'):
2228 name = field.attrib['name']
2229 varName = prefix+name+'Field'
2230 if hasSubTag(field,'required'):
2231 o.write('\t\t\t\''+name+'\': \''+field.xpath('required')[0].text+'\',\n')
2232 else:
2233 o.write('\t\t\t\''+name+'\': options.'+varName+',\n')
2234 o.write('\t\t}\n')
2235
2236 encodeFunction = 'encode'
2237 if prefixName: encodeFunction = msgName+'Encode'
2238 o.write('''
2239 bits = '''+encodeFunction+'''(msgDict)
2240 if 'binary'==options.ioType: print str(bits)
2241 elif 'nmeapayload'==options.ioType:
2242 # FIX: figure out if this might be necessary at compile time
2243 print "bitLen",len(bits)
2244 bitLen=len(bits)
2245 if bitLen%6!=0:
2246 bits = bits + BitVector(size=(6 - (bitLen%6))) # Pad out to multiple of 6
2247 print "result:",binary.bitvectoais6(bits)[0]
2248
2249
2250 # FIX: Do not emit this option for the binary message payloads. Does not make sense.
2251 elif 'nmea'==options.ioType: sys.exit("FIX: need to implement this capability")
2252 else: sys.exit('ERROR: unknown ioType. Help!')
2253 ''')
2254
2255
2256
2257
2258
2259 decodeFunction = 'decode'
2260 printFields='printFields'
2261 if prefixName:
2262 decodeFunction = msgName+'Decode'
2263 printFields = msgName+'PrintFields'
2264 o.write('''
2265
2266 if options.sqlCreate:
2267 sqlCreateStr(outfile,options.fieldList,dbType=options.dbType)
2268
2269 if options.printCsvfieldList:
2270 # Make a csv separated list of fields that will be displayed for csv
2271 if None == options.fieldList: options.fieldList = fieldList
2272 import StringIO
2273 buf = StringIO.StringIO()
2274 for field in options.fieldList:
2275 buf.write(field+',')
2276 result = buf.getvalue()
2277 if result[-1] == ',': print result[:-1]
2278 else: print result
2279
2280 if options.doDecode:
2281 for msg in args:
2282 bv = None
2283
2284 if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'):
2285 # Found nmea
2286 # FIX: do checksum
2287 bv = binary.ais6tobitvec(msg.split(',')[5])
2288 else: # either binary or nmeapayload... expect mostly nmeapayloads
2289 # assumes that an all 0 and 1 string can not be a nmeapayload
2290 binaryMsg=True
2291 for c in msg:
2292 if c not in ('0','1'):
2293 binaryMsg=False
2294 break
2295 if binaryMsg:
2296 bv = BitVector(bitstring=msg)
2297 else: # nmeapayload
2298 bv = binary.ais6tobitvec(msg)
2299
2300 '''+printFields+'''('''+decodeFunction+'''(bv)
2301 ,out=outfile
2302 ,format=options.outputType
2303 ,fieldList=options.fieldList
2304 ,dbType=options.dbType
2305 )
2306
2307 ''')
2308
2309
2310
2311 if __name__=='__main__':
2312 from optparse import OptionParser
2313 parser = OptionParser(usage="%prog [options]",
2314 version="%prog "+__version__)
2315
2316 parser.add_option('-o','--output',dest='outputFileName',default=None,
2317 help='Name of the python file to write')
2318
2319
2320 parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None,
2321 help='XML definition file for the msg to use')
2322
2323
2324
2325
2326
2327 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
2328 help='run the documentation tests')
2329
2330 parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true',
2331 help='put the field name in front of all function names.'
2332 +' Allows multiple messages in one file')
2333
2334 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
2335 help='run the tests run in verbose mode')
2336
2337 (options,args) = parser.parse_args()
2338
2339 success=True
2340
2341 if options.doctest:
2342 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
2343 argvOrig = sys.argv
2344 sys.argv= [sys.argv[0]]
2345 if options.verbose: sys.argv.append('-v')
2346 import doctest
2347 numfail,numtests=doctest.testmod()
2348 if numfail==0: print 'ok'
2349 else:
2350 print 'FAILED'
2351 success=False
2352 sys.argv = argvOrig
2353 del argvOrig
2354 sys.exit()
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364 if None==options.xmlFileName:
2365 sys.exit('ERROR: must specify an xml definition file.')
2366 if None==options.outputFileName:
2367 sys.exit('ERROR: must specify an python file to write to.')
2368 generatePython(options.xmlFileName,options.outputFileName,prefixName=options.prefix, verbose=options.verbose)
2369
2370 print '\nrecommend running pychecker like this:'
2371 print ' pychecker -q',options.outputFileName
2372