mirror of
https://github.com/Xastir/Xastir
synced 2026-08-13 17:46:16 -04:00
Make all copyright notices include this year. Most say "2000-2026" but some files created later had later start dates, and some created last year only had the start date. They now all have "-2026" regardless. Closes #340
2479 lines
91 KiB
Perl
Executable file
2479 lines
91 KiB
Perl
Executable file
#!/usr/bin/env perl
|
||
use warnings;
|
||
###########################################################################
|
||
#
|
||
#
|
||
# XASTIR, Amateur Station Tracking and Information Reporting
|
||
# Copyright (C) 2000-2026 The Xastir Group
|
||
#
|
||
# "ais.pl", a Perl script to connect aisdecoder and Xastir for receiving
|
||
# packets from ships. This script will create a UDP server at localhost:10110
|
||
# for aisdecoder to send packets into. It decodes AIS sentences, creates APRS
|
||
# packets out of them and sends them to Xastir's server port (2023) via UDP.
|
||
#
|
||
# AIS is the international tracking system for ships. They transmit on Marine
|
||
# VHF frequencies between 156.025 and 162.025, using a low power output of 1W,
|
||
# high power output of either 5W or 12.5W, at a bit rate of 9600 bits/s using
|
||
# NRZI data encoding and GMSK/FM at 0.5 modulation index.
|
||
#
|
||
# The two default AIS channels are:
|
||
# Ch 87B, AIS-1: 161.975 MHz
|
||
# Ch 88B, AIS-2: 162.025 MHz
|
||
#
|
||
# The two long-range channels are:
|
||
# Ch 75: 156.775 MHz
|
||
# Ch 76: 156.825 MHz
|
||
#
|
||
#
|
||
# You'll need "libusb", "libpthread", "librtlsdr" installed to be able to
|
||
# compile "rtl_ais".
|
||
#
|
||
# Get aisdecoder (rtl-ais).
|
||
# git clone https://github.com/dgiardini/rtl-ais/
|
||
# cd rtl_ais
|
||
# make
|
||
#
|
||
#
|
||
# Run "rtl_ais" like this (create a simple script!) for the two default channels:
|
||
#
|
||
# ./rtl_ais -h 127.0.0.1 -P 10110 -d 0 -l 161.975M -r 162.025M -n -p -2
|
||
#
|
||
# Change to marine channels 75 (156.775 MHz) and 76 (156.825 MHz) to pick
|
||
# up long-range AIS (Type 27 packets), perhaps with another dongle:
|
||
#
|
||
# ./rtl_ais -h 127.0.0.1 -P 10110 -d 1 -l 156.775M -r 156.825M -n -p -2
|
||
#
|
||
# Note that "-p -2" is the frequency error of the RTL dongle.
|
||
# Set that to the proper number determined from "Kal".
|
||
#
|
||
# "-d 0" or "-d 1" selects which dongle you're attaching to.
|
||
#
|
||
# Don't use "-g <number>" and instead use auto-gain, which appears to work
|
||
# better. This also works better than enabling AGC in the chipset itself.
|
||
#
|
||
#
|
||
# Run "ais.pl" like this (again, create a simple script!):
|
||
# ./ais.pl boats <passcode> [--pipe] [--logging]
|
||
#
|
||
# You may add the "--pipe" flag in order to have "ais.pl" accept data
|
||
# from a pipe instead of setting up a UDP server for "rtl_ais" to send
|
||
# packets to. This is very useful for debugging or for processing
|
||
# stored log files.
|
||
#
|
||
# If you add " --logging" to the end, this script will save the APRS portion of
|
||
# the output to a file called "~/.xastir/logs/ships.log". You can later suck
|
||
# this file back in to see the ships move around the map in hyperspeed. Useful
|
||
# for a quick demo.
|
||
#
|
||
#
|
||
# Antenna length should be:
|
||
# 1/2 wave dipole: 2.89' -or- 2' 10.7" -or- 2' 10 5/8"
|
||
# 1/4 wave: 1.44' -or- 1' 5.3" -or- 1' 5 5/16"
|
||
#
|
||
#
|
||
# Excellent AIS info:
|
||
# http://catb.org/gpsd/AIVDM.html # Eric Raymond/GPSD
|
||
# https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1371-5-201402-I!!PDF-E.pdf # ITU Spec
|
||
# http://www.navcen.uscg.gov/pdf/AIS/AIS_Special_Notice_and_AIS_Encoding_Guide_2012.pdf
|
||
#
|
||
# Example packets w/decoding of fields, plus large sample NMEA data file:
|
||
# http://fossies.org/linux/gpsd/test/sample.aivdm
|
||
#
|
||
# Possible live data feed (Too many connections when I try):
|
||
# ais.exploratorium.edu:80
|
||
#
|
||
###########################################################################
|
||
|
||
|
||
use IO::Socket;
|
||
use Storable;
|
||
#use File::HomeDir;
|
||
#use Data::Dumper; # Only used for debugging vessel_hash
|
||
|
||
|
||
# Enable sending APRS packets to Xastir
|
||
$enable_tx = 1;
|
||
|
||
# Enable the printing of a statistics line every 100 messages
|
||
# The format is: total type1 type2 ... type27
|
||
# Example: 3600 62 0 15 857 0 0 0 0 0 0 0 0 0 0 113 0 0 0 0 685 1868 0 0 0 0 0 0
|
||
$statistics_mode = 1;
|
||
|
||
# Turn on/off printing of various types of messages. Enables debugging of a few
|
||
# sentence types at a time w/o other messages cluttering up the output.
|
||
$print_123 = 1;
|
||
$print_4 = 1;
|
||
$print_5 = 1; # (includes vesselName and shipType)
|
||
$print_9 = 1;
|
||
$print_18 = 1;
|
||
$print_19 = 1; # (includes vesselName)
|
||
$print_21 = 1;
|
||
$print_24_A = 1; # A Variant (includes vesselName)
|
||
$print_24_B = 1; # B Variant (includes shipType
|
||
$print_27 = 1;
|
||
$print_others = 1; # Not decoded yet
|
||
|
||
# Keep statistics on each type (28 slots)
|
||
@message_type_count = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
|
||
$statistics_count = 0; # Number of messages before printing stats
|
||
|
||
# Hash to store tac-calls in, to minimize tac-call assignment packets
|
||
# This stores the complete tactical call, including whatever name
|
||
# the ship goes by currently + the country of registry.
|
||
my %tactical_hash;
|
||
|
||
# Create external copy of vessel_hash for persistent store.
|
||
# This contains only the vessel name, not the country of registry
|
||
# or the 9-digit MMSI number.
|
||
$home = `echo ~`;
|
||
chomp $home;
|
||
$persistentFileSpec = "$home/.xastir/config/vessel_hash";
|
||
#$persistentFileSpec = File::HomeDir->my_home . "/.xastir/config/vessel_hash";
|
||
#
|
||
#
|
||
# Hash to store vessel names in, for assigning tactical calls
|
||
# If it doesn't exist, create it with some initial values
|
||
# For final release this should simply create a dummy file
|
||
my %vessel_hash;
|
||
|
||
if ( !(-e $persistentFileSpec )) {
|
||
%vessel_hash = (
|
||
);
|
||
|
||
# Create the new hash file
|
||
store \%vessel_hash , $persistentFileSpec;
|
||
}
|
||
|
||
# Retrieve the cache
|
||
%vessel_hash = %{retrieve($persistentFileSpec)};
|
||
|
||
# Debug - print the hash values
|
||
#print Dumper(\%vessel_hash);
|
||
|
||
|
||
$udp_client = "xastir_udp_client";
|
||
|
||
$xastir_host = "localhost"; # Server where Xastir is running
|
||
$xastir_port = 2023; # 2023 is Xastir default UDP port
|
||
|
||
$log_file = "~/.xastir/logs/ships.log";
|
||
|
||
|
||
%countries = (
|
||
"201" => "Albania", #"Albania (Republic of)"
|
||
"202" => "Andorra", # "Andorra (Principality of)"
|
||
"203" => "Austria",
|
||
"204" => "Azores", # "Azores - Portugal"
|
||
"205" => "Belgium",
|
||
"206" => "Belarus", # "Belarus (Republic of)"
|
||
"207" => "Bulgaria", # "Bulgaria (Republic of)"
|
||
"208" => "Vatican", # "Vatican City State"
|
||
"209" => "Cyprus", # "Cyprus (Republic of)"
|
||
"210" => "Cyprus", # "Cyprus (Republic of)"
|
||
"211" => "Germany", # "Germany (Federal Republic of)"
|
||
"212" => "Cyprus", # "Cyprus (Republic of)"
|
||
"213" => "Georgia",
|
||
"214" => "Moldova", # "Moldova (Republic of)"
|
||
"215" => "Malta",
|
||
"216" => "Armenia", # "Armenia (Republic of)"
|
||
"218" => "Germany", # "Germany (Federal Republic of)"
|
||
"219" => "Denmark",
|
||
"220" => "Denmark",
|
||
"224" => "Spain",
|
||
"225" => "Spain",
|
||
"226" => "France",
|
||
"227" => "France",
|
||
"228" => "France",
|
||
"229" => "Malta",
|
||
"230" => "Finland",
|
||
"231" => "Faroe Is.", # "Faroe Islands - Denmark"
|
||
"232" => "U.K.", # "United Kingdom of Great Britain and Northern Ireland"
|
||
"233" => "U.K.", # "United Kingdom of Great Britain and Northern Ireland"
|
||
"234" => "U.K.", # "United Kingdom of Great Britain and Northern Ireland"
|
||
"235" => "U.K.", # "United Kingdom of Great Britain and Northern Ireland"
|
||
"236" => "Gibraltar", # "Gibraltar - United Kingdom of Great Britain and Northern Ireland"
|
||
"237" => "Greece",
|
||
"238" => "Croatia", # "Croatia (Republic of)"
|
||
"239" => "Greece",
|
||
"240" => "Greece",
|
||
"241" => "Greece",
|
||
"242" => "Morocco", # "Morocco (Kingdom of)"
|
||
"243" => "Hungary",
|
||
"244" => "Netherlands", # "Netherlands (Kingdom of the)"
|
||
"245" => "Netherlands", # "Netherlands (Kingdom of the)"
|
||
"246" => "Netherlands", # "Netherlands (Kingdom of the)"
|
||
"247" => "Italy",
|
||
"248" => "Malta",
|
||
"249" => "Malta",
|
||
"250" => "Ireland",
|
||
"251" => "Iceland",
|
||
"252" => "Liechtenstein", # "Liechtenstein (Principality of)"
|
||
"253" => "Luxembourg",
|
||
"254" => "Monaco", # "Monaco (Principality of)"
|
||
"255" => "Madeira", # "Madeira - Portugal"
|
||
"256" => "Malta",
|
||
"257" => "Norway",
|
||
"258" => "Norway",
|
||
"259" => "Norway",
|
||
"261" => "Poland", # "Poland (Republic of)"
|
||
"262" => "Montenegro",
|
||
"263" => "Portugal",
|
||
"264" => "Romania",
|
||
"265" => "Sweden",
|
||
"266" => "Sweden",
|
||
"267" => "Slovakia", # "Slovak Republic"
|
||
"268" => "San Marino", # "San Marino (Republic of)"
|
||
"269" => "Switzerland", # "Switzerland (Confederation of)"
|
||
"270" => "Czech Rep.", # "Czech Republic"
|
||
"271" => "Turkey",
|
||
"272" => "Ukraine",
|
||
"273" => "Russian Fed.", # "Russian Federation"
|
||
"274" => "Macedonia", # "The Former Yugoslav Republic of Macedonia"
|
||
"275" => "Latvia", # "Latvia (Republic of)"
|
||
"276" => "Estonia", # "Estonia (Republic of)"
|
||
"277" => "Lithuania", # "Lithuania (Republic of)"
|
||
"278" => "Slovenia", # "Slovenia (Republic of)"
|
||
"279" => "Serbia", # "Serbia (Republic of)"
|
||
"301" => "Anguilla", # "Anguilla - United Kingdom of Great Britain and Northern Ireland"
|
||
"303" => "Alaska,U.S.", # "Alaska (State of) - United States of America"
|
||
"304" => "Antigua & Barbuda", # "Antigua and Barbuda"
|
||
"305" => "Antigua & Barbuda", # "Antigua and Barbuda"
|
||
"306" => "Sint Maarten", # "Sint Maarten (Dutch part) - Netherlands (Kingdom of the)"
|
||
"306" => "Bonaire, Sint Eustatius & Saba", # "Bonaire, Sint Eustatius and Saba - Netherlands (Kingdom of the)"
|
||
"306" => "Cura<72>ao", # "Cura<72>ao - Netherlands (Kingdom of the)"
|
||
"307" => "Aruba", # "Aruba - Netherlands (Kingdom of the)"
|
||
"308" => "Bahamas", # "Bahamas (Commonwealth of the)"
|
||
"309" => "Bahamas", # "Bahamas (Commonwealth of the)"
|
||
"310" => "Bermuda", # "Bermuda - United Kingdom of Great Britain and Northern Ireland"
|
||
"311" => "Bahamas", # "Bahamas (Commonwealth of the)"
|
||
"312" => "Belize",
|
||
"314" => "Barbados",
|
||
"316" => "Canada",
|
||
"319" => "Cayman Is.", # "Cayman Islands - United Kingdom of Great Britain and Northern Ireland"
|
||
"321" => "Costa Rica",
|
||
"323" => "Cuba",
|
||
"325" => "Dominica", # "Dominica (Commonwealth of)"
|
||
"327" => "Dominican Rep.", # "Dominican Republic"
|
||
"329" => "Guadeloupe", # "Guadeloupe (French Department of) - France"
|
||
"330" => "Grenada",
|
||
"331" => "Greenland", # "Greenland - Denmark"
|
||
"332" => "Guatemala", # "Guatemala (Republic of)"
|
||
"334" => "Honduras", # "Honduras (Republic of)"
|
||
"336" => "Haiti", # "Haiti (Republic of)"
|
||
"338" => "U.S.", # "United States of America"
|
||
"339" => "Jamaica",
|
||
"341" => "St. Kitts & Nevis", # "Saint Kitts and Nevis (Federation of)"
|
||
"343" => "St. Lucia",
|
||
"345" => "Mexico",
|
||
"347" => "Martinique", # "Martinique (French Department of) - France"
|
||
"348" => "Montserrat", # "Montserrat - United Kingdom of Great Britain and Northern Ireland"
|
||
"350" => "Nicaragua",
|
||
"351" => "Panama", # "Panama (Republic of)"
|
||
"352" => "Panama", # "Panama (Republic of)"
|
||
"353" => "Panama", # "Panama (Republic of)"
|
||
"354" => "Panama", # "Panama (Republic of)"
|
||
"355" => "Panama", # "Panama (Republic of)"
|
||
"356" => "Panama", # "Panama (Republic of)"
|
||
"357" => "Panama", # "Panama (Republic of)"
|
||
"358" => "Puerto Rico,U.S.", # "Puerto Rico - United States of America"
|
||
"359" => "El Salvador", # "El Salvador (Republic of)"
|
||
"361" => "St. Pierre & Miquelon", # "Saint Pierre and Miquelon (Territorial Collectivity of) - France"
|
||
"362" => "Trinidad & Tobago", # "Trinidad and Tobago"
|
||
"364" => "Turks & Caicos Is.", # "Turks and Caicos Islands - United Kingdom of Great Britain and Northern Ireland"
|
||
"366" => "U.S.", # "United States of America"
|
||
"367" => "U.S.", # "United States of America"
|
||
"368" => "U.S.", # "United States of America"
|
||
"369" => "U.S.", # "United States of America"
|
||
"370" => "Panama", # "Panama (Republic of)"
|
||
"371" => "Panama", # "Panama (Republic of)"
|
||
"372" => "Panama", # "Panama (Republic of)"
|
||
"373" => "Panama", # "Panama (Republic of)"
|
||
"374" => "Panama", # "Panama (Republic of)"
|
||
"375" => "St. Vincent & Grenadines", # "Saint Vincent and the Grenadines"
|
||
"376" => "St. Vincent & Grenadines", # "Saint Vincent and the Grenadines"
|
||
"377" => "St. Vincent & Grenadines", # "Saint Vincent and the Grenadines"
|
||
"378" => "British Virgin Is.", # "British Virgin Islands - United Kingdom of Great Britain and Northern Ireland"
|
||
"379" => "U.S. Virgin Is.", # "United States Virgin Islands - United States of America"
|
||
"401" => "Afghanistan",
|
||
"403" => "Saudi Arabia", # "Saudi Arabia (Kingdom of)"
|
||
"405" => "Bangladesh", # "Bangladesh (People's Republic of)"
|
||
"408" => "Bahrain", # "Bahrain (Kingdom of)"
|
||
"410" => "Bhutan", # "Bhutan (Kingdom of)"
|
||
"412" => "China", # "China (People's Republic of)"
|
||
"413" => "China", # "China (People's Republic of)"
|
||
"414" => "China", # "China (People's Republic of)"
|
||
"416" => "Taiwan", # "Taiwan (Province of China) - China (People's Republic of)"
|
||
"417" => "Sri Lanka", # "Sri Lanka (Democratic Socialist Republic of)"
|
||
"419" => "India", # "India (Republic of)"
|
||
"422" => "Iran", # "Iran (Islamic Republic of)"
|
||
"423" => "Azerbaijan", # "Azerbaijan (Republic of)"
|
||
"425" => "Iraq", # "Iraq (Republic of)"
|
||
"428" => "Israel", # "Israel (State of)"
|
||
"431" => "Japan",
|
||
"432" => "Japan",
|
||
"434" => "Turkmenistan",
|
||
"436" => "Kazakhstan", # "Kazakhstan (Republic of)"
|
||
"437" => "Uzbekistan", # "Uzbekistan (Republic of)"
|
||
"438" => "Jordan", # "Jordan (Hashemite Kingdom of)"
|
||
"440" => "Korea", # "Korea (Republic of)"
|
||
"441" => "Korea", # "Korea (Republic of)"
|
||
"443" => "Palestine", # "State of Palestine (In accordance with Resolution 99 Rev. Guadalajara, 2010)"
|
||
"445" => "N. Korea", # "Democratic People's Republic of Korea"
|
||
"447" => "Kuwait", # "Kuwait (State of)"
|
||
"450" => "Lebanon",
|
||
"451" => "Kyrgyzstan", # "Kyrgyz Republic"
|
||
"453" => "Macao", # "Macao (Special Administrative Region of China) - China (People's Republic of)"
|
||
"455" => "Maldives", # "Maldives (Republic of)"
|
||
"457" => "Mongolia",
|
||
"459" => "Nepal", # "Nepal (Federal Democratic Republic of)"
|
||
"461" => "Oman", # "Oman (Sultanate of)"
|
||
"463" => "Pakistan", # "Pakistan (Islamic Republic of)"
|
||
"466" => "Qatar", # "Qatar (State of)"
|
||
"468" => "Syria", # "Syrian Arab Republic"
|
||
"470" => "United Arab Emirates",
|
||
"471" => "United Arab Emirates",
|
||
"472" => "Tajikistan", # "Tajikistan (Republic of)"
|
||
"473" => "Yemen", # "Yemen (Republic of)"
|
||
"475" => "Yemen", # "Yemen (Republic of)"
|
||
"477" => "Hong Kong", # "Hong Kong (Special Administrative Region of China) - China (People's Republic of)"
|
||
"478" => "Bosnia & Herzegovina", # "Bosnia and Herzegovina"
|
||
"501" => "Adelie Land", # "Adelie Land - France"
|
||
"503" => "Australia",
|
||
"506" => "Myanmar", # "Myanmar (Union of)"
|
||
"508" => "Brunei Darussalam",
|
||
"510" => "Micronesia", # "Micronesia (Federated States of)"
|
||
"511" => "Palau", # "Palau (Republic of)"
|
||
"512" => "New Zealand",
|
||
"514" => "Cambodia", # "Cambodia (Kingdom of)"
|
||
"515" => "Cambodia", # "Cambodia (Kingdom of)"
|
||
"516" => "Christmas Is.", # "Christmas Island (Indian Ocean) - Australia"
|
||
"518" => "Cook Is.", # "Cook Islands - New Zealand"
|
||
"520" => "Fiji", # "Fiji (Republic of)",
|
||
"523" => "Cocos (Keeling) Is.", # "Cocos (Keeling) Islands - Australia"
|
||
"525" => "Indonesia", # "Indonesia (Republic of)"
|
||
"529" => "Kiribati", # "Kiribati (Republic of)"
|
||
"531" => "Laos", # "Lao People's Democratic Republic"
|
||
"533" => "Malaysia",
|
||
"536" => "N. Mariana Is,U.S.", # "Northern Mariana Islands (Commonwealth of the) - United States of America"
|
||
"538" => "Marshall Is.", # "Marshall Islands (Republic of the)"
|
||
"540" => "New Caledonia", # "New Caledonia - France"
|
||
"542" => "Niue", # "Niue - New Zealand"
|
||
"544" => "Nauru", # "Nauru (Republic of)"
|
||
"546" => "French Polynesia", # "French Polynesia - France"
|
||
"548" => "Philippines", # "Philippines (Republic of the)"
|
||
"553" => "Papua New Guinea",
|
||
"555" => "Pitcairn Is.", # "Pitcairn Island - United Kingdom of Great Britain and Northern Ireland"
|
||
"557" => "Solomon Islands",
|
||
"559" => "American Samoa,U.S.", # "American Samoa - United States of America"
|
||
"561" => "Samoa", # "Samoa (Independent State of)"
|
||
"563" => "Singapore", # "Singapore (Republic of)"
|
||
"564" => "Singapore", # "Singapore (Republic of)"
|
||
"565" => "Singapore", # "Singapore (Republic of)"
|
||
"566" => "Singapore", # "Singapore (Republic of)"
|
||
"567" => "Thailand",
|
||
"570" => "Tonga", # "Tonga (Kingdom of)"
|
||
"572" => "Tuvalu",
|
||
"574" => "Viet Nam", # "Viet Nam (Socialist Republic of)"
|
||
"576" => "Vanuatu", # "Vanuatu (Republic of)"
|
||
"577" => "Vanuatu", # "Vanuatu (Republic of)"
|
||
"578" => "Wallis & Futuna Is.", # "Wallis and Futuna Islands - France"
|
||
"601" => "S. Africa", # "South Africa (Republic of)"
|
||
"603" => "Angola", # "Angola (Republic of)"
|
||
"605" => "Algeria", # "Algeria (People's Democratic Republic of)"
|
||
"607" => "St. Paul & Amsterdam Is.", # "Saint Paul and Amsterdam Islands - France"
|
||
"608" => "Ascension Is.", # "Ascension Island - United Kingdom of Great Britain and Northern Ireland"
|
||
"609" => "Burundi", # "Burundi (Republic of)"
|
||
"610" => "Benin", # "Benin (Republic of)"
|
||
"611" => "Botswana", # "Botswana (Republic of)"
|
||
"612" => "Central African Rep.", # "Central African Republic"
|
||
"613" => "Cameroon", # "Cameroon (Republic of)"
|
||
"615" => "Congo", # "Congo (Republic of the)"
|
||
"616" => "Comoros", # "Comoros (Union of the)"
|
||
"617" => "Cape Verde", # "Cabo Verde (Republic of)"
|
||
"618" => "Crozet Archipelago", # "Crozet Archipelago - France"
|
||
"619" => "C<>te d'Ivoire", # "C<>te d'Ivoire (Republic of)"
|
||
"620" => "Comoros", # "Comoros (Union of the)"
|
||
"621" => "Djibouti", # "Djibouti (Republic of)"
|
||
"622" => "Egypt", # "Egypt (Arab Republic of)"
|
||
"624" => "Ethiopia", # "Ethiopia (Federal Democratic Republic of)"
|
||
"625" => "Eritrea",
|
||
"626" => "Gabonese Rep.", # "Gabonese Republic"
|
||
"627" => "Ghana",
|
||
"629" => "Gambia", # "Gambia (Republic of the)"
|
||
"630" => "Guinea-Bissau", # "Guinea-Bissau (Republic of)"
|
||
"631" => "Equatorial Guinea", # "Equatorial Guinea (Republic of)"
|
||
"632" => "Guinea", # "Guinea (Republic of)"
|
||
"633" => "Burkina Faso",
|
||
"634" => "Kenya", # "Kenya (Republic of)"
|
||
"635" => "Kerguelen Is.", # "Kerguelen Islands - France"
|
||
"636" => "Liberia", # "Liberia (Republic of)"
|
||
"637" => "Liberia", # "Liberia (Republic of)"
|
||
"638" => "S. Sudan", # "South Sudan (Republic of)"
|
||
"642" => "Libya",
|
||
"644" => "Lesotho", # "Lesotho (Kingdom of)"
|
||
"645" => "Mauritius", # "Mauritius (Republic of)"
|
||
"647" => "Madagascar", # "Madagascar (Republic of)"
|
||
"649" => "Mali", # "Mali (Republic of)"
|
||
"650" => "Mozambique", # "Mozambique (Republic of)"
|
||
"654" => "Mauritania", # "Mauritania (Islamic Republic of)"
|
||
"655" => "Malawi",
|
||
"656" => "Niger", # "Niger (Republic of the)"
|
||
"657" => "Nigeria", # "Nigeria (Federal Republic of)"
|
||
"659" => "Namibia", # "Namibia (Republic of)"
|
||
"660" => "Reunion", # "Reunion (French Department of) - France"
|
||
"661" => "Rwanda", # "Rwanda (Republic of)"
|
||
"662" => "Sudan", # "Sudan (Republic of the)"
|
||
"663" => "Senegal", # "Senegal (Republic of)"
|
||
"664" => "Seychelles", # "Seychelles (Republic of)"
|
||
"665" => "St. Helena", # "Saint Helena - United Kingdom of Great Britain and Northern Ireland"
|
||
"666" => "Somalia", # "Somalia (Federal Republic of)"
|
||
"667" => "Sierra Leone",
|
||
"668" => "Sao Tome & Principe", # "Sao Tome and Principe (Democratic Republic of)"
|
||
"669" => "Swaziland", # "Swaziland (Kingdom of)"
|
||
"670" => "Chad", # "Chad (Republic of)"
|
||
"671" => "Togolese Rep.", # "Togolese Republic"
|
||
"672" => "Tunisia",
|
||
"674" => "Tanzania", # "Tanzania (United Republic of)"
|
||
"675" => "Uganda", # "Uganda (Republic of)",
|
||
"676" => "Dem. Rep. of Congo", # "Democratic Republic of the Congo"
|
||
"677" => "Tanzania", # "Tanzania (United Republic of)"
|
||
"678" => "Zambia", # "Zambia (Republic of)"
|
||
"679" => "Zimbabwe", # "Zimbabwe (Republic of)"
|
||
"701" => "Argentine Rep.", # "Argentine Republic"
|
||
"710" => "Brazil", # "Brazil (Federative Republic of)"
|
||
"720" => "Bolivia", # "Bolivia (Plurinational State of)"
|
||
"725" => "Chile",
|
||
"730" => "Columbia", # "Colombia (Republic of)"
|
||
"735" => "Ecuador",
|
||
"740" => "Falkland Is.", # "Falkland Islands (Malvinas) - United Kingdom of Great Britain and Northern Ireland"
|
||
"745" => "Guiana", # "Guiana (French Department of) - France"
|
||
"750" => "Guyana",
|
||
"755" => "Paraguay", # "Paraguay (Republic of)"
|
||
"760" => "Peru",
|
||
"765" => "Suriname", # "Suriname (Republic of)"
|
||
"770" => "Uruguay", # "Uruguay (Eastern Republic of)"
|
||
"775" => "Venezuela", # "Venezuela (Bolivarian Republic of)"
|
||
);
|
||
|
||
|
||
|
||
|
||
$xastir_user = shift;
|
||
if (defined($xastir_user)) {
|
||
chomp $xastir_user;
|
||
}
|
||
if ( (!defined($xastir_user)) || ($xastir_user eq "") ) {
|
||
print "Please enter a callsign for Xastir injection, but not Xastir's callsign/SSID!\n";
|
||
die;
|
||
}
|
||
$xastir_user =~ tr/a-z/A-Z/;
|
||
|
||
|
||
$xastir_pass = shift;
|
||
if (defined($xastir_pass)) {
|
||
chomp $xastir_pass;
|
||
}
|
||
if ( (!defined($xastir_pass)) || ($xastir_pass eq "") ) {
|
||
print "Please enter a passcode for Xastir injection\n";
|
||
die;
|
||
}
|
||
|
||
|
||
# Enable pipe mode by adding the "--pipe" flag, then can pipe
|
||
# packets into the program's STDIN for testing. Without this flag
|
||
# we'll get our packets from "rtl_ais" via a UDP server we set up.
|
||
# Enable logging mode by adding the "--logging" flag.
|
||
$pipe_mode = 0;
|
||
$logging_mode = 0;
|
||
$pipe_flag = shift;
|
||
if (defined($pipe_flag) && $pipe_flag ne "") {
|
||
chomp $pipe_flag;
|
||
if ($pipe_flag eq "--pipe") { $pipe_mode = 1; }
|
||
if ($pipe_flag eq "--logging") { $logging_mode = 1; }
|
||
}
|
||
|
||
# Try again in case there are two flags
|
||
$pipe_flag = shift;
|
||
if (defined($pipe_flag) && $pipe_flag ne "") {
|
||
chomp $pipe_flag;
|
||
if ($pipe_flag eq "--pipe") { $pipe_mode = 1; }
|
||
if ($pipe_flag eq "--logging") { $logging_mode = 1; }
|
||
}
|
||
|
||
|
||
if ($enable_tx) {
|
||
# Check Xastir's callsign/SSID to make sure we don't have a collision. This
|
||
# will prevent Xastir adopting the Items as its own and retransmitting them.
|
||
# "xastir_udp_client localhost 2023 <callsign> <passcode> -identify"
|
||
# "Received: WE7U-13"
|
||
#
|
||
$injection_call = $xastir_user;
|
||
$injection_call =~ s/-\d+//; # Get rid of dash and numbers
|
||
|
||
$injection_ssid = $xastir_user;
|
||
$injection_ssid =~ s/\w+//; # Get rid of letters
|
||
$injection_ssid =~ s/-//; # Get rid of dash
|
||
if ($injection_ssid eq "") { $injection_ssid = 0; }
|
||
|
||
# Find out Callsign/SSID of Xastir instance
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass -identify`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
($remote_call, $remote_ssid) = split('-', $result);
|
||
|
||
chomp($remote_call);
|
||
$remote_call =~ s/Received:\s+//;
|
||
|
||
if ( !defined($remote_ssid) ) { $remote_ssid = ""; }
|
||
if ($remote_ssid ne "") { chomp($remote_ssid); }
|
||
if ($remote_ssid eq "") { $remote_ssid = 0; }
|
||
|
||
#print "$remote_call $remote_ssid $injection_call $injection_ssid\n";
|
||
#if ($remote_call eq $injection_call) { print "Call matches\n"; }
|
||
#if ($remote_ssid == $injection_ssid) { print "SSID matches\n"; }
|
||
|
||
if ( ($remote_call eq $injection_call)
|
||
&& ($remote_ssid == $injection_ssid) ) {
|
||
$remote_ssid++;
|
||
$remote_ssid%= 16; # Increment by 1 mod 16
|
||
$xastir_user = "$remote_call-$remote_ssid";
|
||
print "Injection conflict. Corrected. New user = $xastir_user\n";
|
||
}
|
||
}
|
||
|
||
|
||
if ($pipe_mode) {
|
||
#
|
||
# Debug mode:
|
||
# Read from pipe in a loop
|
||
#
|
||
while (<STDIN>) {
|
||
&process_line($_);
|
||
}
|
||
}
|
||
else {
|
||
#
|
||
# Not debug mode:
|
||
# Create UDP server which connects to rtl_ais to get AIS data
|
||
# Listen on localhost port 10110 for UDP packets
|
||
#
|
||
my $host = "localhost";
|
||
my $port = 10110;
|
||
my $protocol = 'udp';
|
||
|
||
my $server = IO::Socket::INET->new(
|
||
#PeerAddr => $host,
|
||
LocalPort => $port,
|
||
Proto => $protocol,
|
||
Type => SOCK_DGRAM
|
||
) or die "Socket could not be created, failed with error: $!\n";
|
||
|
||
|
||
# Main processing loop. Fetch packets as they are received via
|
||
# UDP from port 10110. Create APRS packets out of the interesting
|
||
# ones and inject them into Xastir at port 2023 using UDP.
|
||
#
|
||
while ($server->recv($received_data, 1024)) {
|
||
|
||
#my $peer_address = $server->peerhost();
|
||
#my $peer_port = $server->peerport();
|
||
#print "Message was received from: $peer_address, $peer_port\n";
|
||
#print "$received_data";
|
||
|
||
&process_line($received_data);
|
||
|
||
} # End of main processing loop
|
||
}
|
||
|
||
|
||
|
||
sub process_line () {
|
||
|
||
$received_data = shift;
|
||
|
||
if ($pipe_mode) {
|
||
$received_data =~ s/\n//; # Remove LF if present
|
||
$received_data =~ s/\r//; # Remove CR if present
|
||
}
|
||
else { # Live mode, receiving from rtl-ais. CRLF line-ends.
|
||
chop($received_data);
|
||
chop($received_data);
|
||
}
|
||
|
||
# Check for empty line
|
||
if ($received_data eq "") { return; }
|
||
|
||
# Verify the checksum: Refuse to process a message if it's bad.
|
||
# Computed on entire sentence including the AIVDM tag but excluding
|
||
# the leading "!" character.
|
||
my $str = $received_data;
|
||
$str =~ s/^!//; # Chop off leading '!'
|
||
#$str =~ s/(,\d)\*../$1/; # Chop to '*' char with leading digit and two trailing chars
|
||
$str =~ s/(,\d)\*...*/$1/; # Chop to '*' char with leading digit and at least two trailing chars
|
||
#print "$str\n";
|
||
|
||
# Received checksum. Catches the correct bytes except
|
||
# for those cases where the NMEA sentences continues
|
||
# on past the checksum.
|
||
my $str2 = $received_data;
|
||
#$str2 =~ s/.*(..)/$1/; # Catches only a checksum at the end
|
||
$str2 =~ s/.*,\d\*(..).*/$1/; # Catches checksum at end or middle
|
||
|
||
# Compute the checksum on the string between the '!' and the '*' chars.
|
||
$j = length($str);
|
||
my $sum = 0;
|
||
my $i;
|
||
for ($i = 0; $i <= $j; $i++) {
|
||
my $c = ord( substr($str, $i, 1) );
|
||
$sum = $sum ^ $c;
|
||
}
|
||
my $sum2 = sprintf( "%02X", $sum);
|
||
if ($str2 ne $sum2) {
|
||
#print "$received_data\n";
|
||
#print "$str\t$str2\t$sum2\n";
|
||
printf("***** Bad checksum: Received:%s Computed:%s $received_data\n\n", $str2, $sum2);
|
||
return(); # Skip this sentence
|
||
}
|
||
else {
|
||
#print "$str2\t$sum2\n";
|
||
}
|
||
|
||
@pieces = split( ',', $received_data);
|
||
|
||
# $pieces[0] = NMEA message type
|
||
# $pieces[1] = Number of sentences
|
||
# $pieces[2] = Sequential Message ID
|
||
# $pieces[3] = Sentence number
|
||
# $pieces[4] = AIS Channel
|
||
# $pieces[5] = Encoded AIS data
|
||
# $pieces[6] = End of data & NMEA Checksum
|
||
|
||
$data = $pieces[5];
|
||
|
||
# Encoded AIS data: Each ASCII character represents 6 bits.
|
||
# Subtract 48. If still a decimal number > 40, subtract 8.
|
||
# Convert to binary.
|
||
$bin_string = "";
|
||
$len = length($data);
|
||
for ($i = 0; $i < $len; $i++) {
|
||
$c = substr($data, $i, 1);
|
||
#print("$c");
|
||
$d = ord($c) - 48;
|
||
if ($d > 40) { $d = $d - 8; }
|
||
#print "\t$d\n";
|
||
|
||
# Convert each character to 6 bits of binary.
|
||
# Add the binary strings together to make one
|
||
# long binary string. Pull out the bits we need
|
||
# to decode each piece of the message.
|
||
#
|
||
$b = &dec2bin($d);
|
||
#print "\t$b\n";
|
||
$bin_string = $bin_string . $b;
|
||
}
|
||
#print "$bin_string\n";
|
||
#printf("%d\n", length($data) );
|
||
#printf("%d\n", length($bin_string) );
|
||
|
||
|
||
# If first six bits are 000001 (message type 1):
|
||
# MMSI Number - bit 8 for 30 bits
|
||
# HDG - bit 128 for 9 bits
|
||
# COG - bit 116 for 12 bits (and divide by 10)
|
||
# SOG - bit 50 for 10 bits (and divide by 10)
|
||
# Lat - bit 89 for 27 bits (a signed binary number, divide by 600,000)
|
||
# Lon - bit 61 for 28 bits (a signed binary number, divide by 600,000)
|
||
#
|
||
#if (substr($bin_string,0,6) eq "000001") {
|
||
# print "Message Type: 1\n";
|
||
#}
|
||
#else {
|
||
# printf"Message Type: %s\n", substr($bin_string,0,6);
|
||
# die;
|
||
#}
|
||
|
||
$bmessage_type = substr($bin_string, 0, 6);
|
||
$message_type = &bin2dec($bmessage_type);
|
||
|
||
if ($statistics_mode) {
|
||
# Keep a count of the received message types
|
||
if ($message_type < 28) {
|
||
$message_type_count[$message_type] += 1;
|
||
}
|
||
|
||
# Since there's no message type 0 use it's array location for total
|
||
$message_type_count[0] += 1;
|
||
|
||
# Print the totals every 100 messages
|
||
if ( ++$statistics_count >= 100 ) {
|
||
printf("Stats: ");
|
||
for (my $jj = 0; $jj < 28; $jj++) {
|
||
if ($jj == 0) { printf("%d", $message_type_count[$jj]); }
|
||
else { printf(",%d", $message_type_count[$jj]); }
|
||
}
|
||
print "\n\n";
|
||
$statistics_count = 0;
|
||
}
|
||
}
|
||
|
||
# The escape clause for bad message types is here
|
||
# so that they get included in the total count above.
|
||
# Don't move this escape above that code.
|
||
if ( ($message_type > 27) || ($message_type == 0) ) {
|
||
# print "***** Undefined Msg Type $message_type: $received_data\n\n";
|
||
return();
|
||
}
|
||
|
||
#print "Message Type: $message_type\n";
|
||
# If not messages types 1, 2, 3, skip for now
|
||
if ( $message_type != 1
|
||
&& $message_type != 2
|
||
&& $message_type != 3
|
||
&& $message_type != 4
|
||
&& $message_type != 5
|
||
&& $message_type != 9
|
||
&& $message_type != 18
|
||
&& $message_type != 19
|
||
&& $message_type != 21
|
||
&& $message_type != 24
|
||
&& $message_type != 27
|
||
) {
|
||
# Not something we decode so don't process the sentence.
|
||
if ($print_others == 1) { print " Msg Type: $message_type\n\n"; }
|
||
return;
|
||
}
|
||
|
||
if ( $message_type == 1
|
||
|| $message_type == 2
|
||
|| $message_type == 3 ) {
|
||
if ($print_123) { print " Msg Type: $message_type\n"; }
|
||
&process_types_1_2_3($message_type);
|
||
}
|
||
|
||
elsif ( $message_type == 4 ) {
|
||
if ($print_4) { print " Msg Type: $message_type\n"; }
|
||
&process_type_4($message_type);
|
||
}
|
||
|
||
elsif ( $message_type == 5 ) {
|
||
if ($print_5) { print " Msg Type: $message_type\n"; }
|
||
&process_type_5($message_type);
|
||
}
|
||
|
||
elsif ( $message_type == 9 ) {
|
||
if ($print_9) { print " Msg Type: $message_type\n"; }
|
||
&process_type_9($message_type);
|
||
}
|
||
|
||
elsif ( $message_type == 18 ) {
|
||
if ($print_18) { print " Msg Type: $message_type\n"; }
|
||
&process_type_18($message_type);
|
||
|
||
}
|
||
|
||
elsif ( $message_type == 19 ) {
|
||
if ($print_19) { print " Msg Type: $message_type\n"; }
|
||
&process_type_19($message_type);
|
||
|
||
}
|
||
|
||
elsif ( $message_type == 21 ) {
|
||
if ($print_21) { print " Msg Type: $message_type\n"; }
|
||
&process_type_21($message_type);
|
||
|
||
}
|
||
|
||
elsif ( $message_type == 24 ) {
|
||
#if ($print_24) { print " Msg Type: $message_type\t"; } # This is printed from the subroutine instead
|
||
&process_type_24($message_type);
|
||
}
|
||
|
||
elsif ( $message_type == 27 ) {
|
||
if ($print_27) { print " Msg Type: $message_type\n"; }
|
||
if ($print_27) { print "$received_data\n"; }
|
||
&process_type_27($message_type);
|
||
}
|
||
|
||
} # End of "process_line"
|
||
|
||
|
||
|
||
# Message types 1, 2 and 3: Position Report Class A
|
||
# These messages should have 168 bits total
|
||
#
|
||
sub process_types_1_2_3() {
|
||
|
||
#if ( length($bin_string) < 168 ) {
|
||
if ( length($bin_string) < 128 ) {
|
||
#printf("123\t%s\t%d\n", $bin_string, length($bin_string));
|
||
#printf("123\t%d\n", length($bin_string));
|
||
if ($print_123) { print "Msg too short\n\n"; }
|
||
return;
|
||
}
|
||
|
||
# substr($bin_string, 0, 6); # Message Type
|
||
#my $brepeat_indicator = substr($bin_string, 6, 2);
|
||
my $bUserID = substr($bin_string, 8, 30); # MMSI
|
||
my $bnavStatus = substr($bin_string, 38, 4);
|
||
#my $brateOfTurn = substr($bin_string, 42, 8);
|
||
my $bSpeedOverGnd = substr($bin_string, 50, 10); #####
|
||
#my $bpositionAccuracy = substr($bin_string, 60, 1);
|
||
my $bLongitude = substr($bin_string, 61, 28); #####
|
||
my $bLatitude = substr($bin_string, 89, 27); #####
|
||
my $bCourseOverGnd = substr($bin_string, 116, 12); #####
|
||
#my $btrueHeading = substr($bin_string, 128, 9);
|
||
#my $btimestamp = substr($bin_string, 137, 6);
|
||
#my $bspecialManeuver = substr($bin_string, 143, 2);
|
||
#my $bspare = substr($bin_string, 145, 3);
|
||
#my $bRAIM = substr($bin_string, 148, 1);
|
||
#my $bCommState = substr($bin_string, 149, 19);
|
||
|
||
my $userID = &bin2dec($bUserID);
|
||
if ($print_123) { print " User ID: $userID\n"; }
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_123) { print " Country: $country\n"; }
|
||
|
||
my $navStatus = &bin2dec($bnavStatus);
|
||
my $navStatusTxt = "";
|
||
if ($navStatus == 0) { $navStatusTxt = "Under_way:engine"; }
|
||
elsif ($navStatus == 1) { $navStatusTxt = "At_anchor"; }
|
||
elsif ($navStatus == 2) { $navStatusTxt = "Not_under_command"; }
|
||
elsif ($navStatus == 3) { $navStatusTxt = "Restricted_maneuverability"; }
|
||
elsif ($navStatus == 4) { $navStatusTxt = "Constrained_by_draught"; }
|
||
elsif ($navStatus == 5) { $navStatusTxt = "Moored"; }
|
||
elsif ($navStatus == 6) { $navStatusTxt = "Aground"; }
|
||
elsif ($navStatus == 7) { $navStatusTxt = "Fishing"; }
|
||
elsif ($navStatus == 8) { $navStatusTxt = "Under_way:sailing"; }
|
||
#elsif ($navStatus == 9) { $navStatusTxt = "Reserved"; }
|
||
#elsif ($navStatus == 10) { $navStatusTxt = "Reserved"; }
|
||
#elsif ($navStatus == 11) { $navStatusTxt = "Reserved"; }
|
||
#elsif ($navStatus == 12) { $navStatusTxt = "Reserved"; }
|
||
#elsif ($navStatus == 13) { $navStatusTxt = "Reserved"; }
|
||
elsif ($navStatus == 14) { $navStatusTxt = "AIS-SART_is_active"; }
|
||
#elsif ($navStatus == 15) { $navStatusTxt = "Not defined"; }
|
||
|
||
# NOTE: 0-359 degrees
|
||
# NOTE: 360 = N/A
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $courseOverGnd = &bin2dec($bCourseOverGnd) / 10 ;
|
||
my $course = "";
|
||
if ($courseOverGnd == 360) {
|
||
$course = "...";
|
||
}
|
||
elsif ($courseOverGnd == 0) {
|
||
$course = "360";
|
||
}
|
||
else {
|
||
$course = sprintf("%03d", $courseOverGnd);
|
||
}
|
||
if ($print_123) { print " Course: $courseOverGnd\n"; }
|
||
|
||
# NOTE: 0 to 102 knots
|
||
# NOTE: 102.3 = N/A
|
||
# NOTE: 102.2 = 102.2 knots or higher
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $speedOverGnd = &bin2dec($bSpeedOverGnd) / 10;
|
||
my $speed = "";
|
||
if ($speedOverGnd == 102.3) {
|
||
$speed = "...";
|
||
}
|
||
else {
|
||
$speed = sprintf("%03d", $speedOverGnd);
|
||
}
|
||
if ($print_123) { print " Speed: $speedOverGnd\n"; }
|
||
|
||
# NOTE: -90 to +90
|
||
# NOTE: 91 = N/A
|
||
my $latitude = &signedBin2dec($bLatitude) / 600000.0;
|
||
if ($latitude == 91) {
|
||
if ($print_123) { print "\n"; }
|
||
return();
|
||
}
|
||
my $NS;
|
||
if ($print_123) { printf(" Latitude: %07.5f\n", $latitude); }
|
||
if ($latitude >= 0.0) {
|
||
$NS = 'N';
|
||
} else {
|
||
$NS = 'S';
|
||
$latitude = abs($latitude);
|
||
}
|
||
my $latdeg = int($latitude);
|
||
my $latmins = $latitude - $latdeg;
|
||
my $latmins2 = $latmins * 60.0;
|
||
my $lat = sprintf("%02d%05.2f%s", $latdeg, $latmins2, $NS);
|
||
|
||
# NOTE: -180 to +180
|
||
# NOTE: 181 = N/A
|
||
my $longitude = &signedBin2dec($bLongitude) / 600000.0;
|
||
if ($longitude == 181) {
|
||
if ($print_123) { print "\n"; }
|
||
return();
|
||
}
|
||
my $EW;
|
||
if ($print_123) { printf("Longitude: %08.5f\n", $longitude); }
|
||
if ($longitude >= 0.0) {
|
||
$EW = 'E';
|
||
} else {
|
||
$EW = 'W';
|
||
$longitude = abs($longitude);
|
||
}
|
||
my $londeg = int($longitude);
|
||
my $lonmins = $longitude - $londeg;
|
||
my $lonmins2 = $lonmins * 60.0;
|
||
my $lon = sprintf("%03d%05.2f%s", $londeg, $lonmins2, $EW);
|
||
|
||
my $symbol = "s";
|
||
|
||
# Set up to add country or vessel_name + country to comment field
|
||
my $vesselTag = " ($country)";
|
||
if ( defined($vessel_hash{$userID}) ) { $vesselTag = " " . $vessel_hash{$userID} . " (" . $country . ")"; }
|
||
|
||
my $aprs= &escape_from_shell("$xastir_user>APRS:)$userID!$lat/$lon$symbol$course/$speed $navStatusTxt$vesselTag");
|
||
if ($print_123) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
my $result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
|
||
# Assign tactical call = "$userID + $country" or "$vesselName + $country"
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
my $temp;
|
||
if ( defined( $vessel_hash{$userID}) ) {
|
||
$temp = substr($vessel_hash{$userID} . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
}
|
||
else {
|
||
# No vessel name
|
||
# N7IPB: Display country of registry
|
||
$temp = substr("($country)", 0, 56); # Chop at 56 chars
|
||
|
||
# WE7U: Display the 9-digit MMSI + country of registry
|
||
#$temp = substr($userID . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
}
|
||
if ( !defined($tactical_hash{$userID}) ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_123) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($print_123) { print "\n"; }
|
||
|
||
return();
|
||
}
|
||
|
||
# Message type 4: Base station report
|
||
# These messages should have 168 bits total
|
||
#
|
||
sub process_type_4() {
|
||
|
||
#if ( length($bin_string) < 168 ) {
|
||
if ( length($bin_string) < 128 ) {
|
||
#printf("123\t%s\t%d\n", $bin_string, length($bin_string));
|
||
#printf("123\t%d\n", length($bin_string));
|
||
if ($print_4) { print "Msg too short\n\n"; }
|
||
return;
|
||
}
|
||
|
||
my $bUserID = substr($bin_string, 8, 30); # MMSI
|
||
my $bLongitude = substr($bin_string, 79, 28); #####
|
||
my $bLatitude = substr($bin_string, 107, 27); #####
|
||
my $userID = &bin2dec($bUserID);
|
||
if ($print_4) { print " User ID: $userID\n"; }
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_4) { print " Country: $country\n"; }
|
||
|
||
|
||
# NOTE: -90 to +90
|
||
# NOTE: 91 = N/A
|
||
my $latitude = &signedBin2dec($bLatitude) / 600000.0;
|
||
if ($latitude == 91) { return(); }
|
||
my $NS;
|
||
if ($print_4) { printf(" Latitude: %07.5f\n", $latitude); }
|
||
if ($latitude >= 0.0) {
|
||
$NS = 'N';
|
||
} else {
|
||
$NS = 'S';
|
||
$latitude = abs($latitude);
|
||
}
|
||
my $latdeg = int($latitude);
|
||
my $latmins = $latitude - $latdeg;
|
||
my $latmins2 = $latmins * 60.0;
|
||
my $lat = sprintf("%02d%05.2f%s", $latdeg, $latmins2, $NS);
|
||
|
||
# NOTE: -180 to +180
|
||
# NOTE: 181 = N/A
|
||
my $longitude = &signedBin2dec($bLongitude) / 600000.0;
|
||
if ($longitude == 181) { return(); }
|
||
my $EW;
|
||
if ($print_4) { printf("Longitude: %08.5f\n", $longitude); }
|
||
if ($longitude >= 0.0) {
|
||
$EW = 'E';
|
||
} else {
|
||
$EW = 'W';
|
||
$longitude = abs($longitude);
|
||
}
|
||
my $londeg = int($longitude);
|
||
my $lonmins = $longitude - $londeg;
|
||
my $lonmins2 = $lonmins * 60.0;
|
||
my $lon = sprintf("%03d%05.2f%s", $londeg, $lonmins2, $EW);
|
||
|
||
my $symbol = "/";
|
||
|
||
# Set up to add country or vessel_name + country to comment field
|
||
my $vesselTag = " Fixed Base Station";
|
||
|
||
if ( defined($vessel_hash{$userID}) ) { $vesselTag = " " . $vessel_hash{$userID} . " (" . $country . ")"; }
|
||
|
||
my $aprs= &escape_from_shell("$xastir_user>APRS:)$userID!$lat/$lon$symbol $vesselTag");
|
||
if ($print_4) { print " APRS: $aprs\n"; }
|
||
&log_aprs($aprs);
|
||
if ($enable_tx) {
|
||
my $result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
|
||
# Assign tactical call = "$userID + $country" or "$vesselName + $country"
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
my $temp;
|
||
# if ( defined( $vessel_hash{$userID}) ) {
|
||
# $temp = substr($vessel_hash{$userID} . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
# }
|
||
# else {
|
||
# # No vessel name
|
||
# # N7IPB: Display country of registry
|
||
# $temp = substr("($country)", 0, 56); # Chop at 56 chars
|
||
#
|
||
# # WE7U: Display the 9-digit MMSI + country of registry
|
||
# #$temp = substr($userID . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
# }
|
||
# Force the tactical call to be FS since there are no names for Base Stations
|
||
#
|
||
$temp = "FS";
|
||
if ( !defined($tactical_hash{$userID}) ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_4) { print " APRS: $aprs\n"; }
|
||
&log_aprs($aprs);
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($print_4) { print "\n"; }
|
||
|
||
return();
|
||
|
||
|
||
}
|
||
|
||
|
||
# Message type 5: Static and Voyage Related Data (Vessel name, callsign, ship type)
|
||
# This message should have 424 bits total
|
||
#
|
||
sub process_type_5() {
|
||
|
||
#if ( length($bin_string) < 424 ) {
|
||
if ( length($bin_string) < 240 ) {
|
||
#printf("5\t%s\t%d\n", $bin_string, length($bin_string));
|
||
#printf("5\t%d\n", length($bin_string));
|
||
if ($print_5) { print "Msg too short\n\n"; }
|
||
return;
|
||
}
|
||
|
||
# substr($bin_string, 0, 6); # Message Type
|
||
# my $brepeat_indicator = substr($bin_string, 6, 2); # Repeat Indicator
|
||
my $bUserID = substr($bin_string, 8, 30); # MMSI
|
||
# substr($bin_string, 38, 2); # AIS Version
|
||
# substr($bin_string, 40, 30); # IMO Number
|
||
#my $bCallsign = substr($bin_string, 70, 42); # Call Sign
|
||
my $bVesselName = substr($bin_string, 112, 120); # Vessel Name #####
|
||
my $bShipType = substr($bin_string, 232, 8); # Ship Type: Page 114 of ITU spec, Table 53
|
||
# substr($bin_string, 240, 9); # Dimension to Bow
|
||
# substr($bin_string, 249, 9); # Dimension to Stern
|
||
# substr($bin_string, 258, 6); # Dimension to Port
|
||
# substr($bin_string, 264, 6); # Dimension to Starboard
|
||
# substr($bin_string, 270, 4); # Position Fix Type
|
||
# substr($bin_string, 274, 4); # ETA month (UTC)
|
||
# substr($bin_string, 278, 5); # ETA day (UTC)
|
||
# substr($bin_string, 283, 5); # ETA hour (UC)
|
||
# substr($bin_string, 288, 6); # ETA minute (UTC)
|
||
# substr($bin_string, 294, 8); # Draught
|
||
# substr($bin_string, 302, 120); # Destination
|
||
# substr($bin_string, 422, 1); # DTE
|
||
# substr($bin_string, 423, 1); # Spare
|
||
|
||
my $userID = &bin2dec($bUserID);
|
||
if ($print_5) { print " User ID: $userID\n"; }
|
||
|
||
#my $callSign = &bin2text($bCallsign);
|
||
#print "Callsign: $callSign\n";
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_5) { print " Country: $country\n"; }
|
||
|
||
my $vesselName = &bin2text($bVesselName);
|
||
$vesselName =~ s/\s+$//; # Remove extra spaces at end
|
||
|
||
$vessel_hash{$userID} = $vesselName;
|
||
# Update the persistent store
|
||
store \%vessel_hash , $persistentFileSpec;
|
||
|
||
if ($print_5) { print " Vessel: $vesselName\n"; }
|
||
|
||
my $shipTypeTxt = &decodeShipType($bShipType);
|
||
if ($print_5) { print "Ship Type: $shipTypeTxt\n"; }
|
||
|
||
# Assign tactical call = $vesselName + $country
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
# my $temp = substr($vesselName . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
my $temp = substr($vesselName . " ($shipTypeTxt:$country)", 0, 56); # Chop at 56 chars
|
||
|
||
if ( !defined($tactical_hash{$userID}) || $tactical_hash{$userID} ne $temp ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_5) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
if ($print_5) { print "\n"; }
|
||
|
||
return();
|
||
}
|
||
|
||
|
||
|
||
# Message type 9: Standard SAR Aircraft Position Report
|
||
# This message should have 168 bits total
|
||
#
|
||
sub process_type_9() {
|
||
|
||
#if ( length($bin_string) < 168 ) {
|
||
if ( length($bin_string) < 128 ) {
|
||
#printf("9\t%s\t%d\n", $bin_string, length($bin_string));
|
||
#printf("9\t%d\n", length($bin_string));
|
||
if ($print_9) { print "Msg too short\n\n"; }
|
||
return;
|
||
}
|
||
|
||
# substr($bin_string, 0, 6); # Message Type
|
||
# my $brepeat_indicator = substr($bin_string, 6, 2); # Repeat Indicator
|
||
my $bUserID = substr($bin_string, 8, 30); # MMSI
|
||
my $bAltitude = substr($bin_string, 38, 12); # Altitude
|
||
my $bSpeedOverGnd = substr($bin_string, 50, 10); # SOG
|
||
# substr($bin_string, 60, 1); # Position Accuracy
|
||
my $bLongitude = substr($bin_string, 61, 28); # Longitude
|
||
my $bLatitude = substr($bin_string, 89, 27); # Latitude
|
||
my $bCourseOverGnd = substr($bin_string, 116, 12); # Course Over Ground
|
||
# substr($bin_string, 128, 6); # Time Stamp
|
||
# substr($bin_string, 134, 1); # Altitude sensor
|
||
# substr($bin_string, 135, 7); # Spare
|
||
# substr($bin_string, 142, 1); # DTE
|
||
# substr($bin_string, 143, 3); # Spare
|
||
# substr($bin_string, 146, 1); # Assigned mode
|
||
# substr($bin_string, 147, 1); # RAIM flag
|
||
# substr($bin_string, 148, 1); # Comm state selector flag
|
||
# substr($bin_string, 149, 19); # Radio status
|
||
|
||
my $userID = &bin2dec($bUserID);
|
||
if ($print_9) { print " User ID: $userID\n"; }
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_9) { print " Country: $country\n"; }
|
||
|
||
# NOTE: 4095 = N/A
|
||
# NOTE: 4094 = 4094 meters or higher.
|
||
my $altitude_meters = &bin2dec($bAltitude);
|
||
my $altitude = "";
|
||
if ($altitude_meters != 4095) {
|
||
if ($altitude_meters == 4094) {
|
||
$altitude = sprintf( "%s%06d%s", " /A=", $altitude_meters * 3.28084, "(Higher than)" );
|
||
}
|
||
else {
|
||
$altitude = sprintf( "%s%06d", " /A=", $altitude_meters * 3.28084 );
|
||
}
|
||
if ($print_9) { print " Altitude: $altitude\n"; }
|
||
}
|
||
|
||
# NOTE: 0-359 degrees
|
||
# NOTE: 360 = N/A
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $courseOverGnd = &bin2dec($bCourseOverGnd) / 10 ;
|
||
my $course = "";
|
||
if ($courseOverGnd == 360) {
|
||
$course = "...";
|
||
}
|
||
elsif ($courseOverGnd == 0) {
|
||
$course = "360";
|
||
}
|
||
else {
|
||
$course = sprintf("%03d", $courseOverGnd);
|
||
}
|
||
if ($print_9) { print " Course: $courseOverGnd\n"; }
|
||
|
||
# NOTE: 0 to 102 knots
|
||
# NOTE: 102.3 = N/A
|
||
# NOTE: 102.2 = 102.2 knots or higher
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $speedOverGnd = &bin2dec($bSpeedOverGnd) / 10;
|
||
my $speed = "";
|
||
if ($speedOverGnd == 102.3) {
|
||
$speed = "...";
|
||
}
|
||
else {
|
||
$speed = sprintf("%03d", $speedOverGnd);
|
||
}
|
||
if ($print_9) { print " Speed: $speedOverGnd\n"; }
|
||
|
||
# NOTE: -90 to +90
|
||
# NOTE: 91 = N/A
|
||
my $latitude = &signedBin2dec($bLatitude) / 600000.0;
|
||
if ($latitude == 91) {
|
||
if ($print_9) { print "\n"; }
|
||
return();
|
||
}
|
||
my $NS;
|
||
if ($print_9) { printf(" Latitude: %07.5f\n", $latitude); }
|
||
if ($latitude >= 0.0) {
|
||
$NS = 'N';
|
||
} else {
|
||
$NS = 'S';
|
||
$latitude = abs($latitude);
|
||
}
|
||
my $latdeg = int($latitude);
|
||
my $latmins = $latitude - $latdeg;
|
||
my $latmins2 = $latmins * 60.0;
|
||
my $lat = sprintf("%02d%05.2f%s", $latdeg, $latmins2, $NS);
|
||
|
||
# NOTE: -180 to +180
|
||
# NOTE: 181 = N/A
|
||
my $longitude = &signedBin2dec($bLongitude) / 600000.0;
|
||
if ($longitude == 181) {
|
||
if ($print_9) { print "\n"; }
|
||
return();
|
||
}
|
||
my $EW;
|
||
if ($print_9) { printf("Longitude: %08.5f\n", $longitude); }
|
||
if ($longitude >= 0.0) {
|
||
$EW = 'E';
|
||
} else {
|
||
$EW = 'W';
|
||
$longitude = abs($longitude);
|
||
}
|
||
my $londeg = int($longitude);
|
||
my $lonmins = $longitude - $londeg;
|
||
my $lonmins2 = $lonmins * 60.0;
|
||
my $lon = sprintf("%03d%05.2f%s", $londeg, $lonmins2, $EW);
|
||
|
||
my $symbol = "^"; # "^" = Large Aircraft. Could be "'" for small aircraft, "X" for helicopter.
|
||
my $vesselTag = " ($country)";
|
||
if ( defined($vessel_hash{$userID}) ) { $vesselTag = " " . $vessel_hash{$userID} . " (" . $country . ")"; }
|
||
my $aprs = &escape_from_shell("$xastir_user>APRS:)$userID!$lat/$lon$symbol$course/$speed$altitude SAR Aircraft$vesselTag");
|
||
if ($print_9) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
my $result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
|
||
# Assign tactical call = "$userID + $country" or "$vesselName + $country"
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
my $temp;
|
||
if ( defined( $vessel_hash{$userID}) ) {
|
||
$temp = substr($vessel_hash{$userID} . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
}
|
||
else {
|
||
# No vessel name
|
||
# N7IPB: Display country of registry
|
||
$temp = substr("($country)", 0, 56); # Chop at 56 chars
|
||
|
||
# WE7U: Display the 9-digit MMSI + country of registry
|
||
#$temp = substr($userID . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
}
|
||
if ( !defined($tactical_hash{$userID}) ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_9) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($print_9) { print "\n"; }
|
||
|
||
return();
|
||
}
|
||
|
||
|
||
|
||
# Message type 18: Standard Class B CS Position Report
|
||
# This message should have 168 bits total
|
||
#
|
||
sub process_type_18() {
|
||
|
||
#if ( length($bin_string) < 168 ) {
|
||
if ( length($bin_string) < 124 ) {
|
||
#printf("18\t%s\t%d\n", $bin_string, length($bin_string));
|
||
#printf("18\t%d\n", length($bin_string));
|
||
if ($print_18) { print "Msg too short\n\n"; }
|
||
return;
|
||
}
|
||
|
||
# substr($bin_string, 0, 6); # Message Type
|
||
# my $brepeat_indicator = substr($bin_string, 6, 2); # Repeat Indicator
|
||
my $bUserID = substr($bin_string, 8, 30); # MMSI
|
||
# substr($bin_string, 38, 8); # Spare
|
||
my $bSpeedOverGnd = substr($bin_string, 46, 10); # Speed Over Ground
|
||
# substr($bin_string, 56, 1); # Position Accuracy
|
||
my $bLongitude = substr($bin_string, 57, 28); # Longitude
|
||
my $bLatitude = substr($bin_string, 85, 27); # Latitude
|
||
my $bCourseOverGnd = substr($bin_string, 112, 12); # Course Over Ground
|
||
# substr($bin_string, 124, 9); # True Heading
|
||
# substr($bin_string, 133, 6); # Time Stamp
|
||
# substr($bin_string, 139, 2); # Spare
|
||
# substr($bin_string, 141, 1); # CS Unit
|
||
# substr($bin_string, 142, 1); # Display flag
|
||
# substr($bin_string, 143, 1); # DSC Flag
|
||
# substr($bin_string, 144, 1); # Band flag
|
||
# substr($bin_string, 145, 1); # Message 22 flag
|
||
# substr($bin_string, 146, 1); # Assigned mode
|
||
# substr($bin_string, 147, 1); # RAIM flag
|
||
# substr($bin_string, 148, 1); # Comm state selector flag
|
||
# substr($bin_string, 149, 19); # Radio status
|
||
|
||
my $userID = &bin2dec($bUserID);
|
||
if ($print_18) { print " User ID: $userID\n"; }
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_18) { print " Country: $country\n"; }
|
||
|
||
# NOTE: 0-359 degrees
|
||
# NOTE: 360 = N/A
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $courseOverGnd = &bin2dec($bCourseOverGnd) / 10 ;
|
||
my $course = "";
|
||
if ($courseOverGnd == 360) {
|
||
$course = "...";
|
||
}
|
||
elsif ($courseOverGnd == 0) {
|
||
$course = "360";
|
||
}
|
||
else {
|
||
$course = sprintf("%03d", $courseOverGnd);
|
||
}
|
||
if ($print_18) { print " Course: $courseOverGnd\n"; }
|
||
|
||
# NOTE: 0 to 102 knots
|
||
# NOTE: 102.3 = N/A
|
||
# NOTE: 102.2 = 102.2 knots or higher
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $speedOverGnd = &bin2dec($bSpeedOverGnd) / 10;
|
||
my $speed = "";
|
||
if ($speedOverGnd == 102.3) {
|
||
$speed = "...";
|
||
}
|
||
else {
|
||
$speed = sprintf("%03d", $speedOverGnd);
|
||
}
|
||
if ($print_18) { print " Speed: $speedOverGnd\n"; }
|
||
|
||
# NOTE: -90 to +90
|
||
# NOTE: 91 = N/A
|
||
my $latitude = &signedBin2dec($bLatitude) / 600000.0;
|
||
if ($latitude == 91) {
|
||
if ($print_18) { print "\n"; }
|
||
return();
|
||
}
|
||
my $NS;
|
||
if ($print_18) { printf(" Latitude: %07.5f\n", $latitude); }
|
||
if ($latitude >= 0.0) {
|
||
$NS = 'N';
|
||
} else {
|
||
$NS = 'S';
|
||
$latitude = abs($latitude);
|
||
}
|
||
my $latdeg = int($latitude);
|
||
my $latmins = $latitude - $latdeg;
|
||
my $latmins2 = $latmins * 60.0;
|
||
my $lat = sprintf("%02d%05.2f%s", $latdeg, $latmins2, $NS);
|
||
|
||
# NOTE: -180 to +180
|
||
# NOTE: 181 = N/A
|
||
my $longitude = &signedBin2dec($bLongitude) / 600000.0;
|
||
if ($longitude == 181) {
|
||
if ($print_18) { print "\n"; }
|
||
return();
|
||
}
|
||
my $EW;
|
||
if ($print_18) { printf("Longitude: %08.5f\n", $longitude); }
|
||
if ($longitude >= 0.0) {
|
||
$EW = 'E';
|
||
} else {
|
||
$EW = 'W';
|
||
$longitude = abs($longitude);
|
||
}
|
||
my $londeg = int($longitude);
|
||
my $lonmins = $longitude - $londeg;
|
||
my $lonmins2 = $lonmins * 60.0;
|
||
my $lon = sprintf("%03d%05.2f%s", $londeg, $lonmins2, $EW);
|
||
|
||
my $symbol = "s";
|
||
my $vesselTag = " ($country)";
|
||
if ( defined($vessel_hash{$userID}) ) { $vesselTag = " " . $vessel_hash{$userID} . " (" . $country . ")"; }
|
||
my $aprs = &escape_from_shell("$xastir_user>APRS:)$userID!$lat/$lon$symbol$course/$speed$vesselTag");
|
||
if ($print_18) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
my $result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
|
||
# Assign tactical call = "$userID + $country" or "$vesselName + $country"
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
my $temp;
|
||
if ( defined( $vessel_hash{$userID}) ) {
|
||
$temp = substr($vessel_hash{$userID} . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
}
|
||
else {
|
||
# No vessel name
|
||
# N7IPB: Display country of registry
|
||
$temp = substr("($country)", 0, 56); # Chop at 56 chars
|
||
|
||
# WE7U: Display the 9-digit MMSI + country of registry
|
||
#$temp = substr($userID . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
}
|
||
if ( !defined($tactical_hash{$userID}) ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_18) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($print_18) { print "\n"; }
|
||
|
||
return();
|
||
}
|
||
|
||
|
||
|
||
# Message type 19: Extended Class B CS Position Report
|
||
# This message should have 312 bits total
|
||
#
|
||
sub process_type_19() {
|
||
|
||
#if ( length($bin_string) < 312 ) {
|
||
if ( length($bin_string) < 263 ) {
|
||
#printf("19\t%s\t%d\n", $bin_string, length($bin_string));
|
||
#printf("19\t%d\n", length($bin_string));
|
||
if ($print_19) { print "Msg too short\n\n"; }
|
||
return;
|
||
}
|
||
|
||
# substr($bin_string, 0, 6); # Message Type
|
||
# my $brepeat_indicator = substr($bin_string, 6, 2); # Repeat Indicator
|
||
my $bUserID = substr($bin_string, 8, 30); # MMSI
|
||
# substr($bin_string, 38, 8); # Spare
|
||
my $bSpeedOverGnd = substr($bin_string, 46, 10); # Speed Over Ground
|
||
# substr($bin_string, 56, 1); # Position Accuracy
|
||
my $bLongitude = substr($bin_string, 57, 28); # Longitude
|
||
my $bLatitude = substr($bin_string, 85, 27); # Latitude
|
||
my $bCourseOverGnd = substr($bin_string, 112, 12); # Course Over Ground
|
||
# substr($bin_string, 124, 9); # True Heading
|
||
# substr($bin_string, 133, 6); # Time Stamp
|
||
# substr($bin_string, 139, 4); # Spare
|
||
my $bVesselName = substr($bin_string, 143, 120); # Name
|
||
# substr($bin_string, 263, 8); # Type of ship and cargo
|
||
# substr($bin_string, 271, 9); # Dimension to Bow
|
||
# substr($bin_string, 280, 9); # Dimension to Stern
|
||
# substr($bin_string, 289, 6); # Dimension to Port
|
||
# substr($bin_string, 295, 6); # Dimension to Starboard
|
||
# substr($bin_string, 301, 4); # Position Fix Type
|
||
# substr($bin_string, 305, 1); # RAIM flag
|
||
# substr($bin_string, 306, 1); # DTE
|
||
# substr($bin_string, 307, 1); # Assigned mode flag
|
||
# substr($bin_string, 308, 4); # Spare
|
||
|
||
my $userID = &bin2dec($bUserID);
|
||
if ($print_19) { print " User ID: $userID\n"; }
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_19) { print " Country: $country\n"; }
|
||
|
||
# NOTE: 0-359 degrees
|
||
# NOTE: 360 = N/A
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $courseOverGnd = &bin2dec($bCourseOverGnd) / 10 ;
|
||
my $course = "";
|
||
if ($courseOverGnd == 360) {
|
||
$course = "...";
|
||
}
|
||
elsif ($courseOverGnd == 0) {
|
||
$course = "360";
|
||
}
|
||
else {
|
||
$course = sprintf("%03d", $courseOverGnd);
|
||
}
|
||
if ($print_19) { print " Course: $courseOverGnd\n"; }
|
||
|
||
# NOTE: 0 to 102 knots
|
||
# NOTE: 102.3 = N/A
|
||
# NOTE: 102.2 = 102.2 knots or higher
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $speedOverGnd = &bin2dec($bSpeedOverGnd) / 10;
|
||
my $speed = "";
|
||
if ($speedOverGnd == 102.3) {
|
||
$speed = "...";
|
||
}
|
||
else {
|
||
$speed = sprintf("%03d", $speedOverGnd);
|
||
}
|
||
if ($print_19) { print " Speed: $speedOverGnd\n"; }
|
||
|
||
# NOTE: -90 to +90
|
||
# NOTE: 91 = N/A
|
||
my $latitude = &signedBin2dec($bLatitude) / 600000.0;
|
||
if ($latitude == 91) {
|
||
if ($print_19) { print "\n"; }
|
||
return();
|
||
}
|
||
my $NS;
|
||
if ($print_19) { printf(" Latitude: %07.5f\n", $latitude); }
|
||
if ($latitude >= 0.0) {
|
||
$NS = 'N';
|
||
} else {
|
||
$NS = 'S';
|
||
$latitude = abs($latitude);
|
||
}
|
||
my $latdeg = int($latitude);
|
||
my $latmins = $latitude - $latdeg;
|
||
my $latmins2 = $latmins * 60.0;
|
||
my $lat = sprintf("%02d%05.2f%s", $latdeg, $latmins2, $NS);
|
||
# NOTE: -180 to +180
|
||
# NOTE: 181 = N/A
|
||
my $longitude = &signedBin2dec($bLongitude) / 600000.0;
|
||
if ($longitude == 181) {
|
||
if ($print_19) { print "\n"; }
|
||
return();
|
||
}
|
||
my $EW;
|
||
if ($print_19) { printf("Longitude: %08.5f\n", $longitude); }
|
||
if ($longitude >= 0.0) {
|
||
$EW = 'E';
|
||
} else {
|
||
$EW = 'W';
|
||
$longitude = abs($longitude);
|
||
}
|
||
my $londeg = int($longitude);
|
||
my $lonmins = $longitude - $londeg;
|
||
my $lonmins2 = $lonmins * 60.0;
|
||
my $lon = sprintf("%03d%05.2f%s", $londeg, $lonmins2, $EW);
|
||
|
||
my $vesselName = "";
|
||
if ($bVesselName ne "") {
|
||
$vesselName = &bin2text($bVesselName);
|
||
$vesselName =~ s/\s+$//; # Remove extra spaces at end
|
||
}
|
||
|
||
my $symbol = "s";
|
||
my $vesselTag = " ($country)";
|
||
if ( defined($vessel_hash{$userID}) ) { $vesselTag = " " . $vessel_hash{$userID} . " (" . $country . ")"; }
|
||
my $aprs = &escape_from_shell("$xastir_user>APRS:)$userID!$lat/$lon$symbol$course/$speed$vesselTag");
|
||
if ($print_19) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
my $result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
|
||
if ($vesselName ne "") {
|
||
$vessel_hash{$userID} = $vesselName;
|
||
# Update the persistent store
|
||
store \%vessel_hash , $persistentFileSpec;
|
||
|
||
if ($print_19) { print " Vessel: $vesselName\n"; }
|
||
|
||
# Assign tactical call = $vesselName
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
my $temp = substr($vesselName . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
if ( !defined($tactical_hash{$userID}) || $tactical_hash{$userID} ne $temp ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_19) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($print_19) { print "\n"; }
|
||
|
||
return();
|
||
}
|
||
|
||
# Message type 21: Aid to Navigation report
|
||
# These messages should have between 272 and 360 bits total
|
||
# We don't use the full message and don't parse the name extension
|
||
#
|
||
sub process_type_21() {
|
||
|
||
#if ( length($bin_string) < 168 ) {
|
||
if ( length($bin_string) < 272 ) {
|
||
#printf("21\t%s\t%d\n", $bin_string, length($bin_string));
|
||
#printf("21\t%d\n", length($bin_string));
|
||
if ($print_21) { print "Msg too short\n\n"; }
|
||
return;
|
||
}
|
||
|
||
my $bUserID = substr($bin_string, 8, 30); # MMSI
|
||
my $bAidType = substr($bin_string, 38, 5);
|
||
my $bVesselName = substr($bin_string, 43, 120); # Vessel Name #####
|
||
my $bLongitude = substr($bin_string, 164, 28); #####
|
||
my $bLatitude = substr($bin_string, 192, 27); #####
|
||
|
||
my $userID = &bin2dec($bUserID);
|
||
if ($print_21) { print " User ID: $userID\n"; }
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_21) { print " Country: $country\n"; }
|
||
|
||
my $vesselName = &bin2text($bVesselName);
|
||
$vesselName =~ s/\s+$//; # Remove extra spaces at end
|
||
|
||
$vessel_hash{$userID} = $vesselName;
|
||
# Update the persistent store
|
||
store \%vessel_hash , $persistentFileSpec;
|
||
|
||
if ($print_21) { print " Vessel: $vesselName\n"; }
|
||
|
||
my $AidTypeTxt = &decodeAidType($bAidType);
|
||
if ($print_21) { print "Navaid Type: $AidTypeTxt\n"; }
|
||
|
||
|
||
# NOTE: -90 to +90
|
||
# NOTE: 91 = N/A
|
||
my $latitude = &signedBin2dec($bLatitude) / 600000.0;
|
||
if ($latitude == 91) { return(); }
|
||
my $NS;
|
||
if ($print_21) { printf(" Latitude: %07.5f\n", $latitude); }
|
||
if ($latitude >= 0.0) {
|
||
$NS = 'N';
|
||
} else {
|
||
$NS = 'S';
|
||
$latitude = abs($latitude);
|
||
}
|
||
my $latdeg = int($latitude);
|
||
my $latmins = $latitude - $latdeg;
|
||
my $latmins2 = $latmins * 60.0;
|
||
my $lat = sprintf("%02d%05.2f%s", $latdeg, $latmins2, $NS);
|
||
|
||
# NOTE: -180 to +180
|
||
# NOTE: 181 = N/A
|
||
my $longitude = &signedBin2dec($bLongitude) / 600000.0;
|
||
if ($longitude == 181) { return(); }
|
||
my $EW;
|
||
if ($print_21) { printf("Longitude: %08.5f\n", $longitude); }
|
||
if ($longitude >= 0.0) {
|
||
$EW = 'E';
|
||
} else {
|
||
$EW = 'W';
|
||
$longitude = abs($longitude);
|
||
}
|
||
my $londeg = int($longitude);
|
||
my $lonmins = $longitude - $londeg;
|
||
my $lonmins2 = $lonmins * 60.0;
|
||
my $lon = sprintf("%03d%05.2f%s", $londeg, $lonmins2, $EW);
|
||
|
||
my $symbol = "N";
|
||
|
||
# Set up to add country or vessel_name + country to comment field
|
||
my $vesselTag = " (nav-aid)";
|
||
if ( defined($vessel_hash{$userID}) ) { $vesselTag = " " . $vessel_hash{$userID} . " (" . $country . ")"; }
|
||
|
||
my $aprs= &escape_from_shell("$xastir_user>APRS:)$userID!$lat\\$lon$symbol $AidTypeTxt");
|
||
if ($print_21) { print " APRS: $aprs\n"; }
|
||
&log_aprs($aprs);
|
||
if ($enable_tx) {
|
||
my $result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
|
||
# Assign tactical call = $vesselName + $country
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
my $temp = substr($vesselName, 0, 56); # Chop at 56 chars
|
||
if ( !defined($tactical_hash{$userID}) || $tactical_hash{$userID} ne $temp ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_21) { print " APRS: $aprs\n"; }
|
||
&log_aprs($aprs);
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($print_21) { print "\n"; }
|
||
|
||
return();
|
||
|
||
}
|
||
|
||
|
||
# Message type 24: Static Data Report (Vessel name, callsign, ship type)
|
||
# This one has three variations:
|
||
#
|
||
# If Part Number field is 0, it's a Type A message.
|
||
# If Part Number is 1, it's a Type B.
|
||
#
|
||
# Part B has two variations as well:
|
||
# If the MMSI is that of an auxiliary craft, then the Mothership MMSI
|
||
# is that of the mothership.
|
||
# If not, then those 30 bits represent vessel dimmensions.
|
||
#
|
||
# This message should have:
|
||
# Type A: 160 bits total
|
||
# Type B: 168 bits total
|
||
#
|
||
sub process_type_24() {
|
||
|
||
#if ( length($bin_string) < 160 ) {
|
||
if ( length($bin_string) < 160 ) {
|
||
#printf("24\t%s\t%d\n", $bin_string, length($bin_string));
|
||
#printf("24\t%d\n", length($bin_string));
|
||
if ($print_24_A || $print_24_B) { print "Msg too short\n\n"; }
|
||
return;
|
||
}
|
||
|
||
# substr($bin_string, 0, 6); # Message Type
|
||
# my $brepeat_indicator = substr($bin_string, 6, 2); # Repeat Indicator
|
||
my $bUserID = substr($bin_string, 8, 30); # MMSI
|
||
my $bPartNumber = substr($bin_string, 38, 2); # Part Number
|
||
|
||
my $userID = &bin2dec($bUserID);
|
||
|
||
# Check for Type A/B Variant (Type B also has two variants!)
|
||
$PartNumber = &bin2dec($bPartNumber);
|
||
if ($PartNumber == 0) {
|
||
|
||
# It is a Type A Variant
|
||
if ($print_24_A) { print " Msg Type: $message_type\tType A Variant\n"; }
|
||
|
||
if ($print_24_A) { print " User ID: $userID\n"; }
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_24_A) { print " Country: $country\n"; }
|
||
|
||
# Type A Variant format starts at bit 40:
|
||
my $bVesselName = substr($bin_string, 40, 120); # Vessel Name
|
||
# substr($bin_string, 160, 8); # Spare.
|
||
|
||
my $vesselName = "";
|
||
if ($bVesselName ne "") {
|
||
$vesselName = &bin2text($bVesselName);
|
||
$vesselName =~ s/\s+$//; # Remove extra spaces at end
|
||
}
|
||
if ($vesselName ne "") {
|
||
$vessel_hash{$userID} = $vesselName;
|
||
# Update the persistent store
|
||
store \%vessel_hash , $persistentFileSpec;
|
||
|
||
if ($print_24_A) { print " Vessel: $vesselName\n"; }
|
||
|
||
# Assign tactical call = $vesselName
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
my $temp = substr($vesselName . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
if ( !defined($tactical_hash{$userID}) || $tactical_hash{$userID} ne $temp ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_24_A) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if ($print_24_A) { print "\n"; }
|
||
}
|
||
else {
|
||
|
||
# It is a Type B Variant. There are two variants of this type!
|
||
if ($print_24_B) { print " Msg Type: $message_type\tType B Variant\n"; }
|
||
|
||
if ($print_24_B) { print " User ID: $userID\n"; }
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_24_B) { print " Country: $country\n"; }
|
||
|
||
# Check the UserID to see if it is an Auxiliary Craft,
|
||
# to determine which Type B Variant to decode.
|
||
# Auxiliary craft: MMSI of form 98XXXYYYY, the XXX digits are the country code.
|
||
|
||
if ( !($userID =~ m/^98/)) {
|
||
# It is NOT an auxiliary craft:
|
||
|
||
# Alternate format starting at bit 40:
|
||
$bShipType = substr($bin_string, 40, 8); # Ship Type: Page 114 of ITU spec, Table 53
|
||
# substr($bin_string, 48, 18); # Vendor ID
|
||
# substr($bin_string, 66, 4); # Unit Model Code
|
||
# substr($bin_string, 70, 20); # Serial Number)
|
||
# substr($bin_string, 90, 42); # Call Sign
|
||
# substr($bin_string, 132, 9); # Dimension to bow
|
||
# substr($bin_string, 141, 9); # Dimension to Stern
|
||
# substr($bin_string, 150, 6); # Dimension to Port
|
||
# substr($bin_string, 156, 6); # Dimension to Starboard
|
||
# substr($bin_string, 162, 4); # Type of fix
|
||
# substr($bin_string, 166, 2); # Spare
|
||
|
||
my $shipTypeTxt = &decodeShipType($bShipType);
|
||
if ($print_24_B) { print "Ship Type: $shipTypeTxt\n"; }
|
||
|
||
# Assign tactical call = $vesselName + $shipTypeTxt
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
#my $temp = substr($vesselName . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
my $temp;
|
||
if (defined($vessel_hash{$userID}) ) {
|
||
$temp = substr($vessel_hash{$userID} . " ($shipTypeTxt:$country)", 0, 56); # Chop at 56 chars
|
||
}
|
||
else {
|
||
$temp = substr("($shipTypeTxt:$country)", 0, 56); # Chop at 56 chars
|
||
}
|
||
|
||
if ( !defined($tactical_hash{$userID}) || $tactical_hash{$userID} ne $temp ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_24_A) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
# It IS an auxiliary craft:
|
||
|
||
# Alternate format starting at bit 132:
|
||
# substr($bin_string, 132, 30); # Mothership MMSI
|
||
# substr($bin_string, 162, 6); # Spare
|
||
}
|
||
|
||
if ($print_24_B) { print "\n"; }
|
||
}
|
||
|
||
return();
|
||
}
|
||
|
||
|
||
|
||
# Message type 27: Position Report For Long-Range Applications (rare)
|
||
# This message should have 96 bits total
|
||
#
|
||
sub process_type_27() {
|
||
|
||
#if ( length($bin_string) < 96 ) {
|
||
if ( length($bin_string) < 94 ) {
|
||
#printf("27\t%s\t%d\n", $bin_string, length($bin_string));
|
||
#printf("27\t%d\n", length($bin_string));
|
||
if ($print_27) { print "Msg too short\n\n"; }
|
||
return;
|
||
}
|
||
|
||
# substr($bin_string, 0, 6); # Message Type
|
||
# my $brepeat_indicator = substr($bin_string, 6, 2); # Repeat Indicator
|
||
my $bUserID = substr($bin_string, 8, 30); # MMSI
|
||
# substr($bin_string, 38, 1); # Position Accuracy
|
||
# substr($bin_string, 39, 1); # RAIM flag
|
||
my $bNavStatus = substr($bin_string, 40, 4); # Navigation Status
|
||
my $bLongitude = substr($bin_string, 44, 18); # Longitude
|
||
my $bLatitude = substr($bin_string, 62, 17); # Latitude
|
||
my $bSpeedOverGnd = substr($bin_string, 79, 6); # Speed Over Ground
|
||
my $bCourseOverGnd = substr($bin_string, 85, 9); # Course Over Ground
|
||
# substr($bin_string, 94, 1); # GNSS Position latency
|
||
# substr($bin_string, 95, 1); # Spare
|
||
|
||
my $userID = &bin2dec($bUserID);
|
||
if ($print_27) { print " User ID: $userID\n"; }
|
||
|
||
my $country = &decode_MID($userID);
|
||
if ($print_27) { print " Country: $country\n"; }
|
||
|
||
my $navStatus = &bin2dec($bNavStatus);
|
||
my $navStatusTxt = "";
|
||
if ($navStatus == 0) { $navStatusTxt = "Under_way:engine"; }
|
||
elsif ($navStatus == 1) { $navStatusTxt = "At_anchor"; }
|
||
elsif ($navStatus == 2) { $navStatusTxt = "Not_under_command"; }
|
||
elsif ($navStatus == 3) { $navStatusTxt = "Restricted_maneuverability"; }
|
||
elsif ($navStatus == 4) { $navStatusTxt = "Constrained_by_draught"; }
|
||
elsif ($navStatus == 5) { $navStatusTxt = "Moored"; }
|
||
elsif ($navStatus == 6) { $navStatusTxt = "Aground"; }
|
||
elsif ($navStatus == 7) { $navStatusTxt = "Fishing"; }
|
||
elsif ($navStatus == 8) { $navStatusTxt = "Under_way:sailing"; }
|
||
#elsif ($navStatus == 9) { $navStatusTxt = "Reserved"; }
|
||
#elsif ($navStatus == 10) { $navStatusTxt = "Reserved"; }
|
||
#elsif ($navStatus == 11) { $navStatusTxt = "Reserved"; }
|
||
#elsif ($navStatus == 12) { $navStatusTxt = "Reserved"; }
|
||
#elsif ($navStatus == 13) { $navStatusTxt = "Reserved"; }
|
||
elsif ($navStatus == 14) { $navStatusTxt = "AIS-SART_is_active"; }
|
||
#elsif ($navStatus == 15) { $navStatusTxt = "Not defined"; }
|
||
|
||
# NOTE: 0-359 degrees
|
||
# NOTE: 511 = N/A
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $courseOverGnd = &bin2dec($bCourseOverGnd);
|
||
my $course = "";
|
||
if ($courseOverGnd == 511) {
|
||
$course = "...";
|
||
}
|
||
elsif ($courseOverGnd == 0) {
|
||
$course = "360";
|
||
}
|
||
else {
|
||
$course = sprintf("%03d", $courseOverGnd);
|
||
}
|
||
if ($print_27) { print " Course: $courseOverGnd\n"; }
|
||
|
||
# NOTE: 0-62 knots
|
||
# NOTE: 63 = N/A
|
||
# APRS spec says can set to "000", "...", or " " if unknown
|
||
my $speedOverGnd = &bin2dec($bSpeedOverGnd);
|
||
my $speed = "";
|
||
if ($speedOverGnd == 63) {
|
||
$speed = "...";
|
||
}
|
||
else {
|
||
$speed = sprintf("%03d", $speedOverGnd);
|
||
}
|
||
if ($print_27) { print " Speed: $speedOverGnd\n"; }
|
||
|
||
# NOTE: -90 to +90
|
||
# NOTE: 91 = N/A = D548h or 54600 decimal. Divide by 91 to get 600 (our factor).
|
||
my $latitude = &signedBin2dec($bLatitude) / 600.0;
|
||
if ($latitude == 91) {
|
||
if ($print_27) { print "\n"; }
|
||
return();
|
||
}
|
||
if ($print_27) {
|
||
#printf(" bLatitude: %s\n", $bLatitude);
|
||
printf(" Latitude: %07.5f\n", $latitude);
|
||
}
|
||
my $NS;
|
||
if ($latitude >= 0.0) {
|
||
$NS = 'N';
|
||
} else {
|
||
$NS = 'S';
|
||
$latitude = abs($latitude);
|
||
}
|
||
my $latdeg = int($latitude);
|
||
my $latmins = $latitude - $latdeg;
|
||
my $latmins2 = $latmins * 60.0;
|
||
my $lat = sprintf("%02d%05.2f%s", $latdeg, $latmins2, $NS);
|
||
|
||
# NOTE: -180 to +180
|
||
# NOTE: 181 = N/A = 1A838h or 108600 decimal. Divide by 181 to get 600 (our factor).
|
||
my $longitude = &signedBin2dec($bLongitude) / 600.0;
|
||
if ($longitude == 181) {
|
||
if ($print_27) { print "\n"; }
|
||
return();
|
||
}
|
||
if ($print_27) {
|
||
#printf("bLongitude: %s\n", $bLongitude);
|
||
printf("Longitude: %08.5f\n", $longitude);
|
||
}
|
||
my $EW;
|
||
if ($longitude >= 0.0) {
|
||
$EW = 'E';
|
||
} else {
|
||
$EW = 'W';
|
||
$longitude = abs($longitude);
|
||
}
|
||
my $londeg = int($longitude);
|
||
my $lonmins = $longitude - $londeg;
|
||
my $lonmins2 = $lonmins * 60.0;
|
||
my $lon = sprintf("%03d%05.2f%s", $londeg, $lonmins2, $EW);
|
||
|
||
my $symbol = "s";
|
||
my $vesselTag = " ($country)";
|
||
if ( defined($vessel_hash{$userID}) ) { $vesselTag = " " . $vessel_hash{$userID} . " (" . $country . ")"; }
|
||
my $aprs = &escape_from_shell("$xastir_user>APRS:)$userID!$lat/$lon$symbol$course/$speed $navStatusTxt$vesselTag");
|
||
if ($print_27) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
my $result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
|
||
# Assign tactical call = "$userID + $country" or "$vesselName + $country"
|
||
# Max tactical call in Xastir is 57 chars (56 + terminator?)
|
||
#
|
||
my $temp;
|
||
if ( defined( $vessel_hash{$userID}) ) {
|
||
$temp = substr($vessel_hash{$userID} . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
}
|
||
else {
|
||
# No vessel name
|
||
# N7IPB: Display country of registry
|
||
$temp = substr("($country)", 0, 56); # Chop at 56 chars
|
||
|
||
# WE7U: Display the 9-digit MMSI + country of registry
|
||
#$temp = substr($userID . " (" . $country . ")", 0, 56); # Chop at 56 chars
|
||
}
|
||
if ( !defined($tactical_hash{$userID}) ) {
|
||
$tactical_hash{$userID} = $temp;
|
||
$aprs = &escape_from_shell($xastir_user . '>' . "APRS::TACTICAL :" . $userID . "=" . $temp);
|
||
if ($print_27) { print " APRS: $aprs\n"; }
|
||
|
||
&log_aprs($aprs);
|
||
|
||
if ($enable_tx) {
|
||
$result = `$udp_client $xastir_host $xastir_port $xastir_user $xastir_pass \"$aprs\"`;
|
||
if ($result =~ m/NACK/) {
|
||
die "Received NACK from Xastir: Callsign/Passcode don't match?\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($print_27) { print "\n"; }
|
||
|
||
return();
|
||
}
|
||
|
||
# Convert binary string $bShipType into text string $shipType
|
||
#
|
||
sub decodeAidType() {
|
||
my $bAidType = shift;
|
||
|
||
my $aidType = sprintf("%02d", &bin2dec($bAidType));
|
||
my $aidTypeTxt = "$aidType:";
|
||
|
||
# Special Craft
|
||
if ($aidType == 1) { $aidTypeTxt = $aidTypeTxt . "Reference point"; }
|
||
elsif ($aidType == 2) { $aidTypeTxt = $aidTypeTxt . "RACON"; }
|
||
elsif ($aidType == 3) { $aidTypeTxt = $aidTypeTxt . "Fixed structure off shore"; }
|
||
elsif ($aidType == 4) { $aidTypeTxt = $aidTypeTxt . "Reserved"; }
|
||
elsif ($aidType == 5) { $aidTypeTxt = $aidTypeTxt . "Light, without sectors"; }
|
||
elsif ($aidType == 6) { $aidTypeTxt = $aidTypeTxt . "Light, with sectors"; }
|
||
elsif ($aidType == 7) { $aidTypeTxt = $aidTypeTxt . "Leading Light Front"; }
|
||
elsif ($aidType == 8) { $aidTypeTxt = $aidTypeTxt . "Leading Light Rear"; }
|
||
elsif ($aidType == 9) { $aidTypeTxt = $aidTypeTxt . "Beacon, Cardinal N"; }
|
||
elsif ($aidType == 10) { $aidTypeTxt = $aidTypeTxt . "Beacon, Cardinal E"; }
|
||
elsif ($aidType == 11) { $aidTypeTxt = $aidTypeTxt . "Beacon, Cardinal S"; }
|
||
elsif ($aidType == 12) { $aidTypeTxt = $aidTypeTxt . "Beacon, Cardinal W"; }
|
||
elsif ($aidType == 13) { $aidTypeTxt = $aidTypeTxt . "Beacon, Port hand"; }
|
||
elsif ($aidType == 14) { $aidTypeTxt = $aidTypeTxt . "Beacon, Starboard hand"; }
|
||
elsif ($aidType == 15) { $aidTypeTxt = $aidTypeTxt . "Beacon, Preferred Channel port hand"; }
|
||
elsif ($aidType == 16) { $aidTypeTxt = $aidTypeTxt . "Beacon, Preferred Channel starboard hand"; }
|
||
elsif ($aidType == 17) { $aidTypeTxt = $aidTypeTxt . "Beacon, Isolated danger"; }
|
||
elsif ($aidType == 18) { $aidTypeTxt = $aidTypeTxt . "Beacon, Safe water"; }
|
||
elsif ($aidType == 19) { $aidTypeTxt = $aidTypeTxt . "Beacon, Special mark"; }
|
||
elsif ($aidType == 20) { $aidTypeTxt = $aidTypeTxt . "Cardinal Mark N"; }
|
||
elsif ($aidType == 21) { $aidTypeTxt = $aidTypeTxt . "Cardinal Mark E"; }
|
||
elsif ($aidType == 22) { $aidTypeTxt = $aidTypeTxt . "Cardinal Mark S"; }
|
||
elsif ($aidType == 23) { $aidTypeTxt = $aidTypeTxt . "Cardinal Mark W"; }
|
||
elsif ($aidType == 24) { $aidTypeTxt = $aidTypeTxt . "Port hand Mark"; }
|
||
elsif ($aidType == 25) { $aidTypeTxt = $aidTypeTxt . "Starboard hand Mark"; }
|
||
elsif ($aidType == 26) { $aidTypeTxt = $aidTypeTxt . "Preferred Channel Port hand"; }
|
||
elsif ($aidType == 27) { $aidTypeTxt = $aidTypeTxt . "Preferred Channel Starboard hand"; }
|
||
elsif ($aidType == 28) { $aidTypeTxt = $aidTypeTxt . "Isolated danger"; }
|
||
elsif ($aidType == 29) { $aidTypeTxt = $aidTypeTxt . "Safe Water"; }
|
||
elsif ($aidType == 30) { $aidTypeTxt = $aidTypeTxt . "Special Mark"; }
|
||
elsif ($aidType == 31) { $aidTypeTxt = $aidTypeTxt . "Light Vessel / LANBY / Rigs"; }
|
||
|
||
return($aidTypeTxt);
|
||
}
|
||
|
||
|
||
|
||
|
||
# Convert binary string $bShipType into text string $shipType
|
||
#
|
||
sub decodeShipType() {
|
||
my $bShipType = shift;
|
||
|
||
my $shipType = sprintf("%02d", &bin2dec($bShipType));
|
||
my $shipTypeTxt = "$shipType:";
|
||
|
||
# Special Craft
|
||
if ($shipType == 50) { $shipTypeTxt = $shipTypeTxt . "Pilot_Vessel"; }
|
||
elsif ($shipType == 51) { $shipTypeTxt = $shipTypeTxt . "Search_and_Rescue"; }
|
||
elsif ($shipType == 52) { $shipTypeTxt = $shipTypeTxt . "Harbor_Tug"; }
|
||
elsif ($shipType == 53) { $shipTypeTxt = $shipTypeTxt . "Fish,Offshore_or_Port_Tender"; }
|
||
elsif ($shipType == 54) { $shipTypeTxt = $shipTypeTxt . "Anti-pollution"; }
|
||
elsif ($shipType == 55) { $shipTypeTxt = $shipTypeTxt . "Law_Enforcement"; }
|
||
elsif ($shipType == 56) { $shipTypeTxt = $shipTypeTxt . "Local"; }
|
||
elsif ($shipType == 57) { $shipTypeTxt = $shipTypeTxt . "Local"; }
|
||
elsif ($shipType == 58) { $shipTypeTxt = $shipTypeTxt . "Medical_Transport_or_Public_Safety"; }
|
||
elsif ($shipType == 59) { $shipTypeTxt = $shipTypeTxt . "Neutral_State_Vessel"; }
|
||
|
||
# U.S. Specific Vessels
|
||
elsif ($shipType =~ m/^2/) {
|
||
if ($shipType =~ m/0$/) { $shipTypeTxt = $shipTypeTxt . "Wing_in_Ground"; }
|
||
if ($shipType =~ m/1$/) { $shipTypeTxt = $shipTypeTxt . "Tow-Push"; }
|
||
if ($shipType =~ m/2$/) { $shipTypeTxt = $shipTypeTxt . "Tow-Push"; }
|
||
if ($shipType =~ m/3$/) { $shipTypeTxt = $shipTypeTxt . "Light_Boat"; }
|
||
if ($shipType =~ m/4$/) { $shipTypeTxt = $shipTypeTxt . "Mobile_Offshore_Drilling"; }
|
||
if ($shipType =~ m/5$/) { $shipTypeTxt = $shipTypeTxt . "Offshore_Supply_Vessel"; }
|
||
if ($shipType =~ m/6$/) { $shipTypeTxt = $shipTypeTxt . "Processing_Vessel"; }
|
||
if ($shipType =~ m/7$/) { $shipTypeTxt = $shipTypeTxt . "School/Scientific/Research/Training_Vessel"; }
|
||
if ($shipType =~ m/8$/) { $shipTypeTxt = $shipTypeTxt . "U.S._Public_or_Govt"; }
|
||
if ($shipType =~ m/9$/) { $shipTypeTxt = $shipTypeTxt . "Autonomous_or_Remotely-Operated"; }
|
||
}
|
||
|
||
# Other Vessels
|
||
elsif ($shipType =~ m/^3/) {
|
||
if ($shipType =~ m/0$/) { $shipTypeTxt = $shipTypeTxt . "Fishing"; }
|
||
if ($shipType =~ m/1$/) { $shipTypeTxt = $shipTypeTxt . "Towing-Pull"; }
|
||
if ($shipType =~ m/2$/) { $shipTypeTxt = $shipTypeTxt . "Towing-Big"; }
|
||
if ($shipType =~ m/3$/) { $shipTypeTxt = $shipTypeTxt . "Dredging_or_Underwater_Ops"; }
|
||
if ($shipType =~ m/4$/) { $shipTypeTxt = $shipTypeTxt . "Diving_Ops"; }
|
||
if ($shipType =~ m/5$/) { $shipTypeTxt = $shipTypeTxt . "Military_Ops"; }
|
||
if ($shipType =~ m/6$/) { $shipTypeTxt = $shipTypeTxt . "Sailing"; }
|
||
if ($shipType =~ m/7$/) { $shipTypeTxt = $shipTypeTxt . "Pleasure_Craft"; }
|
||
if ($shipType =~ m/8$/) { $shipTypeTxt = $shipTypeTxt . "Other:Reserved"; }
|
||
if ($shipType =~ m/9$/) { $shipTypeTxt = $shipTypeTxt . "Other:Reserved"; }
|
||
}
|
||
|
||
elsif ($shipType =~ m/^0/) { $shipTypeTxt = $shipTypeTxt . "Not-Available"; }
|
||
elsif ($shipType =~ m/^1/) { $shipTypeTxt = $shipTypeTxt . "Reserved"; }
|
||
elsif ($shipType =~ m/^4/) { $shipTypeTxt = $shipTypeTxt . "Passenger_or_High_Speed"; }
|
||
elsif ($shipType =~ m/^6/) { $shipTypeTxt = $shipTypeTxt . "Passenger-Big"; }
|
||
elsif ($shipType =~ m/^7/) { $shipTypeTxt = $shipTypeTxt . "Cargo"; }
|
||
elsif ($shipType =~ m/^8/) { $shipTypeTxt = $shipTypeTxt . "Tanker"; }
|
||
elsif ($shipType =~ m/^9/) { $shipTypeTxt = $shipTypeTxt . "Other"; }
|
||
|
||
# Decode 2nd digit for 1/4/6/7/8/9 types:
|
||
if ( ($shipType =~ m/^1/)
|
||
|| ($shipType =~ m/^4/)
|
||
|| ($shipType =~ m/^6/)
|
||
|| ($shipType =~ m/^7/)
|
||
|| ($shipType =~ m/^8/)
|
||
|| ($shipType =~ m/^9/) ) {
|
||
|
||
if ($shipType =~ m/0$/) { $shipTypeTxt = $shipTypeTxt . ""; }
|
||
if ($shipType =~ m/1$/) { $shipTypeTxt = $shipTypeTxt . "-Haz_Cat:A/X"; }
|
||
if ($shipType =~ m/2$/) { $shipTypeTxt = $shipTypeTxt . "-Haz_Cat:B/Y"; }
|
||
if ($shipType =~ m/3$/) { $shipTypeTxt = $shipTypeTxt . "-Haz_Cat:C/Z"; }
|
||
if ($shipType =~ m/4$/) { $shipTypeTxt = $shipTypeTxt . "-Haz_Cat:D/O"; }
|
||
if ($shipType =~ m/5$/) { $shipTypeTxt = $shipTypeTxt . "-Reserved"; }
|
||
if ($shipType =~ m/6$/) { $shipTypeTxt = $shipTypeTxt . "-Reserved"; }
|
||
if ($shipType =~ m/7$/) { $shipTypeTxt = $shipTypeTxt . "-Reserved"; }
|
||
if ($shipType =~ m/8$/) { $shipTypeTxt = $shipTypeTxt . "-Reserved"; }
|
||
if ($shipType =~ m/9$/) { $shipTypeTxt = $shipTypeTxt . ""; }
|
||
}
|
||
|
||
|
||
return($shipTypeTxt);
|
||
}
|
||
|
||
|
||
|
||
# Convert a 6-digit binary string to ASCII text
|
||
# Encoded AIS data: Each 6 bits represents one ASCII character.
|
||
#
|
||
# Each six-bit nibble maps to an ASCII character.
|
||
# Decimal 0-31 map to "@" ( ASCII 64) through "\_" (ASCII 95)
|
||
# Decimal 32-63 map to " " (ASCII 32) though "?" (ASCII 63)
|
||
# Lowercase, backtick, right/left braces, pipe, tilde and DEL can't be encoded
|
||
sub bin2text() {
|
||
my $input = shift;
|
||
my $final_string = "";
|
||
my $len = length($input);
|
||
my $i;
|
||
for ($i = 0; $i < $len; $i+=6) {
|
||
my $binary_char = substr($input, $i, 6);
|
||
# Convert from binary to decimal (ord)
|
||
my $d = unpack("N", pack("B32", substr("00000000000000000000000000000000" . $binary_char, -32)));
|
||
if ($d > 0) { # Skip NULL characters
|
||
if ($d < 32) { $d += 64; }
|
||
$final_string = $final_string . chr($d);
|
||
}
|
||
}
|
||
$final_string =~ s/\s\s/ /g;
|
||
return($final_string);
|
||
}
|
||
|
||
|
||
|
||
## Convert a signed binary string of ASCII 0's and 1's to a decimal
|
||
#sub signedBin2dec {
|
||
# my $input = shift;
|
||
#
|
||
# if ( substr($input, 0, 1) eq "1") { # Negative number
|
||
# # Pad to 32 bits with 1's
|
||
## return unpack("i", pack("B32", substr("11111111111111111111111111111111" . $input, -32)));
|
||
# # Invert all the bits. Add 1. Convert to decimal. Negate.
|
||
# my $ii;
|
||
# my $input2 = "";
|
||
# my $len = length($input);
|
||
# for ($ii = 0; $ii < $len; $ii++) {
|
||
# my $j = substr($input, $ii, 1);
|
||
# if ($j eq "0") { $j = "1"; }
|
||
# else { $j = "0"; }
|
||
# $input2 = $input2 . $j;
|
||
# }
|
||
##print "$input\n";
|
||
##print "$input2\n";
|
||
# my $binary = pack("B32", substr("00000000000000000000000000000000" . $input2, -32, 32));
|
||
# my $decimal = unpack("N", $binary);
|
||
# $decimal++;
|
||
# $decimal = -$decimal;
|
||
# return $decimal;
|
||
# }
|
||
# else { # Positive number
|
||
# # Pad to 32 bits with 0's
|
||
# return unpack("N", pack("B32", substr("00000000000000000000000000000000" . $input, -32))); #####
|
||
#
|
||
# }
|
||
#}
|
||
|
||
|
||
|
||
# Convert a signed binary string of ASCII 0's and 1's to a decimal the hard way.
|
||
# This subroutine should be platform-agnostic as it does all the work internally.
|
||
sub signedBin2dec {
|
||
my $input = shift;
|
||
my $input2 = "";
|
||
my $decimal;
|
||
my $multiplier;
|
||
|
||
#print "\n$input\n";
|
||
if ( substr($input, 0, 1) eq "1") { # Negative number
|
||
# Invert all the bits. Add 1. Convert to decimal. Negate.
|
||
my $ii;
|
||
my $len = length($input);
|
||
for ($ii = 0; $ii < $len; $ii++) {
|
||
my $j = substr($input, $ii, 1);
|
||
if ($j eq "0") { $j = "1"; }
|
||
else { $j = "0"; }
|
||
$input2 = $input2 . $j;
|
||
}
|
||
$decimal = &toDecimal($input2);
|
||
$decimal++;
|
||
$decimal = -$decimal;
|
||
return $decimal;
|
||
}
|
||
else { # Positive number
|
||
return &toDecimal($input);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
# Convert to decimal from positive binary string.
|
||
# Used by above signedBin2dec subroutine.
|
||
sub toDecimal {
|
||
my $input = shift;
|
||
my $multiplier = 1;
|
||
my $decimal = 0;
|
||
my $len = length($input);
|
||
# Start at LSB bit, work toward MSB bit
|
||
for (my $ii = $len-1; $ii >= 0; $ii--) {
|
||
if (substr($input, $ii, 1) == 1) {
|
||
$decimal = $decimal + $multiplier;
|
||
#print "1\tMult: $multiplier\n";
|
||
}
|
||
else {
|
||
#print "0\n";
|
||
}
|
||
$multiplier = $multiplier * 2 ;
|
||
}
|
||
return $decimal;
|
||
}
|
||
|
||
|
||
|
||
# Convert an unsigned binary string of ASCII 0's and 1's to a decimal
|
||
sub bin2dec {
|
||
my $temp = shift;
|
||
if (!defined($temp) || $temp eq "") { return; }
|
||
return unpack("N", pack("B32", substr("00000000000000000000000000000000" . $temp, -32))); #####
|
||
}
|
||
|
||
|
||
|
||
# Convert a decimal string (ASCII) to a binary string (ASCII)
|
||
sub dec2bin {
|
||
my $input = "00000000000000000000000000" . shift;
|
||
my $str = unpack("B32", pack("N", $input));
|
||
my $str2 = substr($str, -6);
|
||
return $str2;
|
||
}
|
||
|
||
|
||
|
||
# Decode MID out of MMSI ($userID) too:
|
||
# 8MIDXXXXX Diver's radio (not used in the U.S. in 2013)
|
||
# MIDXXXXXX Ship
|
||
# 0MIDXXXXX Group of ships; the U.S. Coast Guard, for example, is 03699999
|
||
# 00MIDXXXX Coastal stations
|
||
# 111MIDXXX SAR (Search and Rescue) aircraft
|
||
# 99MIDXXXX Aids to Navigation
|
||
# 98MIDXXXX Auxiliary craft associated with a parent ship
|
||
# 970MIDXXX AIS SART (Search and Rescue Transmitter)
|
||
# 972XXXXXX MOB (Man Overboard) device
|
||
# 974XXXXXX EPIRB (Emergency Position Indicating Radio Beacon) AIS
|
||
#
|
||
# NOTE: U.S. ships sometimes incorrectly send "669" for those first 3 digits.
|
||
# http://www.itu.int/online/mms/glad/cga_mids.sh?lng=E
|
||
#
|
||
sub decode_MID {
|
||
my $userID = shift;
|
||
my $MID = $userID;
|
||
if ($userID =~ m/^8.*/) { # Diver's radio
|
||
$MID =~ s/^8(...).*/$1/;
|
||
}
|
||
elsif ($userID =~ m/^00.*/) { # Coastal station
|
||
$MID =~ s/^00(...).*/$1/;
|
||
}
|
||
elsif ($userID =~ m/^0.*/) { # Group of ships
|
||
$MID =~ s/^0(...).*/$1/;
|
||
}
|
||
elsif ($userID =~ m/^111.*/) { # SAR aircraft
|
||
$MID =~ s/^111(...).*/$1/;
|
||
}
|
||
elsif ($userID =~ m/^99.*/) { # Navigation aid
|
||
$MID =~ s/^99(...).*/$1/;
|
||
}
|
||
elsif ($userID =~ m/^98.*/) { # Auxiliary craft
|
||
$MID =~ s/^98(...).*/$1/;
|
||
}
|
||
elsif ($userID =~ m/^970.*/) { # AIS SART
|
||
$MID =~ s/^970(...).*/$1/;
|
||
}
|
||
elsif ($userID =~ m/^972.*/) { # Man overboard device
|
||
$MID = "MOB";
|
||
}
|
||
elsif ($userID =~ m/^974.*/) { # EPIRB
|
||
$MID = "EPIRB";
|
||
}
|
||
else { # Ship
|
||
$MID =~ s/^(...).*/$1/;
|
||
}
|
||
|
||
my $country = "";
|
||
if (defined($countries{$MID})) {
|
||
$country = $countries{$MID};
|
||
}
|
||
else {
|
||
$country = "Unknown";
|
||
}
|
||
|
||
return $country;
|
||
}
|
||
|
||
|
||
|
||
# Escape characters from the shell
|
||
#
|
||
# Between double quotes the literal value of all characters is preserved except
|
||
# for dollar sign, backticks (backward single quotes, ``) and backslash.
|
||
#
|
||
# The backslash retains its meaning only when followed by dollar, backtick, double quote,
|
||
# backslash or newline. Within double quotes, the backslashes are removed from the input
|
||
# stream when followed by one of these characters. Backslashes preceding characters that
|
||
# don't have a special meaning are left unmodified for processing by the shell interpreter.
|
||
#
|
||
sub escape_from_shell {
|
||
$temp = shift;
|
||
$temp =~ s/\\/\\/g; # Look for \ and escape it. Do this one FIRST!
|
||
$temp =~ s/"/\\"/g; # Look for " and escape it
|
||
$temp =~ s/\$/\\\$/g; # Look for $ and escape it
|
||
$temp =~ s/\`/\\`/g; # Look for ` and escape it
|
||
return ($temp);
|
||
}
|
||
|
||
|
||
|
||
# Log messages to log file if logging enabled
|
||
#
|
||
sub log_aprs {
|
||
$sentence = shift;
|
||
if ($logging_mode) {
|
||
`echo \"$sentence\" >> $log_file`;
|
||
}
|
||
}
|
||
|
||
|