ais-tx/AIVDM_Encoder.py

312 lines
14 KiB
Python
Executable File

#!/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.
#
# 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.
#
# Usage examples:
# $ ./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
_rot = '{0:b}'.format(128).rjust(8,'0') # rate of turn not defined
_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
_rstatus = '0'*19 # ??
# '11100000000000000110' : Radio status
return _type+_repeat+_mmsi+_status+_rot+_speed+_accurancy+_long+_lat+_course+_true_heading+_ts+_flags+_rstatus
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')
_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
_rstatus = '0'*19 # ??
# '11100000000000000110' : Radio status
return _type+_repeat+_mmsi+'0'*23+_hour+_min+_sec+_accurancy+_long+_lat+_device+'0'*11+_rstatus
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))
return _type+_repeat+_mmsi+_spare+_msg
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
_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)
_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'
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)
_channel_a = '{0:b}'.format(__channel_a).rjust(12,'0') # 2087
_channel_b = '{0:b}'.format(__channel_b).rjust(12,'0') # 2088
_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)
(_ne_lon, _ne_lat) = compute_long_lat22(__ne_lon, __ne_lat)
(_sw_lon, _sw_lat) = compute_long_lat22(__sw_lon, __sw_lat)
_zonesize = '{0:b}'.format(4).rjust(3,'0') # default
return _type+_repeat+_mmsi+'00'+_channel_a+_channel_b+_txrxmode+_power+_ne_lon+_ne_lat+_sw_lon+_sw_lat+'000'+_zonesize+'0'*23
def encode_23(__mmsi, __ne_lon, __ne_lat, __sw_lon, __sw_lat, __interval_time, __quiet_time):
_type = '{0:b}'.format(23).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)
(_ne_lon, _ne_lat) = compute_long_lat22(__ne_lon, __ne_lat)
(_sw_lon, _sw_lat) = compute_long_lat22(__sw_lon, __sw_lat)
_station_type = '0000' # Target all stations (class A, B, ...)
_ship_type = '0'*8 # Target all ships/cargos
_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
_interval = '{0:b}'.format(__interval_time).rjust(4,'0')
_quiet = '{0:b}'.format(__quiet_time).rjust(4,'0')
return _type+_repeat+_mmsi+'00'+_ne_lon+_ne_lat+_sw_lon+_sw_lat+_station_type+_ship_type+'0'*22+_txrxmode+_interval+_quiet+'0'*6
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
else:
_part = "01"
_vtype = '{0:b}'.format(__vtype).rjust(8,'0') # 60 = passengers
_vendorID = "0"*42 # vendor ID
_tmp = encode_string(__callsign)
_callsign = _tmp + "0"*(42-len(_tmp)) # 7 six-bit characters
_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"
def main():
from optparse import OptionParser
desc="""Use this tool to generate the binary payload of an AIVDM sentence."""
parser = OptionParser(description=desc)
parser.add_option("--type", help="""Type:
1 = Position Report Class A;
4 = Base Station Report;
14 = Safety-Related Broadcast Message;
18 = Standard Class B CS Position Report;
20 = Data Link Management Message (ref. RFC);
21 = Aid-to-Navigation Report;
22 = Channel Management;
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.
970010000 for SART device""",
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)
parser.add_option("--ts", help="18. Timestamp (sec), default = 38", default=38)
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)
parser.add_option("--fatdmaincrement", help="20. Increment, default = 0", default=0)
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="@@@@@@@@@@@@@@@@@@@@")
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")
(options, args) = parser.parse_args()
if not options.type:
parser.error("Sentence type not specified: -h for help.")
payload = ""
if options.type == "1":
payload = encode_1(int(options.mmsi), float(options.speed), float(options.long), float(options.lat), float(options.course), int(options.ts))
elif options.type == "4":
payload = encode_4(int(options.mmsi), float(options.speed), float(options.long), float(options.lat), float(options.course), int(options.ts))
elif options.type == "14":
payload = encode_14(int(options.mmsi), options.sart_msg)
elif options.type == "20":
payload = encode_20(int(options.mmsi), int(options.fatdmaoffset), int(options.fatdmaslots), int(options.fatdmatimeout), int(options.fatdmaincrement))
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'
payload = encode_21(int(options.mmsi), int(options.aid_type), options.aid_name, float(options.long), float(options.lat), options.vsize, __virtual)
elif options.type == "22":
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))
elif options.type == "23":
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))
elif options.type == "24":
if options.part=="A":
payload = encode_24(int(options.mmsi), "A", __vname=options.vname.upper())
else:
payload = encode_24(int(options.mmsi), "B", __callsign=options.callsign.upper(), __vsize=options.vsize, __vtype=int(options.vtype))
else:
parser.error("Sentence type not supported: -h for help.")
print payload
if __name__ == "__main__":
main()