#!/usr/bin/env python

"""Autogenerated python functions to serialize/deserialize binary messages.

Generated by: ../scripts/aisxmlbinmsg2py.py

Need to then wrap these functions with the outer AIS packet and then
convert the whole binary blob to a NMEA string.  Those functions are
not currently provided in this file.

serialize: python to ais binary
deserialize: ais binary to python

The generated code uses translators.py, binary.py, and aisstring.py
which should be packaged with the resulting files.


 TODO(schwehr):FIX: put in a description of the message here with fields and types.
"""
import sys
from decimal import Decimal
import unittest

from aisutils.BitVector import BitVector

from aisutils import aisstring
from aisutils import binary
from aisutils import sqlhelp
from aisutils import uscg

# FIX: check to see if these will be needed
TrueBV  = BitVector(bitstring="1")
"Why always rebuild the True bit?  This should speed things up a bunch"
FalseBV = BitVector(bitstring="0")
"Why always rebuild the False bit?  This should speed things up a bunch"


fieldList = (
    'MessageID',
    'RepeatIndicator',
    'UserID',
    'Spare',
    'dac',
    'fid',
    'month',
    'day',
    'hour',
    'min',
    'stationid',
    'waterlevel',
    'datum',
    'sigma',
    'source',
)

fieldListPostgres = (
    'MessageID',
    'RepeatIndicator',
    'UserID',
    'Spare',
    'dac',
    'fid',
    'month',
    'day',
    'hour',
    'min',
    'stationid',
    'waterlevel',
    'datum',
    'sigma',
    'source',
)

toPgFields = {
}
"""
Go to the Postgis field names from the straight field name
"""

fromPgFields = {
}
"""
Go from the Postgis field names to the straight field name
"""

pgTypes = {
}
"""
Lookup table for each postgis field name to get its type.
"""

def encode(params, validate=False):
    '''Create a waterlevel binary message payload to pack into an AIS Msg waterlevel.

    Fields in params:
      - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")
      - RepeatIndicator(uint): Indicated how many times a message has been repeated
      - UserID(uint): Unique ship identification number (MMSI)
      - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
      - dac(uint): Designated Area Code (field automatically set to "366")
      - fid(uint): Functional Identifier (field automatically set to "63")
      - month(uint): Time the measurement represents  month 1..12
      - day(uint): Time the measurement represents  day of the month 1..31
      - hour(uint): Time the measurement represents  UTC hours 0..23
      - min(uint): Time the measurement represents  minutes
      - stationid(aisstr6): Character identifier of the station.  Usually a number.
      - waterlevel(int): Water level in centimeters
      - datum(uint): What reference datum applies to the value
      - sigma(uint): Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?
      - source(uint): How the water level was derived
    @param params: Dictionary of field names/values.  Throws a ValueError exception if required is missing
    @param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.
    @rtype: BitVector
    @return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8
    @note: The returned bits may not be 6 bit aligned.  It is up to you to pad out the bits.
    '''

    bvList = []
    bvList.append(binary.setBitVectorSize(BitVector(intVal=8),6))
    if 'RepeatIndicator' in params:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=params['RepeatIndicator']),2))
    else:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=0),2))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['UserID']),30))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=0),2))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=366),10))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=63),6))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['month']),4))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['day']),5))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['hour']),5))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['min']),6))
    if 'stationid' in params:
        bvList.append(aisstring.encode(params['stationid'],42))
    else:
        bvList.append(aisstring.encode('@@@@@@@',42))
    if 'waterlevel' in params:
        bvList.append(binary.bvFromSignedInt(params['waterlevel'],16))
    else:
        bvList.append(binary.bvFromSignedInt(-32768,16))
    if 'datum' in params:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=params['datum']),5))
    else:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=31),5))
    if 'sigma' in params:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=params['sigma']),7))
    else:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=127),7))
    if 'source' in params:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=params['source']),3))
    else:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=0),3))

    return binary.joinBV(bvList)

def decode(bv, validate=False):
    '''Unpack a waterlevel message.

    Fields in params:
      - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")
      - RepeatIndicator(uint): Indicated how many times a message has been repeated
      - UserID(uint): Unique ship identification number (MMSI)
      - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
      - dac(uint): Designated Area Code (field automatically set to "366")
      - fid(uint): Functional Identifier (field automatically set to "63")
      - month(uint): Time the measurement represents  month 1..12
      - day(uint): Time the measurement represents  day of the month 1..31
      - hour(uint): Time the measurement represents  UTC hours 0..23
      - min(uint): Time the measurement represents  minutes
      - stationid(aisstr6): Character identifier of the station.  Usually a number.
      - waterlevel(int): Water level in centimeters
      - datum(uint): What reference datum applies to the value
      - sigma(uint): Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?
      - source(uint): How the water level was derived
    @type bv: BitVector
    @param bv: Bits defining a message
    @param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.
    @rtype: dict
    @return: params
    '''

    #Would be nice to check the bit count here..
    #if validate:
    #    assert (len(bv)==FIX: SOME NUMBER)
    r = {}
    r['MessageID']=8
    r['RepeatIndicator']=int(bv[6:8])
    r['UserID']=int(bv[8:38])
    r['Spare']=0
    r['dac']=366
    r['fid']=63
    r['month']=int(bv[56:60])
    r['day']=int(bv[60:65])
    r['hour']=int(bv[65:70])
    r['min']=int(bv[70:76])
    r['stationid']=aisstring.decode(bv[76:118])
    r['waterlevel']=binary.signedIntFromBV(bv[118:134])
    r['datum']=int(bv[134:139])
    r['sigma']=int(bv[139:146])
    r['source']=int(bv[146:149])
    return r

def decodeMessageID(bv, validate=False):
    return 8

def decodeRepeatIndicator(bv, validate=False):
    return int(bv[6:8])

def decodeUserID(bv, validate=False):
    return int(bv[8:38])

def decodeSpare(bv, validate=False):
    return 0

def decodedac(bv, validate=False):
    return 366

def decodefid(bv, validate=False):
    return 63

def decodemonth(bv, validate=False):
    return int(bv[56:60])

def decodeday(bv, validate=False):
    return int(bv[60:65])

def decodehour(bv, validate=False):
    return int(bv[65:70])

def decodemin(bv, validate=False):
    return int(bv[70:76])

def decodestationid(bv, validate=False):
    return aisstring.decode(bv[76:118])

def decodewaterlevel(bv, validate=False):
    return binary.signedIntFromBV(bv[118:134])

def decodedatum(bv, validate=False):
    return int(bv[134:139])

def decodesigma(bv, validate=False):
    return int(bv[139:146])

def decodesource(bv, validate=False):
    return int(bv[146:149])


def printHtml(params, out=sys.stdout):
        out.write("<h3>waterlevel</h3>\n")
        out.write("<table border=\"1\">\n")
        out.write("<tr bgcolor=\"orange\">\n")
        out.write("<th align=\"left\">Field Name</th>\n")
        out.write("<th align=\"left\">Type</th>\n")
        out.write("<th align=\"left\">Value</th>\n")
        out.write("<th align=\"left\">Value in Lookup Table</th>\n")
        out.write("<th align=\"left\">Units</th>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>MessageID</td>\n")
        out.write("<td>uint</td>\n")
        if 'MessageID' in params:
            out.write("    <td>"+str(params['MessageID'])+"</td>\n")
            out.write("    <td>"+str(params['MessageID'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>RepeatIndicator</td>\n")
        out.write("<td>uint</td>\n")
        if 'RepeatIndicator' in params:
            out.write("    <td>"+str(params['RepeatIndicator'])+"</td>\n")
            if str(params['RepeatIndicator']) in RepeatIndicatorDecodeLut:
                out.write("<td>"+RepeatIndicatorDecodeLut[str(params['RepeatIndicator'])]+"</td>")
            else:
                out.write("<td><i>Missing LUT entry</i></td>")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>UserID</td>\n")
        out.write("<td>uint</td>\n")
        if 'UserID' in params:
            out.write("    <td>"+str(params['UserID'])+"</td>\n")
            out.write("    <td>"+str(params['UserID'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>Spare</td>\n")
        out.write("<td>uint</td>\n")
        if 'Spare' in params:
            out.write("    <td>"+str(params['Spare'])+"</td>\n")
            out.write("    <td>"+str(params['Spare'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>dac</td>\n")
        out.write("<td>uint</td>\n")
        if 'dac' in params:
            out.write("    <td>"+str(params['dac'])+"</td>\n")
            out.write("    <td>"+str(params['dac'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>fid</td>\n")
        out.write("<td>uint</td>\n")
        if 'fid' in params:
            out.write("    <td>"+str(params['fid'])+"</td>\n")
            out.write("    <td>"+str(params['fid'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>month</td>\n")
        out.write("<td>uint</td>\n")
        if 'month' in params:
            out.write("    <td>"+str(params['month'])+"</td>\n")
            out.write("    <td>"+str(params['month'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>day</td>\n")
        out.write("<td>uint</td>\n")
        if 'day' in params:
            out.write("    <td>"+str(params['day'])+"</td>\n")
            out.write("    <td>"+str(params['day'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>hour</td>\n")
        out.write("<td>uint</td>\n")
        if 'hour' in params:
            out.write("    <td>"+str(params['hour'])+"</td>\n")
            out.write("    <td>"+str(params['hour'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>min</td>\n")
        out.write("<td>uint</td>\n")
        if 'min' in params:
            out.write("    <td>"+str(params['min'])+"</td>\n")
            out.write("    <td>"+str(params['min'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>stationid</td>\n")
        out.write("<td>aisstr6</td>\n")
        if 'stationid' in params:
            out.write("    <td>"+str(params['stationid'])+"</td>\n")
            out.write("    <td>"+str(params['stationid'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>waterlevel</td>\n")
        out.write("<td>int</td>\n")
        if 'waterlevel' in params:
            out.write("    <td>"+str(params['waterlevel'])+"</td>\n")
            out.write("    <td>"+str(params['waterlevel'])+"</td>\n")
        out.write("<td>cm</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>datum</td>\n")
        out.write("<td>uint</td>\n")
        if 'datum' in params:
            out.write("    <td>"+str(params['datum'])+"</td>\n")
            if str(params['datum']) in datumDecodeLut:
                out.write("<td>"+datumDecodeLut[str(params['datum'])]+"</td>")
            else:
                out.write("<td><i>Missing LUT entry</i></td>")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>sigma</td>\n")
        out.write("<td>uint</td>\n")
        if 'sigma' in params:
            out.write("    <td>"+str(params['sigma'])+"</td>\n")
            out.write("    <td>"+str(params['sigma'])+"</td>\n")
        out.write("<td>cm</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>source</td>\n")
        out.write("<td>uint</td>\n")
        if 'source' in params:
            out.write("    <td>"+str(params['source'])+"</td>\n")
            if str(params['source']) in sourceDecodeLut:
                out.write("<td>"+sourceDecodeLut[str(params['source'])]+"</td>")
            else:
                out.write("<td><i>Missing LUT entry</i></td>")
        out.write("</tr>\n")
        out.write("</table>\n")

def printFields(params, out=sys.stdout, format='std', fieldList=None, dbType='postgres'):
    '''Print a waterlevel message to stdout.

    Fields in params:
      - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")
      - RepeatIndicator(uint): Indicated how many times a message has been repeated
      - UserID(uint): Unique ship identification number (MMSI)
      - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
      - dac(uint): Designated Area Code (field automatically set to "366")
      - fid(uint): Functional Identifier (field automatically set to "63")
      - month(uint): Time the measurement represents  month 1..12
      - day(uint): Time the measurement represents  day of the month 1..31
      - hour(uint): Time the measurement represents  UTC hours 0..23
      - min(uint): Time the measurement represents  minutes
      - stationid(aisstr6): Character identifier of the station.  Usually a number.
      - waterlevel(int): Water level in centimeters
      - datum(uint): What reference datum applies to the value
      - sigma(uint): Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?
      - source(uint): How the water level was derived
    @param params: Dictionary of field names/values.
    @param out: File like object to write to.
    @rtype: stdout
    @return: text to out
    '''

    if 'std'==format:
        out.write("waterlevel:\n")
        if 'MessageID' in params: out.write("    MessageID:        "+str(params['MessageID'])+"\n")
        if 'RepeatIndicator' in params: out.write("    RepeatIndicator:  "+str(params['RepeatIndicator'])+"\n")
        if 'UserID' in params: out.write("    UserID:           "+str(params['UserID'])+"\n")
        if 'Spare' in params: out.write("    Spare:            "+str(params['Spare'])+"\n")
        if 'dac' in params: out.write("    dac:              "+str(params['dac'])+"\n")
        if 'fid' in params: out.write("    fid:              "+str(params['fid'])+"\n")
        if 'month' in params: out.write("    month:            "+str(params['month'])+"\n")
        if 'day' in params: out.write("    day:              "+str(params['day'])+"\n")
        if 'hour' in params: out.write("    hour:             "+str(params['hour'])+"\n")
        if 'min' in params: out.write("    min:              "+str(params['min'])+"\n")
        if 'stationid' in params: out.write("    stationid:        "+str(params['stationid'])+"\n")
        if 'waterlevel' in params: out.write("    waterlevel:       "+str(params['waterlevel'])+"\n")
        if 'datum' in params: out.write("    datum:            "+str(params['datum'])+"\n")
        if 'sigma' in params: out.write("    sigma:            "+str(params['sigma'])+"\n")
        if 'source' in params: out.write("    source:           "+str(params['source'])+"\n")
        elif 'csv'==format:
                if None == options.fieldList:
                        options.fieldList = fieldList
                needComma = False;
                for field in fieldList:
                        if needComma: out.write(',')
                        needComma = True
                        if field in params:
                                out.write(str(params[field]))
                        # else: leave it empty
                out.write("\n")
    elif 'html'==format:
        printHtml(params,out)
    elif 'sql'==format:
                sqlInsertStr(params,out,dbType=dbType)
    else:
        print "ERROR: unknown format:",format
        assert False

    return # Nothing to return

RepeatIndicatorEncodeLut = {
    'default':'0',
    'do not repeat any more':'3',
    } #RepeatIndicatorEncodeLut

RepeatIndicatorDecodeLut = {
    '0':'default',
    '3':'do not repeat any more',
    } # RepeatIndicatorEncodeLut

datumEncodeLut = {
    'MLLW':'0',
    'IGLD-85':'1',
    'WaterDepth':'2',
    'STND':'3',
    'MHW':'4',
    'MSL':'5',
    'NGVD':'6',
    'NAVD':'7',
    'WGS-84':'8',
    'LAT':'9',
    'Pool':'10',
    'Gauge':'11',
    'Unknown/Unavailable':'31',
    } #datumEncodeLut

datumDecodeLut = {
    '0':'MLLW',
    '1':'IGLD-85',
    '2':'WaterDepth',
    '3':'STND',
    '4':'MHW',
    '5':'MSL',
    '6':'NGVD',
    '7':'NAVD',
    '8':'WGS-84',
    '9':'LAT',
    '10':'Pool',
    '11':'Gauge',
    '31':'Unknown/Unavailable',
    } # datumEncodeLut

sourceEncodeLut = {
    'No data':'0',
    'Realtime sensor data':'1',
    'Raw realtime sensor data - no validation':'2',
    'Predicted water level - tidal data generated by using harmonic analysis':'3',
    'Forecast water level - tidal data generated by use of a hydrodynamic model':'4',
    'Reserved':'5',
    'Reserved':'6',
    'Reserved':'7',
    } #sourceEncodeLut

sourceDecodeLut = {
    '0':'No data',
    '1':'Realtime sensor data',
    '2':'Raw realtime sensor data - no validation',
    '3':'Predicted water level - tidal data generated by using harmonic analysis',
    '4':'Forecast water level - tidal data generated by use of a hydrodynamic model',
    '5':'Reserved',
    '6':'Reserved',
    '7':'Reserved',
    } # sourceEncodeLut

######################################################################
# SQL SUPPORT
######################################################################

dbTableName='waterlevel'
'Database table name'

def sqlCreateStr(outfile=sys.stdout, fields=None, extraFields=None
                ,addCoastGuardFields=True
                ,dbType='postgres'
                ):
        """
        Return the SQL CREATE command for this message type
        @param outfile: file like object to print to.
        @param fields: which fields to put in the create.  Defaults to all.
        @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
        @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
        @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
        @type addCoastGuardFields: bool
        @return: sql create string
        @rtype: str

        @see: sqlCreate
        """
        # FIX: should this sqlCreate be the same as in LaTeX (createFuncName) rather than hard coded?
        outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType)))

def sqlCreate(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'):
    """Return the sqlhelp object to create the table.

    @param fields: which fields to put in the create.  Defaults to all.
    @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
    @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
    @type addCoastGuardFields: bool
    @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
    @return: An object that can be used to generate a return
    @rtype: sqlhelp.create
    """
    if fields is None:
        fields = fieldList
    c = sqlhelp.create('waterlevel',dbType=dbType)
    c.addPrimaryKey()
    if 'MessageID' in fields: c.addInt ('MessageID')
    if 'RepeatIndicator' in fields: c.addInt ('RepeatIndicator')
    if 'UserID' in fields: c.addInt ('UserID')
    if 'Spare' in fields: c.addInt ('Spare')
    if 'dac' in fields: c.addInt ('dac')
    if 'fid' in fields: c.addInt ('fid')
    if 'month' in fields: c.addInt ('month')
    if 'day' in fields: c.addInt ('day')
    if 'hour' in fields: c.addInt ('hour')
    if 'min' in fields: c.addInt ('min')
    if 'stationid' in fields: c.addVarChar('stationid',7)
    if 'waterlevel' in fields: c.addInt ('waterlevel')
    if 'datum' in fields: c.addInt ('datum')
    if 'sigma' in fields: c.addInt ('sigma')
    if 'source' in fields: c.addInt ('source')

    if addCoastGuardFields:
        # c.addInt('cg_s_rssi')  # Relative signal strength indicator
        # c.addInt('cg_d_strength')  # dBm receive strength
        # c.addVarChar('cg_x',10)  # Idonno
        c.addInt('cg_t_arrival')  # Receive timestamp from the AIS equipment 'T'
        c.addInt('cg_s_slotnum')  # Slot received in
        c.addVarChar('cg_r',15)  # Receiver station ID  -  should usually be an MMSI, but sometimes is a string
        c.addInt('cg_sec')  # UTC seconds since the epoch

        c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream

    return c

def sqlInsertStr(params, outfile=sys.stdout, extraParams=None, dbType='postgres'):
        """
        Return the SQL INSERT command for this message type
        @param params: dictionary of values keyed by field name
        @param outfile: file like object to print to.
        @param extraParams: A sequence of tuples containing (name,sql type) for additional fields
        @return: sql create string
        @rtype: str

        @see: sqlCreate
        """
        outfile.write(str(sqlInsert(params,extraParams,dbType=dbType)))


def sqlInsert(params,extraParams=None,dbType='postgres'):
        """
        Give the SQL INSERT statement
        @param params: dict keyed by field name of values
        @param extraParams: any extra fields that you have created beyond the normal ais message fields
        @rtype: sqlhelp.insert
        @return: insert class instance
         TODO(schwehr):allow optional type checking of params?
        @warning: this will take invalid keys happily and do what???
        """

        i = sqlhelp.insert('waterlevel',dbType=dbType)

        if dbType=='postgres':
                finished = []
                for key in params:
                        if key in finished:
                                continue

                        if key not in toPgFields and key not in fromPgFields:
                                if type(params[key])==Decimal: i.add(key,float(params[key]))
                                else: i.add(key,params[key])
                        else:
                                if key in fromPgFields:
                                        val = params[key]
                                        # Had better be a WKT type like POINT(-88.1 30.321)
                                        i.addPostGIS(key,val)
                                        finished.append(key)
                                else:
                                        # Need to construct the type.
                                        pgName = toPgFields[key]
                                        #valStr='GeomFromText(\''+pgTypes[pgName]+'('
                                        valStr=pgTypes[pgName]+'('
                                        vals = []
                                        for nonPgKey in fromPgFields[pgName]:
                                                vals.append(str(params[nonPgKey]))
                                                finished.append(nonPgKey)
                                        valStr+=' '.join(vals)+')'
                                        i.addPostGIS(pgName,valStr)
        else:
                for key in params:
                        if type(params[key])==Decimal: i.add(key,float(params[key]))
                        else: i.add(key,params[key])

        if None != extraParams:
                for key in extraParams:
                        i.add(key,extraParams[key])

        return i

######################################################################
# LATEX SUPPORT
######################################################################

def latexDefinitionTable(outfile=sys.stdout
                ):
        """
        Return the LaTeX definition table for this message type
        @param outfile: file like object to print to.
        @type outfile: file obj
        @return: LaTeX table string via the outfile
        @rtype: str

        """
        o = outfile

        o.write("""
\\begin{table}%[htb]
\\centering
\\begin{tabular}{|l|c|l|}
\\hline
Parameter & Number of bits & Description
\\\\  \\hline\\hline
MessageID & 6 & AIS message number.  Must be 8 \\\\ \hline
RepeatIndicator & 2 & Indicated how many times a message has been repeated \\\\ \hline
UserID & 30 & Unique ship identification number (MMSI) \\\\ \hline
Spare & 2 & Reserved for definition by a regional authority. \\\\ \hline
dac & 10 & Designated Area Code \\\\ \hline
fid & 6 & Functional Identifier \\\\ \hline
month & 4 & Time the measurement represents  month 1..12 \\\\ \hline
day & 5 & Time the measurement represents  day of the month 1..31 \\\\ \hline
hour & 5 & Time the measurement represents  UTC hours 0..23 \\\\ \hline
min & 6 & Time the measurement represents  minutes \\\\ \hline
stationid & 42 & Character identifier of the station.  Usually a number. \\\\ \hline
waterlevel & 16 & Water level in centimeters \\\\ \hline
datum & 5 & What reference datum applies to the value \\\\ \hline
sigma & 7 & Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma? \\\\ \hline
source & 3 & How the water level was derived\\\\ \\hline \\hline
Total bits & 149 & Appears to take 1 slot with 19 pad bits to fill the last slot \\\\ \\hline
\\end{tabular}
\\caption{AIS message number 8: Water level report.  This is }
\\label{tab:waterlevel}
\\end{table}
""")

######################################################################
# Text Definition
######################################################################

def textDefinitionTable(outfile=sys.stdout ,delim='    '):
    """Return the text definition table for this message type

    @param outfile: file like object to print to.
    @type outfile: file obj
    @return: text table string via the outfile
    @rtype: str

    """
    o = outfile
    o.write('Parameter'+delim+'Number of bits'+delim+"""Description
MessageID"""+delim+'6'+delim+"""AIS message number.  Must be 8
RepeatIndicator"""+delim+'2'+delim+"""Indicated how many times a message has been repeated
UserID"""+delim+'30'+delim+"""Unique ship identification number (MMSI)
Spare"""+delim+'2'+delim+"""Reserved for definition by a regional authority.
dac"""+delim+'10'+delim+"""Designated Area Code
fid"""+delim+'6'+delim+"""Functional Identifier
month"""+delim+'4'+delim+"""Time the measurement represents  month 1..12
day"""+delim+'5'+delim+"""Time the measurement represents  day of the month 1..31
hour"""+delim+'5'+delim+"""Time the measurement represents  UTC hours 0..23
min"""+delim+'6'+delim+"""Time the measurement represents  minutes
stationid"""+delim+'42'+delim+"""Character identifier of the station.  Usually a number.
waterlevel"""+delim+'16'+delim+"""Water level in centimeters
datum"""+delim+'5'+delim+"""What reference datum applies to the value
sigma"""+delim+'7'+delim+"""Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?
source"""+delim+'3'+delim+"""How the water level was derived
Total bits"""+delim+"""149"""+delim+"""Appears to take 1 slot with 19 pad bits to fill the last slot""")


######################################################################
# UNIT TESTING
######################################################################
def testParams():
    '''Return a params file base on the testvalue tags.
    @rtype: dict
    @return: params based on testvalue tags
    '''
    params = {}
    params['MessageID'] = 8
    params['RepeatIndicator'] = 1
    params['UserID'] = 1193046
    params['Spare'] = 0
    params['dac'] = 366
    params['fid'] = 63
    params['month'] = 2
    params['day'] = 28
    params['hour'] = 23
    params['min'] = 45
    params['stationid'] = 'A234567'
    params['waterlevel'] = -97
    params['datum'] = 0
    params['sigma'] = 97
    params['source'] = 2

    return params

class Testwaterlevel(unittest.TestCase):
    '''Use testvalue tag text from each type to build test case the waterlevel message'''
    def testEncodeDecode(self):

        params = testParams()
        bits   = encode(params)
        r      = decode(bits)

        # Check that each parameter came through ok.
        self.failUnlessEqual(r['MessageID'],params['MessageID'])
        self.failUnlessEqual(r['RepeatIndicator'],params['RepeatIndicator'])
        self.failUnlessEqual(r['UserID'],params['UserID'])
        self.failUnlessEqual(r['Spare'],params['Spare'])
        self.failUnlessEqual(r['dac'],params['dac'])
        self.failUnlessEqual(r['fid'],params['fid'])
        self.failUnlessEqual(r['month'],params['month'])
        self.failUnlessEqual(r['day'],params['day'])
        self.failUnlessEqual(r['hour'],params['hour'])
        self.failUnlessEqual(r['min'],params['min'])
        self.failUnlessEqual(r['stationid'],params['stationid'])
        self.failUnlessEqual(r['waterlevel'],params['waterlevel'])
        self.failUnlessEqual(r['datum'],params['datum'])
        self.failUnlessEqual(r['sigma'],params['sigma'])
        self.failUnlessEqual(r['source'],params['source'])

def addMsgOptions(parser):
    parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true',
                help='decode a "waterlevel" AIS message')
    parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true',
                help='encode a "waterlevel" AIS message')
    parser.add_option('--RepeatIndicator-field', dest='RepeatIndicatorField',default=0,metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--UserID-field', dest='UserIDField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--month-field', dest='monthField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--day-field', dest='dayField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--hour-field', dest='hourField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--min-field', dest='minField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--stationid-field', dest='stationidField',default='@@@@@@@',metavar='aisstr6',type='string'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--waterlevel-field', dest='waterlevelField',default=-32768,metavar='int',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--datum-field', dest='datumField',default=31,metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--sigma-field', dest='sigmaField',default=127,metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--source-field', dest='sourceField',default=0,metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')

def main():
    from optparse import OptionParser
    parser = OptionParser(usage="%prog [options]")

    parser.add_option('--unit-test',dest='unittest',default=False,action='store_true',
        help='run the unit tests')
    parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
        help='Make the test output verbose')

    # FIX: remove nmea from binary messages.  No way to build the whole packet?
    # FIX: or build the surrounding msg 8 for a broadcast?
    typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message?
    parser.add_option('-t', '--type', choices=typeChoices, type='choice',
        dest='ioType', default='nmeapayload',
        help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]')


    outputChoices = ('std','html','csv','sql' )
    parser.add_option('-T', '--output-type', choices=outputChoices,
        type='choice', dest='outputType', default='std',
        help='What kind of string to output ('+', '.join(outputChoices)+') '
        '[default: %default]')

    parser.add_option('-o','--output',dest='outputFileName',default=None,
        help='Name of the python file to write [default: stdout]')

    parser.add_option('-f', '--fields', dest='fieldList', default=None,
        action='append', choices=fieldList,
        help='Which fields to include in the output.  Currently only for csv '
        'output [default: all]')

    parser.add_option('-p', '--print-csv-field-list', dest='printCsvfieldList',
        default=False,action='store_true',
        help='Print the field name for csv')

    parser.add_option('-c', '--sql-create', dest='sqlCreate', default=False,
        action='store_true',
        help='Print out an sql create command for the table.')

    parser.add_option('--latex-table', dest='latexDefinitionTable',
        default=False,action='store_true',
        help='Print a LaTeX table of the type')

    parser.add_option('--text-table', dest='textDefinitionTable', default=False,
        action='store_true',
        help='Print delimited table of the type (for Word table importing)')

    parser.add_option('--delimt-text-table', dest='delimTextDefinitionTable',
        default='    ',
        help='Delimiter for text table [default: \'%default\'] '
        '(for Word table importing)')

    dbChoices = ('sqlite','postgres')
    parser.add_option('-D', '--db-type', dest='dbType', default='postgres',
        choices=dbChoices,type='choice',
        help='What kind of database ('+', '.join(dbChoices)+') '
        '[default: %default]')

    addMsgOptions(parser)

    options, args = parser.parse_args()

    if options.unittest:
            sys.argv = [sys.argv[0]]
            if options.verbose: sys.argv.append('-v')
            unittest.main()

    outfile = sys.stdout
    if None!=options.outputFileName:
            outfile = file(options.outputFileName,'w')


    if options.doEncode:
        # Make sure all non required options are specified.
        if None==options.RepeatIndicatorField: parser.error("missing value for RepeatIndicatorField")
        if None==options.UserIDField: parser.error("missing value for UserIDField")
        if None==options.monthField: parser.error("missing value for monthField")
        if None==options.dayField: parser.error("missing value for dayField")
        if None==options.hourField: parser.error("missing value for hourField")
        if None==options.minField: parser.error("missing value for minField")
        if None==options.stationidField: parser.error("missing value for stationidField")
        if None==options.waterlevelField: parser.error("missing value for waterlevelField")
        if None==options.datumField: parser.error("missing value for datumField")
        if None==options.sigmaField: parser.error("missing value for sigmaField")
        if None==options.sourceField: parser.error("missing value for sourceField")
    msgDict = {
        'MessageID': '8',
        'RepeatIndicator': options.RepeatIndicatorField,
        'UserID': options.UserIDField,
        'Spare': '0',
        'dac': '366',
        'fid': '63',
        'month': options.monthField,
        'day': options.dayField,
        'hour': options.hourField,
        'min': options.minField,
        'stationid': options.stationidField,
        'waterlevel': options.waterlevelField,
        'datum': options.datumField,
        'sigma': options.sigmaField,
        'source': options.sourceField,
    }

    bits = encode(msgDict)
    if 'binary' == options.ioType:
        print str(bits)
    elif 'nmeapayload'==options.ioType:
        # FIX: figure out if this might be necessary at compile time
        bitLen=len(bits)
        if bitLen % 6 != 0:
            bits = bits + BitVector(size=(6 - (bitLen%6)))  # Pad out to multiple of 6
        print binary.bitvectoais6(bits)[0]

    # FIX: Do not emit this option for the binary message payloads.  Does not make sense.
    elif 'nmea' == options.ioType:
        nmea = uscg.create_nmea(bits)
        print nmea
    else:
        sys.exit('ERROR: unknown ioType.  Help!')


        if options.sqlCreate:
                sqlCreateStr(outfile,options.fieldList,dbType=options.dbType)

        if options.latexDefinitionTable:
                latexDefinitionTable(outfile)

        # For conversion to word tables
        if options.textDefinitionTable:
                textDefinitionTable(outfile,options.delimTextDefinitionTable)

        if options.printCsvfieldList:
                # Make a csv separated list of fields that will be displayed for csv
                if None == options.fieldList: options.fieldList = fieldList
                import StringIO
                buf = StringIO.StringIO()
                for field in options.fieldList:
                        buf.write(field+',')
                result = buf.getvalue()
                if result[-1] == ',': print result[:-1]
                else: print result

        if options.doDecode:
                if len(args)==0: args = sys.stdin
                for msg in args:
                        bv = None

                        if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'):
                                # Found nmea
                                # FIX: do checksum
                                bv = binary.ais6tobitvec(msg.split(',')[5])
                        else: # either binary or nmeapayload... expect mostly nmeapayloads
                                # assumes that an all 0 and 1 string can not be a nmeapayload
                                binaryMsg=True
                                for c in msg:
                                        if c not in ('0','1'):
                                                binaryMsg=False
                                                break
                                if binaryMsg:
                                        bv = BitVector(bitstring=msg)
                                else: # nmeapayload
                                        bv = binary.ais6tobitvec(msg)

                        printFields(decode(bv)
                                    ,out=outfile
                                    ,format=options.outputType
                                    ,fieldList=options.fieldList
                                    ,dbType=options.dbType
                                    )

############################################################
if __name__=='__main__':
    main()

#!/usr/bin/env python

"""Autogenerated python functions to serialize/deserialize binary messages.

Generated by: ../scripts/aisxmlbinmsg2py.py

Need to then wrap these functions with the outer AIS packet and then
convert the whole binary blob to a NMEA string.  Those functions are
not currently provided in this file.

serialize: python to ais binary
deserialize: ais binary to python

The generated code uses translators.py, binary.py, and aisstring.py
which should be packaged with the resulting files.


 TODO(schwehr):FIX: put in a description of the message here with fields and types.
"""
import sys
from decimal import Decimal
import unittest

from aisutils.BitVector import BitVector

from aisutils import aisstring
from aisutils import binary
from aisutils import sqlhelp
from aisutils import uscg

# FIX: check to see if these will be needed
TrueBV  = BitVector(bitstring="1")
"Why always rebuild the True bit?  This should speed things up a bunch"
FalseBV = BitVector(bitstring="0")
"Why always rebuild the False bit?  This should speed things up a bunch"


fieldList = (
    'MessageID',
    'RepeatIndicator',
    'UserID',
    'Spare',
    'dac',
    'fid',
    'month',
    'day',
    'hour',
    'min',
    'stationid',
    'waterlevel',
    'datum',
    'sigma',
    'source',
)

fieldListPostgres = (
    'MessageID',
    'RepeatIndicator',
    'UserID',
    'Spare',
    'dac',
    'fid',
    'month',
    'day',
    'hour',
    'min',
    'stationid',
    'waterlevel',
    'datum',
    'sigma',
    'source',
)

toPgFields = {
}
"""
Go to the Postgis field names from the straight field name
"""

fromPgFields = {
}
"""
Go from the Postgis field names to the straight field name
"""

pgTypes = {
}
"""
Lookup table for each postgis field name to get its type.
"""

def encode(params, validate=False):
    '''Create a waterlevel binary message payload to pack into an AIS Msg waterlevel.

    Fields in params:
      - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")
      - RepeatIndicator(uint): Indicated how many times a message has been repeated
      - UserID(uint): Unique ship identification number (MMSI)
      - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
      - dac(uint): Designated Area Code (field automatically set to "366")
      - fid(uint): Functional Identifier (field automatically set to "63")
      - month(uint): Time the measurement represents  month 1..12
      - day(uint): Time the measurement represents  day of the month 1..31
      - hour(uint): Time the measurement represents  UTC hours 0..23
      - min(uint): Time the measurement represents  minutes
      - stationid(aisstr6): Character identifier of the station.  Usually a number.
      - waterlevel(int): Water level in centimeters
      - datum(uint): What reference datum applies to the value
      - sigma(uint): Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?
      - source(uint): How the water level was derived
    @param params: Dictionary of field names/values.  Throws a ValueError exception if required is missing
    @param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.
    @rtype: BitVector
    @return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8
    @note: The returned bits may not be 6 bit aligned.  It is up to you to pad out the bits.
    '''

    bvList = []
    bvList.append(binary.setBitVectorSize(BitVector(intVal=8),6))
    if 'RepeatIndicator' in params:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=params['RepeatIndicator']),2))
    else:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=0),2))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['UserID']),30))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=0),2))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=366),10))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=63),6))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['month']),4))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['day']),5))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['hour']),5))
    bvList.append(binary.setBitVectorSize(BitVector(intVal=params['min']),6))
    if 'stationid' in params:
        bvList.append(aisstring.encode(params['stationid'],42))
    else:
        bvList.append(aisstring.encode('@@@@@@@',42))
    if 'waterlevel' in params:
        bvList.append(binary.bvFromSignedInt(params['waterlevel'],16))
    else:
        bvList.append(binary.bvFromSignedInt(-32768,16))
    if 'datum' in params:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=params['datum']),5))
    else:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=31),5))
    if 'sigma' in params:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=params['sigma']),7))
    else:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=127),7))
    if 'source' in params:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=params['source']),3))
    else:
        bvList.append(binary.setBitVectorSize(BitVector(intVal=0),3))

    return binary.joinBV(bvList)

def decode(bv, validate=False):
    '''Unpack a waterlevel message.

    Fields in params:
      - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")
      - RepeatIndicator(uint): Indicated how many times a message has been repeated
      - UserID(uint): Unique ship identification number (MMSI)
      - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
      - dac(uint): Designated Area Code (field automatically set to "366")
      - fid(uint): Functional Identifier (field automatically set to "63")
      - month(uint): Time the measurement represents  month 1..12
      - day(uint): Time the measurement represents  day of the month 1..31
      - hour(uint): Time the measurement represents  UTC hours 0..23
      - min(uint): Time the measurement represents  minutes
      - stationid(aisstr6): Character identifier of the station.  Usually a number.
      - waterlevel(int): Water level in centimeters
      - datum(uint): What reference datum applies to the value
      - sigma(uint): Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?
      - source(uint): How the water level was derived
    @type bv: BitVector
    @param bv: Bits defining a message
    @param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.
    @rtype: dict
    @return: params
    '''

    #Would be nice to check the bit count here..
    #if validate:
    #    assert (len(bv)==FIX: SOME NUMBER)
    r = {}
    r['MessageID']=8
    r['RepeatIndicator']=int(bv[6:8])
    r['UserID']=int(bv[8:38])
    r['Spare']=0
    r['dac']=366
    r['fid']=63
    r['month']=int(bv[56:60])
    r['day']=int(bv[60:65])
    r['hour']=int(bv[65:70])
    r['min']=int(bv[70:76])
    r['stationid']=aisstring.decode(bv[76:118])
    r['waterlevel']=binary.signedIntFromBV(bv[118:134])
    r['datum']=int(bv[134:139])
    r['sigma']=int(bv[139:146])
    r['source']=int(bv[146:149])
    return r

def decodeMessageID(bv, validate=False):
    return 8

def decodeRepeatIndicator(bv, validate=False):
    return int(bv[6:8])

def decodeUserID(bv, validate=False):
    return int(bv[8:38])

def decodeSpare(bv, validate=False):
    return 0

def decodedac(bv, validate=False):
    return 366

def decodefid(bv, validate=False):
    return 63

def decodemonth(bv, validate=False):
    return int(bv[56:60])

def decodeday(bv, validate=False):
    return int(bv[60:65])

def decodehour(bv, validate=False):
    return int(bv[65:70])

def decodemin(bv, validate=False):
    return int(bv[70:76])

def decodestationid(bv, validate=False):
    return aisstring.decode(bv[76:118])

def decodewaterlevel(bv, validate=False):
    return binary.signedIntFromBV(bv[118:134])

def decodedatum(bv, validate=False):
    return int(bv[134:139])

def decodesigma(bv, validate=False):
    return int(bv[139:146])

def decodesource(bv, validate=False):
    return int(bv[146:149])


def printHtml(params, out=sys.stdout):
        out.write("<h3>waterlevel</h3>\n")
        out.write("<table border=\"1\">\n")
        out.write("<tr bgcolor=\"orange\">\n")
        out.write("<th align=\"left\">Field Name</th>\n")
        out.write("<th align=\"left\">Type</th>\n")
        out.write("<th align=\"left\">Value</th>\n")
        out.write("<th align=\"left\">Value in Lookup Table</th>\n")
        out.write("<th align=\"left\">Units</th>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>MessageID</td>\n")
        out.write("<td>uint</td>\n")
        if 'MessageID' in params:
            out.write("    <td>"+str(params['MessageID'])+"</td>\n")
            out.write("    <td>"+str(params['MessageID'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>RepeatIndicator</td>\n")
        out.write("<td>uint</td>\n")
        if 'RepeatIndicator' in params:
            out.write("    <td>"+str(params['RepeatIndicator'])+"</td>\n")
            if str(params['RepeatIndicator']) in RepeatIndicatorDecodeLut:
                out.write("<td>"+RepeatIndicatorDecodeLut[str(params['RepeatIndicator'])]+"</td>")
            else:
                out.write("<td><i>Missing LUT entry</i></td>")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>UserID</td>\n")
        out.write("<td>uint</td>\n")
        if 'UserID' in params:
            out.write("    <td>"+str(params['UserID'])+"</td>\n")
            out.write("    <td>"+str(params['UserID'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>Spare</td>\n")
        out.write("<td>uint</td>\n")
        if 'Spare' in params:
            out.write("    <td>"+str(params['Spare'])+"</td>\n")
            out.write("    <td>"+str(params['Spare'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>dac</td>\n")
        out.write("<td>uint</td>\n")
        if 'dac' in params:
            out.write("    <td>"+str(params['dac'])+"</td>\n")
            out.write("    <td>"+str(params['dac'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>fid</td>\n")
        out.write("<td>uint</td>\n")
        if 'fid' in params:
            out.write("    <td>"+str(params['fid'])+"</td>\n")
            out.write("    <td>"+str(params['fid'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>month</td>\n")
        out.write("<td>uint</td>\n")
        if 'month' in params:
            out.write("    <td>"+str(params['month'])+"</td>\n")
            out.write("    <td>"+str(params['month'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>day</td>\n")
        out.write("<td>uint</td>\n")
        if 'day' in params:
            out.write("    <td>"+str(params['day'])+"</td>\n")
            out.write("    <td>"+str(params['day'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>hour</td>\n")
        out.write("<td>uint</td>\n")
        if 'hour' in params:
            out.write("    <td>"+str(params['hour'])+"</td>\n")
            out.write("    <td>"+str(params['hour'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>min</td>\n")
        out.write("<td>uint</td>\n")
        if 'min' in params:
            out.write("    <td>"+str(params['min'])+"</td>\n")
            out.write("    <td>"+str(params['min'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>stationid</td>\n")
        out.write("<td>aisstr6</td>\n")
        if 'stationid' in params:
            out.write("    <td>"+str(params['stationid'])+"</td>\n")
            out.write("    <td>"+str(params['stationid'])+"</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>waterlevel</td>\n")
        out.write("<td>int</td>\n")
        if 'waterlevel' in params:
            out.write("    <td>"+str(params['waterlevel'])+"</td>\n")
            out.write("    <td>"+str(params['waterlevel'])+"</td>\n")
        out.write("<td>cm</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>datum</td>\n")
        out.write("<td>uint</td>\n")
        if 'datum' in params:
            out.write("    <td>"+str(params['datum'])+"</td>\n")
            if str(params['datum']) in datumDecodeLut:
                out.write("<td>"+datumDecodeLut[str(params['datum'])]+"</td>")
            else:
                out.write("<td><i>Missing LUT entry</i></td>")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>sigma</td>\n")
        out.write("<td>uint</td>\n")
        if 'sigma' in params:
            out.write("    <td>"+str(params['sigma'])+"</td>\n")
            out.write("    <td>"+str(params['sigma'])+"</td>\n")
        out.write("<td>cm</td>\n")
        out.write("</tr>\n")
        out.write("\n")
        out.write("<tr>\n")
        out.write("<td>source</td>\n")
        out.write("<td>uint</td>\n")
        if 'source' in params:
            out.write("    <td>"+str(params['source'])+"</td>\n")
            if str(params['source']) in sourceDecodeLut:
                out.write("<td>"+sourceDecodeLut[str(params['source'])]+"</td>")
            else:
                out.write("<td><i>Missing LUT entry</i></td>")
        out.write("</tr>\n")
        out.write("</table>\n")

def printFields(params, out=sys.stdout, format='std', fieldList=None, dbType='postgres'):
    '''Print a waterlevel message to stdout.

    Fields in params:
      - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")
      - RepeatIndicator(uint): Indicated how many times a message has been repeated
      - UserID(uint): Unique ship identification number (MMSI)
      - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")
      - dac(uint): Designated Area Code (field automatically set to "366")
      - fid(uint): Functional Identifier (field automatically set to "63")
      - month(uint): Time the measurement represents  month 1..12
      - day(uint): Time the measurement represents  day of the month 1..31
      - hour(uint): Time the measurement represents  UTC hours 0..23
      - min(uint): Time the measurement represents  minutes
      - stationid(aisstr6): Character identifier of the station.  Usually a number.
      - waterlevel(int): Water level in centimeters
      - datum(uint): What reference datum applies to the value
      - sigma(uint): Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?
      - source(uint): How the water level was derived
    @param params: Dictionary of field names/values.
    @param out: File like object to write to.
    @rtype: stdout
    @return: text to out
    '''

    if 'std'==format:
        out.write("waterlevel:\n")
        if 'MessageID' in params: out.write("    MessageID:        "+str(params['MessageID'])+"\n")
        if 'RepeatIndicator' in params: out.write("    RepeatIndicator:  "+str(params['RepeatIndicator'])+"\n")
        if 'UserID' in params: out.write("    UserID:           "+str(params['UserID'])+"\n")
        if 'Spare' in params: out.write("    Spare:            "+str(params['Spare'])+"\n")
        if 'dac' in params: out.write("    dac:              "+str(params['dac'])+"\n")
        if 'fid' in params: out.write("    fid:              "+str(params['fid'])+"\n")
        if 'month' in params: out.write("    month:            "+str(params['month'])+"\n")
        if 'day' in params: out.write("    day:              "+str(params['day'])+"\n")
        if 'hour' in params: out.write("    hour:             "+str(params['hour'])+"\n")
        if 'min' in params: out.write("    min:              "+str(params['min'])+"\n")
        if 'stationid' in params: out.write("    stationid:        "+str(params['stationid'])+"\n")
        if 'waterlevel' in params: out.write("    waterlevel:       "+str(params['waterlevel'])+"\n")
        if 'datum' in params: out.write("    datum:            "+str(params['datum'])+"\n")
        if 'sigma' in params: out.write("    sigma:            "+str(params['sigma'])+"\n")
        if 'source' in params: out.write("    source:           "+str(params['source'])+"\n")
        elif 'csv'==format:
                if None == options.fieldList:
                        options.fieldList = fieldList
                needComma = False;
                for field in fieldList:
                        if needComma: out.write(',')
                        needComma = True
                        if field in params:
                                out.write(str(params[field]))
                        # else: leave it empty
                out.write("\n")
    elif 'html'==format:
        printHtml(params,out)
    elif 'sql'==format:
                sqlInsertStr(params,out,dbType=dbType)
    else:
        print "ERROR: unknown format:",format
        assert False

    return # Nothing to return

RepeatIndicatorEncodeLut = {
    'default':'0',
    'do not repeat any more':'3',
    } #RepeatIndicatorEncodeLut

RepeatIndicatorDecodeLut = {
    '0':'default',
    '3':'do not repeat any more',
    } # RepeatIndicatorEncodeLut

datumEncodeLut = {
    'MLLW':'0',
    'IGLD-85':'1',
    'WaterDepth':'2',
    'STND':'3',
    'MHW':'4',
    'MSL':'5',
    'NGVD':'6',
    'NAVD':'7',
    'WGS-84':'8',
    'LAT':'9',
    'Pool':'10',
    'Gauge':'11',
    'Unknown/Unavailable':'31',
    } #datumEncodeLut

datumDecodeLut = {
    '0':'MLLW',
    '1':'IGLD-85',
    '2':'WaterDepth',
    '3':'STND',
    '4':'MHW',
    '5':'MSL',
    '6':'NGVD',
    '7':'NAVD',
    '8':'WGS-84',
    '9':'LAT',
    '10':'Pool',
    '11':'Gauge',
    '31':'Unknown/Unavailable',
    } # datumEncodeLut

sourceEncodeLut = {
    'No data':'0',
    'Realtime sensor data':'1',
    'Raw realtime sensor data - no validation':'2',
    'Predicted water level - tidal data generated by using harmonic analysis':'3',
    'Forecast water level - tidal data generated by use of a hydrodynamic model':'4',
    'Reserved':'5',
    'Reserved':'6',
    'Reserved':'7',
    } #sourceEncodeLut

sourceDecodeLut = {
    '0':'No data',
    '1':'Realtime sensor data',
    '2':'Raw realtime sensor data - no validation',
    '3':'Predicted water level - tidal data generated by using harmonic analysis',
    '4':'Forecast water level - tidal data generated by use of a hydrodynamic model',
    '5':'Reserved',
    '6':'Reserved',
    '7':'Reserved',
    } # sourceEncodeLut

######################################################################
# SQL SUPPORT
######################################################################

dbTableName='waterlevel'
'Database table name'

def sqlCreateStr(outfile=sys.stdout, fields=None, extraFields=None
                ,addCoastGuardFields=True
                ,dbType='postgres'
                ):
        """
        Return the SQL CREATE command for this message type
        @param outfile: file like object to print to.
        @param fields: which fields to put in the create.  Defaults to all.
        @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
        @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
        @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
        @type addCoastGuardFields: bool
        @return: sql create string
        @rtype: str

        @see: sqlCreate
        """
        # FIX: should this sqlCreate be the same as in LaTeX (createFuncName) rather than hard coded?
        outfile.write(str(sqlCreate(fields,extraFields,addCoastGuardFields,dbType=dbType)))

def sqlCreate(fields=None, extraFields=None, addCoastGuardFields=True, dbType='postgres'):
    """Return the sqlhelp object to create the table.

    @param fields: which fields to put in the create.  Defaults to all.
    @param extraFields: A sequence of tuples containing (name,sql type) for additional fields
    @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format
    @type addCoastGuardFields: bool
    @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')
    @return: An object that can be used to generate a return
    @rtype: sqlhelp.create
    """
    if fields is None:
        fields = fieldList
    c = sqlhelp.create('waterlevel',dbType=dbType)
    c.addPrimaryKey()
    if 'MessageID' in fields: c.addInt ('MessageID')
    if 'RepeatIndicator' in fields: c.addInt ('RepeatIndicator')
    if 'UserID' in fields: c.addInt ('UserID')
    if 'Spare' in fields: c.addInt ('Spare')
    if 'dac' in fields: c.addInt ('dac')
    if 'fid' in fields: c.addInt ('fid')
    if 'month' in fields: c.addInt ('month')
    if 'day' in fields: c.addInt ('day')
    if 'hour' in fields: c.addInt ('hour')
    if 'min' in fields: c.addInt ('min')
    if 'stationid' in fields: c.addVarChar('stationid',7)
    if 'waterlevel' in fields: c.addInt ('waterlevel')
    if 'datum' in fields: c.addInt ('datum')
    if 'sigma' in fields: c.addInt ('sigma')
    if 'source' in fields: c.addInt ('source')

    if addCoastGuardFields:
        # c.addInt('cg_s_rssi')  # Relative signal strength indicator
        # c.addInt('cg_d_strength')  # dBm receive strength
        # c.addVarChar('cg_x',10)  # Idonno
        c.addInt('cg_t_arrival')  # Receive timestamp from the AIS equipment 'T'
        c.addInt('cg_s_slotnum')  # Slot received in
        c.addVarChar('cg_r',15)  # Receiver station ID  -  should usually be an MMSI, but sometimes is a string
        c.addInt('cg_sec')  # UTC seconds since the epoch

        c.addTimestamp('cg_timestamp') # UTC decoded cg_sec - not actually in the data stream

    return c

def sqlInsertStr(params, outfile=sys.stdout, extraParams=None, dbType='postgres'):
        """
        Return the SQL INSERT command for this message type
        @param params: dictionary of values keyed by field name
        @param outfile: file like object to print to.
        @param extraParams: A sequence of tuples containing (name,sql type) for additional fields
        @return: sql create string
        @rtype: str

        @see: sqlCreate
        """
        outfile.write(str(sqlInsert(params,extraParams,dbType=dbType)))


def sqlInsert(params,extraParams=None,dbType='postgres'):
        """
        Give the SQL INSERT statement
        @param params: dict keyed by field name of values
        @param extraParams: any extra fields that you have created beyond the normal ais message fields
        @rtype: sqlhelp.insert
        @return: insert class instance
         TODO(schwehr):allow optional type checking of params?
        @warning: this will take invalid keys happily and do what???
        """

        i = sqlhelp.insert('waterlevel',dbType=dbType)

        if dbType=='postgres':
                finished = []
                for key in params:
                        if key in finished:
                                continue

                        if key not in toPgFields and key not in fromPgFields:
                                if type(params[key])==Decimal: i.add(key,float(params[key]))
                                else: i.add(key,params[key])
                        else:
                                if key in fromPgFields:
                                        val = params[key]
                                        # Had better be a WKT type like POINT(-88.1 30.321)
                                        i.addPostGIS(key,val)
                                        finished.append(key)
                                else:
                                        # Need to construct the type.
                                        pgName = toPgFields[key]
                                        #valStr='GeomFromText(\''+pgTypes[pgName]+'('
                                        valStr=pgTypes[pgName]+'('
                                        vals = []
                                        for nonPgKey in fromPgFields[pgName]:
                                                vals.append(str(params[nonPgKey]))
                                                finished.append(nonPgKey)
                                        valStr+=' '.join(vals)+')'
                                        i.addPostGIS(pgName,valStr)
        else:
                for key in params:
                        if type(params[key])==Decimal: i.add(key,float(params[key]))
                        else: i.add(key,params[key])

        if None != extraParams:
                for key in extraParams:
                        i.add(key,extraParams[key])

        return i

######################################################################
# LATEX SUPPORT
######################################################################

def latexDefinitionTable(outfile=sys.stdout
                ):
        """
        Return the LaTeX definition table for this message type
        @param outfile: file like object to print to.
        @type outfile: file obj
        @return: LaTeX table string via the outfile
        @rtype: str

        """
        o = outfile

        o.write("""
\\begin{table}%[htb]
\\centering
\\begin{tabular}{|l|c|l|}
\\hline
Parameter & Number of bits & Description
\\\\  \\hline\\hline
MessageID & 6 & AIS message number.  Must be 8 \\\\ \hline
RepeatIndicator & 2 & Indicated how many times a message has been repeated \\\\ \hline
UserID & 30 & Unique ship identification number (MMSI) \\\\ \hline
Spare & 2 & Reserved for definition by a regional authority. \\\\ \hline
dac & 10 & Designated Area Code \\\\ \hline
fid & 6 & Functional Identifier \\\\ \hline
month & 4 & Time the measurement represents  month 1..12 \\\\ \hline
day & 5 & Time the measurement represents  day of the month 1..31 \\\\ \hline
hour & 5 & Time the measurement represents  UTC hours 0..23 \\\\ \hline
min & 6 & Time the measurement represents  minutes \\\\ \hline
stationid & 42 & Character identifier of the station.  Usually a number. \\\\ \hline
waterlevel & 16 & Water level in centimeters \\\\ \hline
datum & 5 & What reference datum applies to the value \\\\ \hline
sigma & 7 & Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma? \\\\ \hline
source & 3 & How the water level was derived\\\\ \\hline \\hline
Total bits & 149 & Appears to take 1 slot with 19 pad bits to fill the last slot \\\\ \\hline
\\end{tabular}
\\caption{AIS message number 8: Water level report.  This is }
\\label{tab:waterlevel}
\\end{table}
""")

######################################################################
# Text Definition
######################################################################

def textDefinitionTable(outfile=sys.stdout ,delim='    '):
    """Return the text definition table for this message type

    @param outfile: file like object to print to.
    @type outfile: file obj
    @return: text table string via the outfile
    @rtype: str

    """
    o = outfile
    o.write('Parameter'+delim+'Number of bits'+delim+"""Description
MessageID"""+delim+'6'+delim+"""AIS message number.  Must be 8
RepeatIndicator"""+delim+'2'+delim+"""Indicated how many times a message has been repeated
UserID"""+delim+'30'+delim+"""Unique ship identification number (MMSI)
Spare"""+delim+'2'+delim+"""Reserved for definition by a regional authority.
dac"""+delim+'10'+delim+"""Designated Area Code
fid"""+delim+'6'+delim+"""Functional Identifier
month"""+delim+'4'+delim+"""Time the measurement represents  month 1..12
day"""+delim+'5'+delim+"""Time the measurement represents  day of the month 1..31
hour"""+delim+'5'+delim+"""Time the measurement represents  UTC hours 0..23
min"""+delim+'6'+delim+"""Time the measurement represents  minutes
stationid"""+delim+'42'+delim+"""Character identifier of the station.  Usually a number.
waterlevel"""+delim+'16'+delim+"""Water level in centimeters
datum"""+delim+'5'+delim+"""What reference datum applies to the value
sigma"""+delim+'7'+delim+"""Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?
source"""+delim+'3'+delim+"""How the water level was derived
Total bits"""+delim+"""149"""+delim+"""Appears to take 1 slot with 19 pad bits to fill the last slot""")


######################################################################
# UNIT TESTING
######################################################################
def testParams():
    '''Return a params file base on the testvalue tags.
    @rtype: dict
    @return: params based on testvalue tags
    '''
    params = {}
    params['MessageID'] = 8
    params['RepeatIndicator'] = 1
    params['UserID'] = 1193046
    params['Spare'] = 0
    params['dac'] = 366
    params['fid'] = 63
    params['month'] = 2
    params['day'] = 28
    params['hour'] = 23
    params['min'] = 45
    params['stationid'] = 'A234567'
    params['waterlevel'] = -97
    params['datum'] = 0
    params['sigma'] = 97
    params['source'] = 2

    return params

class Testwaterlevel(unittest.TestCase):
    '''Use testvalue tag text from each type to build test case the waterlevel message'''
    def testEncodeDecode(self):

        params = testParams()
        bits   = encode(params)
        r      = decode(bits)

        # Check that each parameter came through ok.
        self.failUnlessEqual(r['MessageID'],params['MessageID'])
        self.failUnlessEqual(r['RepeatIndicator'],params['RepeatIndicator'])
        self.failUnlessEqual(r['UserID'],params['UserID'])
        self.failUnlessEqual(r['Spare'],params['Spare'])
        self.failUnlessEqual(r['dac'],params['dac'])
        self.failUnlessEqual(r['fid'],params['fid'])
        self.failUnlessEqual(r['month'],params['month'])
        self.failUnlessEqual(r['day'],params['day'])
        self.failUnlessEqual(r['hour'],params['hour'])
        self.failUnlessEqual(r['min'],params['min'])
        self.failUnlessEqual(r['stationid'],params['stationid'])
        self.failUnlessEqual(r['waterlevel'],params['waterlevel'])
        self.failUnlessEqual(r['datum'],params['datum'])
        self.failUnlessEqual(r['sigma'],params['sigma'])
        self.failUnlessEqual(r['source'],params['source'])

def addMsgOptions(parser):
    parser.add_option('-d','--decode',dest='doDecode',default=False,action='store_true',
                help='decode a "waterlevel" AIS message')
    parser.add_option('-e','--encode',dest='doEncode',default=False,action='store_true',
                help='encode a "waterlevel" AIS message')
    parser.add_option('--RepeatIndicator-field', dest='RepeatIndicatorField',default=0,metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--UserID-field', dest='UserIDField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--month-field', dest='monthField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--day-field', dest='dayField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--hour-field', dest='hourField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--min-field', dest='minField',metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--stationid-field', dest='stationidField',default='@@@@@@@',metavar='aisstr6',type='string'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--waterlevel-field', dest='waterlevelField',default=-32768,metavar='int',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--datum-field', dest='datumField',default=31,metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--sigma-field', dest='sigmaField',default=127,metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')
    parser.add_option('--source-field', dest='sourceField',default=0,metavar='uint',type='int'
        ,help='Field parameter value [default: %default]')

def main():
    from optparse import OptionParser
    parser = OptionParser(usage="%prog [options]")

    parser.add_option('--unit-test',dest='unittest',default=False,action='store_true',
        help='run the unit tests')
    parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
        help='Make the test output verbose')

    # FIX: remove nmea from binary messages.  No way to build the whole packet?
    # FIX: or build the surrounding msg 8 for a broadcast?
    typeChoices = ('binary','nmeapayload','nmea') # FIX: what about a USCG type message?
    parser.add_option('-t', '--type', choices=typeChoices, type='choice',
        dest='ioType', default='nmeapayload',
        help='What kind of string to write for encoding ('+', '.join(typeChoices)+') [default: %default]')


    outputChoices = ('std','html','csv','sql' )
    parser.add_option('-T', '--output-type', choices=outputChoices,
        type='choice', dest='outputType', default='std',
        help='What kind of string to output ('+', '.join(outputChoices)+') '
        '[default: %default]')

    parser.add_option('-o','--output',dest='outputFileName',default=None,
        help='Name of the python file to write [default: stdout]')

    parser.add_option('-f', '--fields', dest='fieldList', default=None,
        action='append', choices=fieldList,
        help='Which fields to include in the output.  Currently only for csv '
        'output [default: all]')

    parser.add_option('-p', '--print-csv-field-list', dest='printCsvfieldList',
        default=False,action='store_true',
        help='Print the field name for csv')

    parser.add_option('-c', '--sql-create', dest='sqlCreate', default=False,
        action='store_true',
        help='Print out an sql create command for the table.')

    parser.add_option('--latex-table', dest='latexDefinitionTable',
        default=False,action='store_true',
        help='Print a LaTeX table of the type')

    parser.add_option('--text-table', dest='textDefinitionTable', default=False,
        action='store_true',
        help='Print delimited table of the type (for Word table importing)')

    parser.add_option('--delimt-text-table', dest='delimTextDefinitionTable',
        default='    ',
        help='Delimiter for text table [default: \'%default\'] '
        '(for Word table importing)')

    dbChoices = ('sqlite','postgres')
    parser.add_option('-D', '--db-type', dest='dbType', default='postgres',
        choices=dbChoices,type='choice',
        help='What kind of database ('+', '.join(dbChoices)+') '
        '[default: %default]')

    addMsgOptions(parser)

    options, args = parser.parse_args()

    if options.unittest:
            sys.argv = [sys.argv[0]]
            if options.verbose: sys.argv.append('-v')
            unittest.main()

    outfile = sys.stdout
    if None!=options.outputFileName:
            outfile = file(options.outputFileName,'w')


    if options.doEncode:
        # Make sure all non required options are specified.
        if None==options.RepeatIndicatorField: parser.error("missing value for RepeatIndicatorField")
        if None==options.UserIDField: parser.error("missing value for UserIDField")
        if None==options.monthField: parser.error("missing value for monthField")
        if None==options.dayField: parser.error("missing value for dayField")
        if None==options.hourField: parser.error("missing value for hourField")
        if None==options.minField: parser.error("missing value for minField")
        if None==options.stationidField: parser.error("missing value for stationidField")
        if None==options.waterlevelField: parser.error("missing value for waterlevelField")
        if None==options.datumField: parser.error("missing value for datumField")
        if None==options.sigmaField: parser.error("missing value for sigmaField")
        if None==options.sourceField: parser.error("missing value for sourceField")
    msgDict = {
        'MessageID': '8',
        'RepeatIndicator': options.RepeatIndicatorField,
        'UserID': options.UserIDField,
        'Spare': '0',
        'dac': '366',
        'fid': '63',
        'month': options.monthField,
        'day': options.dayField,
        'hour': options.hourField,
        'min': options.minField,
        'stationid': options.stationidField,
        'waterlevel': options.waterlevelField,
        'datum': options.datumField,
        'sigma': options.sigmaField,
        'source': options.sourceField,
    }

    bits = encode(msgDict)
    if 'binary' == options.ioType:
        print str(bits)
    elif 'nmeapayload'==options.ioType:
        # FIX: figure out if this might be necessary at compile time
        bitLen=len(bits)
        if bitLen % 6 != 0:
            bits = bits + BitVector(size=(6 - (bitLen%6)))  # Pad out to multiple of 6
        print binary.bitvectoais6(bits)[0]

    # FIX: Do not emit this option for the binary message payloads.  Does not make sense.
    elif 'nmea' == options.ioType:
        nmea = uscg.create_nmea(bits)
        print nmea
    else:
        sys.exit('ERROR: unknown ioType.  Help!')


        if options.sqlCreate:
                sqlCreateStr(outfile,options.fieldList,dbType=options.dbType)

        if options.latexDefinitionTable:
                latexDefinitionTable(outfile)

        # For conversion to word tables
        if options.textDefinitionTable:
                textDefinitionTable(outfile,options.delimTextDefinitionTable)

        if options.printCsvfieldList:
                # Make a csv separated list of fields that will be displayed for csv
                if None == options.fieldList: options.fieldList = fieldList
                import StringIO
                buf = StringIO.StringIO()
                for field in options.fieldList:
                        buf.write(field+',')
                result = buf.getvalue()
                if result[-1] == ',': print result[:-1]
                else: print result

        if options.doDecode:
                if len(args)==0: args = sys.stdin
                for msg in args:
                        bv = None

                        if msg[0] in ('$','!') and msg[3:6] in ('VDM','VDO'):
                                # Found nmea
                                # FIX: do checksum
                                bv = binary.ais6tobitvec(msg.split(',')[5])
                        else: # either binary or nmeapayload... expect mostly nmeapayloads
                                # assumes that an all 0 and 1 string can not be a nmeapayload
                                binaryMsg=True
                                for c in msg:
                                        if c not in ('0','1'):
                                                binaryMsg=False
                                                break
                                if binaryMsg:
                                        bv = BitVector(bitstring=msg)
                                else: # nmeapayload
                                        bv = binary.ais6tobitvec(msg)

                        printFields(decode(bv)
                                    ,out=outfile
                                    ,format=options.outputType
                                    ,fieldList=options.fieldList
                                    ,dbType=options.dbType
                                    )

############################################################
if __name__=='__main__':
    main()

#!/usr/bin/env python Autogenerated python functions to serialize/deserialize binary messages.  Generated by: ../scripts/aisxmlbinmsg2py.py  Need to then wrap these functions with the outer AIS packet and then convert the whole binary blob to a NMEA string.  Those functions are not currently provided in this file.  serialize: python to ais binary deserialize: ais binary to python  The generated code uses translators.py, binary.py, and aisstring.py which should be packaged with the resulting files.    TODO(schwehr):FIX: put in a description of the message here with fields and types. # FIX: check to see if these will be needed Go to the Postgis field names from the straight field name Go from the Postgis field names to the straight field name Lookup table for each postgis field name to get its type. Create a waterlevel binary message payload to pack into an AIS Msg waterlevel.      Fields in params:       - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")       - RepeatIndicator(uint): Indicated how many times a message has been repeated       - UserID(uint): Unique ship identification number (MMSI)       - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")       - dac(uint): Designated Area Code (field automatically set to "366")       - fid(uint): Functional Identifier (field automatically set to "63")       - month(uint): Time the measurement represents  month 1..12       - day(uint): Time the measurement represents  day of the month 1..31       - hour(uint): Time the measurement represents  UTC hours 0..23       - min(uint): Time the measurement represents  minutes       - stationid(aisstr6): Character identifier of the station.  Usually a number.       - waterlevel(int): Water level in centimeters       - datum(uint): What reference datum applies to the value       - sigma(uint): Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?       - source(uint): How the water level was derived     @param params: Dictionary of field names/values.  Throws a ValueError exception if required is missing     @param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.     @rtype: BitVector     @return: encoded binary message (for binary messages, this needs to be wrapped in a msg 8     @note: The returned bits may not be 6 bit aligned.  It is up to you to pad out the bits. Unpack a waterlevel message.      Fields in params:       - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")       - RepeatIndicator(uint): Indicated how many times a message has been repeated       - UserID(uint): Unique ship identification number (MMSI)       - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")       - dac(uint): Designated Area Code (field automatically set to "366")       - fid(uint): Functional Identifier (field automatically set to "63")       - month(uint): Time the measurement represents  month 1..12       - day(uint): Time the measurement represents  day of the month 1..31       - hour(uint): Time the measurement represents  UTC hours 0..23       - min(uint): Time the measurement represents  minutes       - stationid(aisstr6): Character identifier of the station.  Usually a number.       - waterlevel(int): Water level in centimeters       - datum(uint): What reference datum applies to the value       - sigma(uint): Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?       - source(uint): How the water level was derived     @type bv: BitVector     @param bv: Bits defining a message     @param validate: Set to true to cause checking to occur.  Runs slower.  FIX: not implemented.     @rtype: dict     @return: params #Would be nice to check the bit count here.. #if validate: #    assert (len(bv)==FIX: SOME NUMBER) Print a waterlevel message to stdout.      Fields in params:       - MessageID(uint): AIS message number.  Must be 8 (field automatically set to "8")       - RepeatIndicator(uint): Indicated how many times a message has been repeated       - UserID(uint): Unique ship identification number (MMSI)       - Spare(uint): Reserved for definition by a regional authority. (field automatically set to "0")       - dac(uint): Designated Area Code (field automatically set to "366")       - fid(uint): Functional Identifier (field automatically set to "63")       - month(uint): Time the measurement represents  month 1..12       - day(uint): Time the measurement represents  day of the month 1..31       - hour(uint): Time the measurement represents  UTC hours 0..23       - min(uint): Time the measurement represents  minutes       - stationid(aisstr6): Character identifier of the station.  Usually a number.       - waterlevel(int): Water level in centimeters       - datum(uint): What reference datum applies to the value       - sigma(uint): Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma?       - source(uint): How the water level was derived     @param params: Dictionary of field names/values.     @param out: File like object to write to.     @rtype: stdout     @return: text to out # else: leave it empty # Nothing to return #RepeatIndicatorEncodeLut # RepeatIndicatorEncodeLut #datumEncodeLut # datumEncodeLut #sourceEncodeLut # sourceEncodeLut ###################################################################### # SQL SUPPORT ###################################################################### Return the SQL CREATE command for this message type         @param outfile: file like object to print to.         @param fields: which fields to put in the create.  Defaults to all.         @param extraFields: A sequence of tuples containing (name,sql type) for additional fields         @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format         @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')         @type addCoastGuardFields: bool         @return: sql create string         @rtype: str          @see: sqlCreate # FIX: should this sqlCreate be the same as in LaTeX (createFuncName) rather than hard coded? Return the sqlhelp object to create the table.      @param fields: which fields to put in the create.  Defaults to all.     @param extraFields: A sequence of tuples containing (name,sql type) for additional fields     @param addCoastGuardFields: Add the extra fields that come after the NMEA check some from the USCG N-AIS format     @type addCoastGuardFields: bool     @param dbType: Which flavor of database we are using so that the create is tailored ('sqlite' or 'postgres')     @return: An object that can be used to generate a return     @rtype: sqlhelp.create # c.addInt('cg_s_rssi')  # Relative signal strength indicator # c.addInt('cg_d_strength')  # dBm receive strength # c.addVarChar('cg_x',10)  # Idonno # Receive timestamp from the AIS equipment 'T' # Slot received in # Receiver station ID  -  should usually be an MMSI, but sometimes is a string # UTC seconds since the epoch # UTC decoded cg_sec - not actually in the data stream Return the SQL INSERT command for this message type         @param params: dictionary of values keyed by field name         @param outfile: file like object to print to.         @param extraParams: A sequence of tuples containing (name,sql type) for additional fields         @return: sql create string         @rtype: str          @see: sqlCreate Give the SQL INSERT statement         @param params: dict keyed by field name of values         @param extraParams: any extra fields that you have created beyond the normal ais message fields         @rtype: sqlhelp.insert         @return: insert class instance          TODO(schwehr):allow optional type checking of params?         @warning: this will take invalid keys happily and do what??? # Had better be a WKT type like POINT(-88.1 30.321) # Need to construct the type. #valStr='GeomFromText(\''+pgTypes[pgName]+'(' ###################################################################### # LATEX SUPPORT ###################################################################### Return the LaTeX definition table for this message type         @param outfile: file like object to print to.         @type outfile: file obj         @return: LaTeX table string via the outfile         @rtype: str \\begin{table}%[htb] \\centering \\begin{tabular}{|l|c|l|} \\hline Parameter & Number of bits & Description \\\\  \\hline\\hline MessageID & 6 & AIS message number.  Must be 8 \\\\ \hline RepeatIndicator & 2 & Indicated how many times a message has been repeated \\\\ \hline UserID & 30 & Unique ship identification number (MMSI) \\\\ \hline Spare & 2 & Reserved for definition by a regional authority. \\\\ \hline dac & 10 & Designated Area Code \\\\ \hline fid & 6 & Functional Identifier \\\\ \hline month & 4 & Time the measurement represents  month 1..12 \\\\ \hline day & 5 & Time the measurement represents  day of the month 1..31 \\\\ \hline hour & 5 & Time the measurement represents  UTC hours 0..23 \\\\ \hline min & 6 & Time the measurement represents  minutes \\\\ \hline stationid & 42 & Character identifier of the station.  Usually a number. \\\\ \hline waterlevel & 16 & Water level in centimeters \\\\ \hline datum & 5 & What reference datum applies to the value \\\\ \hline sigma & 7 & Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma? \\\\ \hline source & 3 & How the water level was derived\\\\ \\hline \\hline Total bits & 149 & Appears to take 1 slot with 19 pad bits to fill the last slot \\\\ \\hline \\end{tabular} \\caption{AIS message number 8: Water level report.  This is } \\label{tab:waterlevel} \\end{table} ###################################################################### # Text Definition ###################################################################### Return the text definition table for this message type      @param outfile: file like object to print to.     @type outfile: file obj     @return: text table string via the outfile     @rtype: str Description MessageID AIS message number.  Must be 8 RepeatIndicator Indicated how many times a message has been repeated UserID Unique ship identification number (MMSI) Spare Reserved for definition by a regional authority. dac Designated Area Code fid Functional Identifier month Time the measurement represents  month 1..12 day Time the measurement represents  day of the month 1..31 hour Time the measurement represents  UTC hours 0..23 min Time the measurement represents  minutes stationid Character identifier of the station.  Usually a number. waterlevel Water level in centimeters datum What reference datum applies to the value sigma Standard deviation of 1 second samples used to compute the water level height.  FIX: is this the correct description of sigma? source How the water level was derived Total bits 149 Appears to take 1 slot with 19 pad bits to fill the last slot ###################################################################### # UNIT TESTING ###################################################################### Return a params file base on the testvalue tags.     @rtype: dict     @return: params based on testvalue tags Use testvalue tag text from each type to build test case the waterlevel message # Check that each parameter came through ok. # FIX: remove nmea from binary messages.  No way to build the whole packet? # FIX: or build the surrounding msg 8 for a broadcast? # FIX: what about a USCG type message? # Make sure all non required options are specified. # FIX: figure out if this might be necessary at compile time # Pad out to multiple of 6 # FIX: Do not emit this option for the binary message payloads.  Does not make sense. # For conversion to word tables # Make a csv separated list of fields that will be displayed for csv # Found nmea # FIX: do checksum # either binary or nmeapayload... expect mostly nmeapayloads # assumes that an all 0 and 1 string can not be a nmeapayload # nmeapayload ############################################################