2014-12-12 13:31:06 +00:00
#!/usr/bin/env python
#
# This script is part of the AIS BlackToolkit.
# AIVDM_Encoder.py allows you to generate arbitrary AIVDM payloads. The main AIS message types are supported.
2015-03-31 21:29:09 +00:00
#
2014-12-12 13:31:06 +00:00
# Copyright 2013-2014 -- Embyte & Pastus
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
2015-03-31 21:29:09 +00:00
# Usage examples:
2014-12-12 13:31:06 +00:00
# $ ./AIVDM_Encoder.py --type=1 --vsize=30x10 | xargs -IA ./unpacker A 1 A
# $ ./AIVDM_Encoder.py --type=1 --vsize=30x10 | xargs -IX ./AiS_TX.py --payload=X --channel=A
#
import sys
# Adapted from gpsd-3.9's driver_ais.c
def encode_string ( string ) :
vocabolary = " @ABCDEFGHIJKLMNOPQRSTUVWXYZ[ \\ ]^- ! \" #$ % & ' ()*+,-./0123456789:;<=>? "
encoded_string = " "
for c in string . upper ( ) :
index = vocabolary . find ( c )
encoded_string + = ' {0:b} ' . format ( index ) . rjust ( 6 , ' 0 ' )
return encoded_string
# NB. We add a mask to tell python how long is our rapresentation (overwise on negative integers, it cannot do the complement 2).
def compute_long_lat ( __long , __lat ) :
_long = ' {0:b} ' . format ( int ( round ( __long * 600000 ) ) & 0b1111111111111111111111111111 ) . rjust ( 28 , ' 0 ' )
_lat = ' {0:b} ' . format ( int ( round ( __lat * 600000 ) ) & 0b111111111111111111111111111 ) . rjust ( 27 , ' 0 ' )
return ( _long , _lat )
def compute_long_lat22 ( __long , __lat ) :
_long = ' {0:b} ' . format ( int ( round ( __long * 600 ) ) & 0b111111111111111111 ) . rjust ( 18 , ' 0 ' )
_lat = ' {0:b} ' . format ( int ( round ( __lat * 600 ) ) & 0b11111111111111111 ) . rjust ( 17 , ' 0 ' )
return ( _long , _lat )
def encode_1 ( __mmsi , __speed , __long , __lat , __course , __ts ) :
_type = ' {0:b} ' . format ( 1 ) . rjust ( 6 , ' 0 ' ) # 18
_repeat = " 00 " # repeat (directive to an AIS transceiver that this message should be rebroadcast.)
_mmsi = ' {0:b} ' . format ( __mmsi ) . rjust ( 30 , ' 0 ' ) # 30 bits (247320162)
_status = ' {0:b} ' . format ( 15 ) . rjust ( 4 , ' 0 ' ) # status not defined
2015-03-31 21:29:09 +00:00
_rot = ' {0:b} ' . format ( 128 ) . rjust ( 8 , ' 0 ' ) # rate of turn not defined
2014-12-12 13:31:06 +00:00
_speed = ' {0:b} ' . format ( int ( round ( __speed * 10 ) ) ) . rjust ( 10 , ' 0 ' ) # Speed over ground is in 0.1-knot resolution from 0 to 102 knots. value 1023 indicates speed is not available, value 1022 indicates 102.2 knots or higher.
_accurancy = ' 0 ' # > 10m
( _long , _lat ) = compute_long_lat ( __long , __lat )
_course = ' {0:b} ' . format ( int ( round ( __course * 10 ) ) ) . rjust ( 12 , ' 0 ' ) # 0.1 resolution. Course over ground will be 3600 (0xE10) if that data is not available.
_true_heading = ' 1 ' * 9 # 511 (N/A)
_ts = ' {0:b} ' . format ( __ts ) . rjust ( 6 , ' 0 ' ) # Second of UTC timestamp.
_flags = ' 0 ' * 6
# '00': manufactor NaN
# '000': spare
# '0': Raim flag
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_rstatus = ' 0 ' * 19 # ??
# '11100000000000000110' : Radio status
return _type + _repeat + _mmsi + _status + _rot + _speed + _accurancy + _long + _lat + _course + _true_heading + _ts + _flags + _rstatus
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
def encode_4 ( __mmsi , __speed , __long , __lat , __course , __ts ) :
_type = ' {0:b} ' . format ( 4 ) . rjust ( 6 , ' 0 ' ) # 18
_repeat = " 00 " # repeat (directive to an AIS transceiver that this message should be rebroadcast.)
_mmsi = ' {0:b} ' . format ( __mmsi ) . rjust ( 30 , ' 0 ' ) # 30 bits (247320162)
_hour = ' {0:b} ' . format ( 24 ) . rjust ( 5 , ' 0 ' )
_min = _sec = ' {0:b} ' . format ( 60 ) . rjust ( 6 , ' 0 ' )
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_accurancy = ' 1 ' # <= 10m
( _long , _lat ) = compute_long_lat ( __long , __lat )
_device = ' 0001 ' # GPS
_flags = ' 0 ' * 12
# '0': transmission control for packet 24
# '000000000': spare
# '0': Raim flag
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_rstatus = ' 0 ' * 19 # ??
# '11100000000000000110' : Radio status
return _type + _repeat + _mmsi + ' 0 ' * 23 + _hour + _min + _sec + _accurancy + _long + _lat + _device + ' 0 ' * 11 + _rstatus
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
def encode_14 ( __mmsi , __msg ) :
_type = ' {0:b} ' . format ( 14 ) . rjust ( 6 , ' 0 ' ) # 14
_repeat = " 00 " # repeat (directive to an AIS transceiver that this message should be rebroadcast.)
_mmsi = ' {0:b} ' . format ( __mmsi ) . rjust ( 30 , ' 0 ' ) # 30 bits (247320162)
_spare = " 00 " # spare bit
_msg = encode_string ( __msg )
# _padding = '0'*(168-6-2-30-2-len(_msg))
2015-03-31 21:29:09 +00:00
return _type + _repeat + _mmsi + _spare + _msg
2014-12-12 13:31:06 +00:00
def encode_18 ( __mmsi , __speed , __long , __lat , __course , __ts ) :
_type = ' {0:b} ' . format ( 18 ) . rjust ( 6 , ' 0 ' ) # 18
_repeat = " 00 " # repeat (directive to an AIS transceiver that this message should be rebroadcast.)
_mmsi = ' {0:b} ' . format ( __mmsi ) . rjust ( 30 , ' 0 ' ) # 30 bits (247320162)
_reserved = ' 0 ' * 8
_speed = ' {0:b} ' . format ( int ( round ( __speed * 10 ) ) ) . rjust ( 10 , ' 0 ' ) # Speed over ground is in 0.1-knot resolution from 0 to 102 knots. value 1023 indicates speed is not available, value 1022 indicates 102.2 knots or higher.
_accurancy = ' 0 ' # > 10m
( _long , _lat ) = compute_long_lat ( __long , __lat )
_course = ' {0:b} ' . format ( int ( round ( __course * 10 ) ) ) . rjust ( 12 , ' 0 ' ) # 0.1 resolution. Course over ground will be 3600 (0xE10) if that data is not available.
_true_heading = ' 1 ' * 9 # 511 (N/A)
_ts = ' {0:b} ' . format ( __ts ) . rjust ( 6 , ' 0 ' ) # Second of UTC timestamp.
_flags = ' 001011100 '
# '00': Regional reserved
# '1': CS mode (carrier sense Class B)
# '0' Display flag
# '1': DSC
# '1': Band Flag
# '1': M22 Flag
# '0': Assigned 0 -> Autonomous mode
# '0': Raim flag
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_rstatus = ' 11100000000000000110 ' # ??
# '11100000000000000110' : Radio status
return _type + _repeat + _mmsi + _reserved + _speed + _accurancy + _long + _lat + _course + _true_heading + _ts + _flags + _rstatus
def encode_20 ( __mmsi , __offset , __slots , __timeout , __increment ) :
_type = ' {0:b} ' . format ( 20 ) . rjust ( 6 , ' 0 ' ) #
_repeat = ' 00 ' # repeat (directive to an AIS transceiver that this message should be rebroadcast.)
_mmsi = ' {0:b} ' . format ( __mmsi ) . rjust ( 30 , ' 0 ' ) # 30 bits (247320162)
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_offset = ' {0:b} ' . format ( __offset ) . rjust ( 12 , ' 0 ' )
_slots = ' {0:b} ' . format ( __slots ) . rjust ( 4 , ' 0 ' )
_timeout = ' {0:b} ' . format ( __timeout ) . rjust ( 3 , ' 0 ' )
_increment = ' {0:b} ' . format ( __increment ) . rjust ( 11 , ' 0 ' )
return _type + _repeat + _mmsi + ' 00 ' + _offset + _slots + _timeout + _increment + ' 00 '
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
def encode_22 ( __mmsi , __channel_a , __channel_b , __ne_lon , __ne_lat , __sw_lon , __sw_lat ) :
_type = ' {0:b} ' . format ( 22 ) . rjust ( 6 , ' 0 ' ) #
_repeat = ' 00 ' # repeat (directive to an AIS transceiver that this message should be rebroadcast.)
_mmsi = ' {0:b} ' . format ( __mmsi ) . rjust ( 30 , ' 0 ' ) # 30 bits (247320162)
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_channel_a = ' {0:b} ' . format ( __channel_a ) . rjust ( 12 , ' 0 ' ) # 2087
_channel_b = ' {0:b} ' . format ( __channel_b ) . rjust ( 12 , ' 0 ' ) # 2088
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_txrxmode = ' 0 ' * 4 #'{0:b}'.format(__channel_b).rjust(4,'0') # 0,1,2 (1 e 2 disable one tx)
_power = ' 0 ' # high ('1' = low)
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
( _ne_lon , _ne_lat ) = compute_long_lat22 ( __ne_lon , __ne_lat )
( _sw_lon , _sw_lat ) = compute_long_lat22 ( __sw_lon , __sw_lat )
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_zonesize = ' {0:b} ' . format ( 4 ) . rjust ( 3 , ' 0 ' ) # default
2015-03-31 21:29:09 +00:00
return _type + _repeat + _mmsi + ' 00 ' + _channel_a + _channel_b + _txrxmode + _power + _ne_lon + _ne_lat + _sw_lon + _sw_lat + ' 000 ' + _zonesize + ' 0 ' * 23
2014-12-12 13:31:06 +00:00
def encode_23 ( __mmsi , __ne_lon , __ne_lat , __sw_lon , __sw_lat , __interval_time , __quiet_time ) :
2015-03-31 21:29:09 +00:00
_type = ' {0:b} ' . format ( 23 ) . rjust ( 6 , ' 0 ' ) #
2014-12-12 13:31:06 +00:00
_repeat = ' 00 ' # repeat (directive to an AIS transceiver that this message should be rebroadcast.)
_mmsi = ' {0:b} ' . format ( __mmsi ) . rjust ( 30 , ' 0 ' ) # 30 bits (247320162)
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
( _ne_lon , _ne_lat ) = compute_long_lat22 ( __ne_lon , __ne_lat )
( _sw_lon , _sw_lat ) = compute_long_lat22 ( __sw_lon , __sw_lat )
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_station_type = ' 0000 ' # Target all stations (class A, B, ...)
_ship_type = ' 0 ' * 8 # Target all ships/cargos
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_txrxmode = ' 0 ' * 2 #'{0:b}'.format(__channel_b).rjust(4,'0') # 0,1,2 (1 e 2 disable one tx) - NB: 2bits in message 23, 4bits in message 22
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_interval = ' {0:b} ' . format ( __interval_time ) . rjust ( 4 , ' 0 ' )
_quiet = ' {0:b} ' . format ( __quiet_time ) . rjust ( 4 , ' 0 ' )
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
return _type + _repeat + _mmsi + ' 00 ' + _ne_lon + _ne_lat + _sw_lon + _sw_lat + _station_type + _ship_type + ' 0 ' * 22 + _txrxmode + _interval + _quiet + ' 0 ' * 6
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
def encode_24 ( __mmsi , __part , __vname = " NAN " , __callsign = " NAN " , __vsize = " 90x14 " , __vtype = 60 ) :
_type = ' {0:b} ' . format ( 24 ) . rjust ( 6 , ' 0 ' ) # 24
_repeat = " 00 " # repeat (directive to an AIS transceiver that this message should be rebroadcast.)
_mmsi = ' {0:b} ' . format ( __mmsi ) . rjust ( 30 , ' 0 ' ) # 30 bits (247320162)
if __part == " A " :
_part = " 00 "
_vname = encode_string ( __vname )
_padding = ' 0 ' * ( 156 - 6 - 2 - 30 - 2 - len ( _vname ) ) # 160 bits per RFC -> 4 bits padding added in Build_Frame_imple.cc
return _type + _repeat + _mmsi + _part + _vname + _padding
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
else :
_part = " 01 "
_vtype = ' {0:b} ' . format ( __vtype ) . rjust ( 8 , ' 0 ' ) # 60 = passengers
_vendorID = " 0 " * 42 # vendor ID
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_tmp = encode_string ( __callsign )
_callsign = _tmp + " 0 " * ( 42 - len ( _tmp ) ) # 7 six-bit characters
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
_hl = int ( __vsize [ : __vsize . find ( " x " ) ] ) / 2 # AIS antenna in the middle of the boat
_hw = int ( __vsize [ __vsize . find ( " x " ) + 1 : ] ) / 2
_half_length = ' {0:b} ' . format ( _hl ) . rjust ( 9 , ' 0 ' )
_half_width = ' {0:b} ' . format ( _hw ) . rjust ( 6 , ' 0 ' )
return _type + _repeat + _mmsi + _part + _vtype + _vendorID + _callsign + _half_length + _half_length + _half_width + _half_width + " 000000 "
2015-03-31 21:29:09 +00:00
def main ( ) :
from optparse import OptionParser
2014-12-12 13:31:06 +00:00
desc = """ Use this tool to generate the binary payload of an AIVDM sentence. """
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
parser = OptionParser ( description = desc )
2015-03-31 21:29:09 +00:00
parser . add_option ( " --type " , help = """ Type:
1 = Position Report Class A ;
2014-12-12 13:31:06 +00:00
4 = Base Station Report ;
2015-03-31 21:29:09 +00:00
14 = Safety - Related Broadcast Message ;
18 = Standard Class B CS Position Report ;
2014-12-12 13:31:06 +00:00
20 = Data Link Management Message ( ref . RFC ) ;
2015-03-31 21:29:09 +00:00
21 = Aid - to - Navigation Report ;
22 = Channel Management ;
2014-12-12 13:31:06 +00:00
23 = Group Assignment Command ;
24 = Static Data Report ) """ )
parser . add_option ( " --sart_msg " , help = " 14. SART alarm message, default = SART ACTIVE " , default = " SART ACTIVE " )
parser . add_option ( " --mmsi " , help = """ MMSI, default = 247320162.
2015-03-31 21:29:09 +00:00
970010000 for SART device """ ,
2014-12-12 13:31:06 +00:00
default = 247320162 )
parser . add_option ( " --speed " , help = " 18. Speed (knot), default = 0.1 " , default = 0.1 )
parser . add_option ( " --long " , help = " 18. Longitude, default = 9.72357833333333 " , default = 9.72357833333333 )
parser . add_option ( " --lat " , help = " 18. Latitude, default = 45.6910166666667 " , default = 45.6910166666667 )
parser . add_option ( " --course " , help = " 18. Course, default = 83.4 " , default = 83.4 )
2015-03-31 21:29:09 +00:00
parser . add_option ( " --ts " , help = " 18. Timestamp (sec), default = 38 " , default = 38 )
2014-12-12 13:31:06 +00:00
parser . add_option ( " --fatdmaoffset " , help = " 20. Offset, default = 0 " , default = 0 )
parser . add_option ( " --fatdmaslots " , help = " 20. Slot, default = 0 " , default = 0 )
parser . add_option ( " --fatdmatimeout " , help = " 20. Timeout, default = 0 " , default = 0 )
2015-03-31 21:29:09 +00:00
parser . add_option ( " --fatdmaincrement " , help = " 20. Increment, default = 0 " , default = 0 )
2014-12-12 13:31:06 +00:00
parser . add_option ( " --v_AtoN " , help = " 21. Specify that the AtoN is virtual, default = real. " , action = " store_true " )
parser . add_option ( " --aid_type " , help = " 21. Type of AtoN (light, bouye) " , default = 1 )
parser . add_option ( " --aid_name " , help = " 21. Name of AtoN " , default = " @@@@@@@@@@@@@@@@@@@@ " )
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
parser . add_option ( " --channel_a " , help = " 22. Specify channel frequency for A, default = 2087 (87B = 161.975 MHz). Ref ITU-R M.1084 " , default = 2087 )
parser . add_option ( " --channel_b " , help = " 22. Specify channel frequency for B, default = 2088 (88B = 162.025 MHz). Ref ITU-R M.1084 " , default = 2088 )
parser . add_option ( " --ne_lon " , help = " 22/23. Specify NE corner ' s longitude of rectangular jurisdiction area, default = 9.9 " , default = 9.9 )
parser . add_option ( " --ne_lat " , help = " 22/23. Specify NE corner ' s latitude of rectangular jurisdiction area, default = 45.8 " , default = 45.8 )
parser . add_option ( " --sw_lon " , help = " 22/23. Specify SW corner ' s longitude of rectangular jurisdiction area, default = 9.5 " , default = 9.5 )
parser . add_option ( " --sw_lat " , help = " 22/23. Specify SW corner ' s latitude of rectangular jurisdiction area, default = 45.5 " , default = 45.5 )
parser . add_option ( " --interval " , help = " 23. Commands the respective stations to the reporting interval, default = 1 (10 minutes) " , default = 1 )
parser . add_option ( " --quiet " , help = " 23. Force the respective stations to quiet interval, default = 15 minutes) " , default = 15 )
parser . add_option ( " --part " , help = " 24. Message part (A/B), default = A " , default = " A " )
parser . add_option ( " --vname " , help = " 24A. Vessel Name (UPPER CASE), default = NaN " , default = " NAN " )
parser . add_option ( " --callsign " , help = " 24B. Call Sign (UPPER CASE, max 7 chars.), default = KC9CAF " , default = " KC9CAF " )
parser . add_option ( " --vtype " , help = """ 24B. Type of ship and cargo type: 60 Passenger, 70 Cargo, 80 Tanker, 3x Special, 5x Carrying dangerous goods, harmful substances or marine pollutants:
- 35 Engaged in military operations ; - )
- 51 Search and rescue vessels
- 55 Law enforcement vessels
""" , default=60)
parser . add_option ( " --vsize " , help = " 24B/21. Vessel Size (multiple of 2), default = 90x14 " , default = " 90x14 " )
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
( options , args ) = parser . parse_args ( )
if not options . type :
parser . error ( " Sentence type not specified: -h for help. " )
payload = " "
if options . type == " 1 " :
2015-03-31 21:29:09 +00:00
payload = encode_1 ( int ( options . mmsi ) , float ( options . speed ) , float ( options . long ) , float ( options . lat ) , float ( options . course ) , int ( options . ts ) )
2014-12-12 13:31:06 +00:00
elif options . type == " 4 " :
2015-03-31 21:29:09 +00:00
payload = encode_4 ( int ( options . mmsi ) , float ( options . speed ) , float ( options . long ) , float ( options . lat ) , float ( options . course ) , int ( options . ts ) )
2014-12-12 13:31:06 +00:00
elif options . type == " 14 " :
payload = encode_14 ( int ( options . mmsi ) , options . sart_msg )
elif options . type == " 20 " :
2015-03-31 21:29:09 +00:00
payload = encode_20 ( int ( options . mmsi ) , int ( options . fatdmaoffset ) , int ( options . fatdmaslots ) , int ( options . fatdmatimeout ) , int ( options . fatdmaincrement ) )
2014-12-12 13:31:06 +00:00
elif options . type == " 18 " :
payload = encode_18 ( int ( options . mmsi ) , float ( options . speed ) , float ( options . long ) , float ( options . lat ) , float ( options . course ) , int ( options . ts ) )
elif options . type == " 21 " :
if options . v_AtoN == True : __virtual = ' 1 '
else : __virtual = ' 0 '
2015-03-31 21:29:09 +00:00
payload = encode_21 ( int ( options . mmsi ) , int ( options . aid_type ) , options . aid_name , float ( options . long ) , float ( options . lat ) , options . vsize , __virtual )
2014-12-12 13:31:06 +00:00
elif options . type == " 22 " :
2015-03-31 21:29:09 +00:00
payload = encode_22 ( int ( options . mmsi ) , int ( options . channel_a ) , int ( options . channel_b ) , float ( options . ne_lon ) , float ( options . ne_lat ) , float ( options . sw_lon ) , float ( options . sw_lat ) )
2014-12-12 13:31:06 +00:00
elif options . type == " 23 " :
2015-03-31 21:29:09 +00:00
payload = encode_23 ( int ( options . mmsi ) , float ( options . ne_lon ) , float ( options . ne_lat ) , float ( options . sw_lon ) , float ( options . sw_lat ) , int ( options . interval ) , int ( options . quiet ) )
2014-12-12 13:31:06 +00:00
elif options . type == " 24 " :
if options . part == " A " :
payload = encode_24 ( int ( options . mmsi ) , " A " , __vname = options . vname . upper ( ) )
else :
2015-03-31 21:29:09 +00:00
payload = encode_24 ( int ( options . mmsi ) , " B " , __callsign = options . callsign . upper ( ) , __vsize = options . vsize , __vtype = int ( options . vtype ) )
2014-12-12 13:31:06 +00:00
else :
parser . error ( " Sentence type not supported: -h for help. " )
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
print payload
2015-03-31 21:29:09 +00:00
2014-12-12 13:31:06 +00:00
if __name__ == " __main__ " :
main ( )