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