Skip to content

Commit

Permalink
New ais encoded/decoder for message 18 and 24
Browse files Browse the repository at this point in the history
  • Loading branch information
pwoods25443 committed Sep 9, 2020
1 parent 49610d8 commit c8b6af8
Show file tree
Hide file tree
Showing 15 changed files with 832 additions and 108 deletions.
6 changes: 4 additions & 2 deletions ais-stream/decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import base64

from config import load_config
from ais_tools import aivdm
from ais_tools.aivdm import AIVDM

decoder = AIVDM()


def handle_event(event, context, pubsub_client):
Expand All @@ -19,7 +21,7 @@ def handle_event(event, context, pubsub_client):
message_in['source'] = config['DEFAULT_SOURCE']

message_out = message_in
message_out.update(aivdm.safe_decode(message_in['nmea']))
message_out.update(decoder.safe_decode(message_in['nmea']))

data_out = json.dumps(message_out).encode("utf-8")
pubsub_client.publish(config['DECODE_PUBSUB_TOPIC'], data=data_out, source=message_out['source'])
Expand Down
43 changes: 43 additions & 0 deletions ais_tools/ais.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from ais_tools.transcode import DecodeError
from ais_tools.transcode import MessageTranscoder
from ais_tools.transcode import UintTranscoder as Uint
from ais_tools.transcode import bits_to_nmea
from ais_tools.transcode import nmea_to_bits
from ais_tools.transcode import byte_align
from ais_tools import ais18
from ais_tools import ais24
from ais_tools import ais25


message_types = {
18: [ais18.AIS18Transcoder()],
24: [ais24.AIS24Transcoder()],
25: [ais25.AIS25Transcoder()],
}


class AISMessageTypeTranscoder(MessageTranscoder):

def get_fields(self, message=None):
msg_type = message.get('id')
if msg_type not in message_types:
raise DecodeError('AIS: Unknown message type {}'.format(msg_type))
fields = message_types.get(msg_type, [])
return fields


ais_fields = [
Uint(name='id', nbits=6, default=0),
AISMessageTypeTranscoder(),
]


class AISMessageTranscoder(MessageTranscoder):
def __init__(self):
super().__init__(ais_fields)

def encode_nmea(self, message):
return bits_to_nmea(byte_align(self.encode(message)))

def decode_nmea(self, body, pad=0):
return self.decode(nmea_to_bits(body, pad))
103 changes: 103 additions & 0 deletions ais_tools/ais18.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from ais_tools import transcode
from ais_tools.transcode import DecodeError
from ais_tools.transcode import BitsTranscoder
from ais_tools.transcode import UintTranscoder as Uint
from ais_tools.transcode import Uint10Transcoder as Uint10
from ais_tools.transcode import LatLonTranscoder as LatLon


class AIS18Transcoder(transcode.MessageTranscoder):
def __init__(self):
super().__init__(ais18_fields)


class AIS18CommState(transcode.MessageTranscoder):
def get_fields(self, message=None):
unit_flag = message.get('unit_flag', 0)
commstate_flag = message.get('commstate_flag', 0)

if unit_flag == 0:
if commstate_flag == 0:
return ais18_commstate_SOTDMA
else:
return ais18_commstate_ITDMA
else:
return ais18_commstate_CS


class AIS18CommStateSOTDMA(transcode.MessageTranscoder):
def get_fields(self, message=None):
slot_timeout = message.get('slot_timeout', 0)
if slot_timeout == 0:
return ais18_commstate_SOTDMA_timeout_0
elif slot_timeout == 1:
return ais18_commstate_SOTDMA_timeout_1
elif slot_timeout in (2, 4, 6):
return ais18_commstate_SOTDMA_timeout_2_4_6
elif slot_timeout in (3, 5, 7):
return ais18_commstate_SOTDMA_timeout_3_5_7
else:
raise DecodeError('AIS18: unknown slot_timeout value {}'.format(slot_timeout))


ais18_fields = [
Uint(name='repeat_indicator', nbits=2),
Uint(name='mmsi', nbits=30),
Uint(name='spare', nbits=8),
Uint10(name='sog', nbits=10, default=102.3),
Uint(name='position_accuracy', nbits=1),
LatLon(name='x', nbits=28, default=181),
LatLon(name='y', nbits=27, default=91),
Uint10(name='cog', nbits=12, default=360),
Uint(name='true_heading', nbits=9, default=511),
Uint(name='timestamp', nbits=6, default=60),
Uint(name='spare2', nbits=2),
Uint(name='unit_flag', nbits=1),
Uint(name='display_flag', nbits=1),
Uint(name='dsc_flag', nbits=1),
Uint(name='band_flag', nbits=1),
Uint(name='m22_flag', nbits=1),
Uint(name='mode_flag', nbits=1),
Uint(name='raim', nbits=1),
Uint(name='commstate_flag', nbits=1),
AIS18CommState()
]

ais18_commstate_CS = [
BitsTranscoder(name='commstate', nbits=19, default='1100000000000000110'),
]

ais18_commstate_ITDMA = [
Uint(name='sync_state', nbits=2),
Uint(name='slot_increment', nbits=13),
Uint(name='slots_to_allocate', nbits=3),
Uint(name='keep_flag', nbits=1),
]

ais18_commstate_SOTDMA = [
Uint(name='sync_state', nbits=2),
Uint(name='slot_timeout', nbits=3),
AIS18CommStateSOTDMA()
]

ais18_commstate_SOTDMA_timeout_0 = [
Uint(name='slot_offset', nbits=14),
]

ais18_commstate_SOTDMA_timeout_1 = [
Uint(name='utc_hour', nbits=5),
Uint(name='utc_min', nbits=7),
Uint(name='utc_spare', nbits=2),
]

ais18_commstate_SOTDMA_timeout_2_4_6 = [
Uint(name='slot_number', nbits=14),
]

ais18_commstate_SOTDMA_timeout_3_5_7 = [
Uint(name='received_stations', nbits=14),
]




101 changes: 101 additions & 0 deletions ais_tools/ais24.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from ais_tools import transcode
from ais_tools.transcode import MessageTranscoder as Message
from ais_tools.transcode import DynamicTranscoder
from ais_tools.transcode import DecodeError
from ais_tools.transcode import UintTranscoder as Uint
from ais_tools.transcode import ASCII6Transcoder as ASCII6


class AIS24Transcoder(Message):
def __init__(self):
super().__init__(ais24_fields)


class AIS24PartAB(Message):
def get_fields(self, message=None):
part_num = message.get('part_num', 0)
if part_num == 0:
return ais24_part_A_fields
elif part_num == 1:
return ais24_part_B_fields
else:
raise DecodeError('AIS24: unknown part number {}'.format(part_num))


class VendorID(DynamicTranscoder):
def __init__(self):
self.vendorid_1371_2 = Message(ais24_vendorid_ITU_R_1371_2)
self.vendorid_1371_4 = Message(ais24_vendorid_ITU_R_1371_4)

def encoder(self, message):
if 'vendor_id' in message:
return self.vendorid_1371_2
else:
return self.vendorid_1371_4

def decoders(self, message):
return [self.vendorid_1371_2, self.vendorid_1371_4]


class DimensionOrMothership(Message):
def __init__(self):
super().__init__()
self.shipDimension = Message(ais24_part_B_dimension_fields)
self.mothershipMMSI = Message(ais24_part_B_mothership_fields)

def get_fields(self, message=None):
mmsi = message.get('id')
if mmsi // 10000000 == 98:
return [self.mothershipMMSI]
else:
return [self.shipDimension]


# COMMON FIELDS
ais24_fields = [
Uint(name='repeat_indicator', nbits=2),
Uint(name='mmsi', nbits=30),
Uint(name='part_num', nbits=2),
AIS24PartAB()
]

# PART A
ais24_part_A_fields = [
ASCII6(name='name', nbits=120),
]

# PART B
ais24_vendorid_ITU_R_1371_2 =[
ASCII6(name='vendor_id', nbits=42, default='@@@@@@@'),
]

ais24_vendorid_ITU_R_1371_4 =[
ASCII6(name='vendor_id_1371_4', nbits=18, default='@@@'),
Uint(name='vendor_model', nbits=4),
Uint(name='vendor_serial', nbits=20),
]

ais24_part_B_dimension_fields = [
Uint(name='dim_a', nbits=9),
Uint(name='dim_b', nbits=9),
Uint(name='dim_c', nbits=6),
Uint(name='dim_d', nbits=6),
]

ais24_part_B_mothership_fields = [
Uint(name='mothership_mmsi', nbits=30),
]

ais24_part_B_fields = [
Uint(name='type_and_cargo', nbits=8),
VendorID(),
ASCII6(name='callsign', nbits=42, default='@@@@@@@'),
DimensionOrMothership(),
Uint(name='gps_type', nbits=4),
Uint(name='spare', nbits=2),
]





47 changes: 47 additions & 0 deletions ais_tools/ais25.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from ais_tools import transcode
from ais_tools.transcode import DecodeError
from ais_tools.transcode import UintTranscoder as Uint
from ais_tools.transcode import ASCII6Transcoder as ASCII6
from ais_tools.transcode import VariableLengthASCII6Transcoder as VarASCII6

# Using this coding http://www.e-navigation.nl/content/text-using-6-bit-ascii-1


class AIS25Transcoder(transcode.MessageTranscoder):
def __init__(self):
super().__init__(ais25_fields)


class AIS25Destination(transcode.MessageTranscoder):
def get_fields(self, message=None):
addressed = message.get('addressed', 0)
if addressed:
return ais25_destination_fields
else:
return []


ais25_fields = [
Uint(name='repeat_indicator', nbits=2),
Uint(name='mmsi', nbits=30),
Uint(name='part_num', nbits=2),
Uint(name='addressed', nbits=1),
Uint(name='use_app_id', nbits=1),
AIS25Destination(),
Uint(name='dac', nbits=10, default=1),
Uint(name='fi', nbits=6),
Uint(name='text_seq', nbits=11),
VarASCII6(name='text', default='')
]

ais25_destination_fields = [
Uint(name='dest_mmsi', nbits=30),
Uint(name='spare', nbits=2),
]







Loading

0 comments on commit c8b6af8

Please sign in to comment.