Ham-APRS-FAP
view release on metacpan or search on metacpan
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.
# This allows declaration use Ham::APRS::FAP ':all';
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
##our %EXPORT_TAGS = (
## 'all' => [ qw(
##
## ) ],
##);
our @EXPORT_OK = (
## @{ $EXPORT_TAGS{'all'} },
'&parseaprs',
'&kiss_to_tnc2',
'&tnc2_to_kiss',
'&aprs_duplicate_parts',
'&count_digihops',
'&check_ax25_call',
'&distance',
'&direction',
'&make_object',
'&make_timestamp',
'&make_position',
'&mice_mbits_to_message',
);
##our @EXPORT = qw(
##
##);
our $VERSION = '1.21';
# Preloaded methods go here.
# no debugging by default
my $debug = 0;
my %result_messages = (
'unknown' => 'Unsupported packet format',
'packet_no' => 'No packet given to parse',
'packet_short' => 'Too short packet',
'packet_nobody' => 'No body in packet',
'srccall_noax25' => 'Source callsign is not a valid AX.25 call',
'srccall_badchars' => 'Source callsign contains bad characters',
'dstpath_toomany' => 'Too many destination path components to be AX.25',
'dstcall_none' => 'No destination field in packet',
'dstcall_noax25' => 'Destination callsign is not a valid AX.25 call',
'digicall_noax25' => 'Digipeater callsign is not a valid AX.25 call',
'digicall_badchars' => 'Digipeater callsign contains bad characters',
'timestamp_inv_loc' => 'Invalid timestamp in location',
'timestamp_inv_obj' => 'Invalid timestamp in object',
'timestamp_inv_sta' => 'Invalid timestamp in status',
'timestamp_inv_gpgga' => 'Invalid timestamp in GPGGA sentence',
'timestamp_inv_gpgll' => 'Invalid timestamp in GPGLL sentence',
'packet_invalid' => 'Invalid packet',
'nmea_inv_cval' => 'Invalid coordinate value in NMEA sentence',
'nmea_large_ew' => 'Too large value in NMEA sentence (east/west)',
'nmea_large_ns' => 'Too large value in NMEA sentence (north/south)',
'nmea_inv_sign' => 'Invalid lat/long sign in NMEA sentence',
'nmea_inv_cksum' => 'Invalid checksum in NMEA sentence',
'gprmc_fewfields' => 'Less than ten fields in GPRMC sentence ',
'gprmc_nofix' => 'No GPS fix in GPRMC sentence',
'gprmc_inv_time' => 'Invalid timestamp in GPRMC sentence',
'gprmc_inv_date' => 'Invalid date in GPRMC sentence',
'gprmc_date_out' => 'GPRMC date does not fit in an Unix timestamp',
'gpgga_fewfields' => 'Less than 11 fields in GPGGA sentence',
'gpgga_nofix' => 'No GPS fix in GPGGA sentence',
'gpgll_fewfields' => 'Less than 5 fields in GPGLL sentence',
'gpgll_nofix' => 'No GPS fix in GPGLL sentence',
'nmea_unsupp' => 'Unsupported NMEA sentence type',
'obj_short' => 'Too short object',
'obj_inv' => 'Invalid object',
'obj_dec_err' => 'Error in object location decoding',
'item_short' => 'Too short item',
'item_inv' => 'Invalid item',
'item_dec_err' => 'Error in item location decoding',
'loc_short' => 'Too short uncompressed location',
'loc_inv' => 'Invalid uncompressed location',
'loc_large' => 'Degree value too large',
'loc_amb_inv' => 'Invalid position ambiguity',
'mice_short' => 'Too short mic-e packet',
'mice_inv' => 'Invalid characters in mic-e packet',
'mice_inv_info' => 'Invalid characters in mic-e information field',
'mice_amb_large' => 'Too much position ambiguity in mic-e packet',
'mice_amb_inv' => 'Invalid position ambiguity in mic-e packet',
'mice_amb_odd' => 'Odd position ambiguity in mic-e packet',
'comp_inv' => 'Invalid compressed packet',
'msg_inv' => 'Invalid message packet',
'wx_unsupp' => 'Unsupported weather format',
'user_unsupp' => 'Unsupported user format',
'dx_inv_src' => 'Invalid DX spot source callsign',
'dx_inf_freq' => 'Invalid DX spot frequency',
'dx_no_dx' => 'No DX spot callsign found',
'tlm_inv' => 'Invalid telemetry packet',
'tlm_large' => 'Too large telemetry value',
'tlm_unsupp' => 'Unsupported telemetry',
'exp_unsupp' => 'Unsupported experimental',
'sym_inv_table' => 'Invalid symbol table or overlay',
);
=over
=item result_messages( )
Returns a reference to a hash containing all possible
return codes as the keys and their plain english descriptions
as the values of the hash.
=back
=cut
sub result_messages()
{
return \%result_messages;
}
_a_err($rethash, 'gprmc_inv_date', "$year $2 $1");
return 0;
}
$month = $2 + 0; # force numeric
$day = $1 + 0;
} else {
_a_err($rethash, 'gprmc_inv_date');
return 0;
}
# Date_to_Time() can only handle 32-bit unix timestamps,
# so make sure it is not used for those years that
# are outside that range.
if ($year >= 2038 || $year < 1970) {
$rethash->{'timestamp'} = 0;
_a_err($rethash, 'gprmc_date_out', $year);
return 0;
} else {
$rethash->{'timestamp'} = Date_to_Time($year, $month, $day, $hour, $minute, $second);
}
# speed (knots) and course, make these optional
# in the parsing sense (don't fail if speed/course
# can't be decoded).
if ($nmeafields[7] =~ /^\s*(\d+(|\.\d+))\s*$/o) {
# convert to km/h
$rethash->{'speed'} = $1 * $knot_to_kmh;
}
if ($nmeafields[8] =~ /^\s*(\d+(|\.\d+))\s*$/o) {
# round to nearest integer
my $course = int($1 + 0.5);
# if zero, set to 360 because in APRS
# zero means invalid course...
if ($course == 0) {
$course = 360;
} elsif ($course > 360) {
$course = 0; # invalid
}
$rethash->{'course'} = $course;
} else {
$rethash->{'course'} = 0; # unknown
}
# latitude and longitude
my $latitude = _nmea_getlatlon($nmeafields[3], $nmeafields[4], $rethash);
if (not(defined($latitude))) {
return 0;
}
$rethash->{'latitude'} = $latitude;
my $longitude = _nmea_getlatlon($nmeafields[5], $nmeafields[6], $rethash);
if (not(defined($longitude))) {
return 0;
}
$rethash->{'longitude'} = $longitude;
# we have everything we want, return
return 1;
} elsif ($nmeafields[0] eq 'GPGGA') {
# we want at least 11 fields
if (@nmeafields < 11) {
_a_err($rethash, 'gpgga_fewfields', scalar(@nmeafields));
return 0;
}
# check for position validity
if ($nmeafields[6] =~ /^\s*(\d+)\s*$/o) {
if ($1 < 1) {
_a_err($rethash, 'gpgga_nofix', $1);
return 0;
}
} else {
_a_err($rethash, 'gpgga_nofix');
return 0;
}
# Use the APRS time parsing routines to check
# the time and convert it to timestamp.
# But before that, remove a possible decimal part
$nmeafields[1] =~ s/\.\d+$//;
$rethash->{'timestamp'} = _parse_timestamp($options, $nmeafields[1] . 'h');
if ($rethash->{'timestamp'} == 0) {
_a_err($rethash, 'timestamp_inv_gpgga');
return 0;
}
# latitude and longitude
my $latitude = _nmea_getlatlon($nmeafields[2], $nmeafields[3], $rethash);
if (not(defined($latitude))) {
return 0;
}
$rethash->{'latitude'} = $latitude;
my $longitude = _nmea_getlatlon($nmeafields[4], $nmeafields[5], $rethash);
if (not(defined($longitude))) {
return 0;
}
$rethash->{'longitude'} = $longitude;
# altitude, only meters are accepted
if ($nmeafields[10] eq 'M' &&
$nmeafields[9] =~ /^(-?\d+(|\.\d+))$/o) {
# force numeric interpretation
$rethash->{'altitude'} = $1 + 0;
}
# ok
return 1;
} elsif ($nmeafields[0] eq 'GPGLL') {
# we want at least 5 fields
if (@nmeafields < 5) {
_a_err($rethash, 'gpgll_fewfields', scalar(@nmeafields));
return 0;
}
# latitude and longitude
my $latitude = _nmea_getlatlon($nmeafields[1], $nmeafields[2], $rethash);
if (not(defined($latitude))) {
return 0;
}
$rethash->{'latitude'} = $latitude;
my $longitude = _nmea_getlatlon($nmeafields[3], $nmeafields[4], $rethash);
if (not(defined($longitude))) {
return 0;
}
$rethash->{'longitude'} = $longitude;
# Use the APRS time parsing routines to check
# the time and convert it to timestamp.
# But before that, remove a possible decimal part
if (@nmeafields >= 6) {
$nmeafields[5] =~ s/\.\d+$//;
$rethash->{'timestamp'} = _parse_timestamp($options, $nmeafields[5] . 'h');
if ($rethash->{'timestamp'} == 0) {
_a_err($rethash, 'timestamp_inv_gpgll');
return 0;
}
}
if (@nmeafields >= 7) {
# GPS fix validity supplied
if ($nmeafields[6] ne 'A') {
_a_err($rethash, 'gpgll_nofix');
return 0;
}
}
# ok
return 1;
##} elsif ($nmeafields[0] eq 'GPVTG') {
##} elsif ($nmeafields[0] eq 'GPWPT') {
} else {
$nmeafields[0] =~ tr/[\x00-\x1f]//d;
_a_err($rethash, 'nmea_unsupp', $nmeafields[0]);
return 0;
}
return 0;
}
# Parse the possible APRS data extension
# as well as comment
sub _comments_to_decimal($$$) {
my $rest = shift @_;
my $srccallsign = shift @_;
my $rethash = shift @_;
# First check the possible APRS data extension,
# immediately following the packet
if (length($rest) >= 7) {
if ($rest =~ /^([0-9. ]{3})\/([0-9. ]{3})/o) {
my $course = $1;
my $speed = $2;
if ($course =~ /^\d{3}$/o &&
$course <= 360 &&
$course >= 1) {
# force numeric interpretation
$course += 0;
$rethash->{'course'} = $course;
} else {
# course is invalid, set it to zero
$rethash->{'course'} = 0;
}
if ($speed =~ /^\d{3}$/o) {
# force numeric interpretation
# and convert to km/h
$rethash->{'speed'} = $speed * $knot_to_kmh;
} else {
# If speed is invalid, don't set it
# (zero speed is a valid speed).
}
$rest = substr($rest, 7);
} elsif ($rest =~ /^PHG(\d[\x30-\x7e]\d\d[0-9A-Z])\//o) {
# PHGR
$rethash->{'phg'} = $1;
$rest = substr($rest, 8);
} elsif ($rest =~ /^PHG(\d[\x30-\x7e]\d\d)/o) {
# don't do anything fancy with PHG, just store it
$rethash->{'phg'} = $1;
( run in 1.826 second using v1.01-cache-2.11-cpan-e1769b4cff6 )