in reply to Total fault tolerance for perl GPS program
I notice that the GPS::NMEA module hasn't been updated since 2009. gpsd was updated in 2022, so there's a good chance that gpsd knows tricks to work around buggy hardware that GPS::NMEA doesn't know. GPS::NMEA seems to use SIGALRM for a timeout on reads, so it *ought* to wake up eventually.
If you'd like some sample code for accessing gpsd, here are some excerpts from mine. (this is designed to be run from a graphics loop where ->update is called repeatedly every few milliseconds and needs to never hang)
Disclaimer: there are simpler/cleaner ways to write this code, especially if you use Mojo::IOLoop, IO::Async, or AnyEvent. This is just what I have, from 2014, in a very "get it done" sort of C-programmer mentality.
use Moo; ... my $json= JSON->new->utf8->allow_nonref; has source => is => 'ro', required => 1, default => sub +{ 'tcp:127.0.0.1:2947' }; sub has_daemon { return defined shift->_socket } sub has_device { return scalar @{ (shift->_devices_packet // + {})->{devices} // [] } } sub has_fix { return defined shift->longitude } has connect_retry_delay => is => 'rw', default => sub { 3 }; has read_fail_count => is => 'rwp', default => sub { 0 }; has last_connect_ts => is => 'rwp', default => sub { 0 }; has last_packet_ts => is => 'rwp', default => sub { 0 }; has last_location_ts => is => 'rwp', default => sub { 0 }; has _source_packed_addr => is => 'lazy'; has _socket => is => 'rw'; has _socket_rbuf => is => 'rw', default => sub { '' }; has _version_packet => is => 'rw'; has _devices_packet => is => 'rw'; has _sky_packet => is => 'rw'; has _tpv_packet => is => 'rw'; ... sub devices { return (shift->_devices_packet // {})->{dev +ices} } sub gps_time_str { return (shift->_tpv_packet // {})->{time} } sub gps_ts { my $self= shift; return undef unless $self->gps_time_str; my $d= $self->gps_time_str; $d =~ s/\.\d\d\dZ$//; return Time::Piece->strptime($d, '%Y-%m-%dT%H:%M:%S')->epoch; } sub latitude { return (shift->_tpv_packet // {})->{lat} } sub longitude { return (shift->_tpv_packet // {})->{lon} } sub satellites { return (shift->_sky_packet // {})->{satelli +tes} } sub lat_km_per_degree { return (shift->_tpv_avg // {})->{lat_deg_km +} } sub lon_km_per_degree { return (shift->_tpv_avg // {})->{lon_deg_km +} } ... sub _build__source_packed_addr { my $self= shift; my $spec= $self->source; if ($spec =~ /^tcp:(.*):(\d+)/) { my ($addr, $port)= ($1, $2); my $packed_addr= inet_aton($addr) // croak "Can't resolve $add +r"; return sockaddr_in($port, $packed_addr); } elsif ($spec =~ /^unix:(.*)/) { return sockaddr_un($1); } else { croak "Unknown address type '$spec'"; } } sub _connect { my $self= shift; $self->_set_last_connect_ts(time_mon); socket(my $sock, AF_INET, SOCK_STREAM, 0) or croak "socket: $!"; connect($sock, $self->_source_packed_addr) or croak "connect: $!"; $sock->blocking(0); $sock->autoflush(1); $self->_socket_rbuf(''); $self->_socket($sock); } sub try_connect { my $self= shift; if ($self->has_daemon) { $self->_reset; } $log->info("connecting to ", $self->source); try { $self->_connect; $self->send('?WATCH={"enable":true,"json":true};'); $log->info("connection succeeded"); 1; } catch { chomp( my $err= $_ ); $log->error("connection failed: $err"); 0; }; } sub update { my $self= shift; local $SIG{PIPE}= 'IGNORE'; if (!defined $self->_socket) { return 0 if defined $self->last_connect_ts and time_mon - $self->last_connect_ts < $self->connect_ret +ry_delay; return 0 unless $self->try_connect; } # Max 5 messages per loop my $i= 0; while (++$i <= 5) { my $msg= $self->_next_line; if (defined $msg) { chomp $msg; try { my $data= $json->decode($msg); $self->_set_last_packet_ts(time_mon); $self->process_message($data); } catch { $log->error("failed to process message \"$msg\": $_"); }; } elsif (!$self->_socket) { return; } else { last; } } # If it's been too long since the last location, reset it to undef $self->_tpv_packet(undef) if time_mon - $self->last_location_ts > 15; # Clear the other two if its been a while since we got anything if (time_mon - $self->last_packet_ts > 20) { if ($self->_devices_packet) { # re-request the device list, but only the first time we c +lear it $self->send('?DEVICE;') if $self->_socket; $self->_devices_packet(undef); } $self->_sky_packet(undef); } # If we have TPV, and it's different from the last one, then add i +t to the # smoothed coordinates. my $tpv= $self->_tpv_packet; my $avg= $self->_tpv_avg; my $t= $self->last_location_ts; if ($tpv && $t > $avg->{ts}) { my $dT= $t - $avg->{ts}; $self->_lat_curve->add_point($t, $tpv->{lat}); $self->_lon_curve->add_point($t, $tpv->{lon}); push @{ $avg->{hist} }, [ $t, $tpv->{lat}, $tpv->{lon} ]; shift @{ $avg->{hist} } if @{ $avg->{hist} } > 6; $avg->{lat}= $self->_lat_curve->evaluate($t); $log->debugf("(x(%.5f) - %.5f) * %.5f + %.5f = %.5f", $t, $self->_lat_curve->hist->[0][0], $self->_lat_curve->_avg_line->[1], $self->_lat_curve->_avg_line->[0], $avg->{lat}); $avg->{lon}= $self->_lon_curve->evaluate($t); $avg->{lat_velocity}= $avg->{lat} - $self->_lat_curve->evaluat +e($t-1); $avg->{lon_velocity}= $avg->{lon} - $self->_lon_curve->evaluat +e($t-1); $avg->{lat_deg_km}= deg2rad(1) * 6371.64; $avg->{lon_deg_km}= haversine_distance($avg->{lat},$avg->{lon} +, $avg->{lat},($avg->{lon}+1)) * 6371.64; $avg->{ts}= $t; } } sub send { my ($self, $msg)= @_; $self->_socket->print("$msg\n"); } sub _next_line { my $self= shift; my $msg= $self->_socket->getline; if (defined $msg) { $self->_set_read_fail_count(0); if ($msg =~ /\n$/) { $msg= $self->_socket_rbuf . $msg; $self->_socket_rbuf(''); return $msg; } # else $self->{_socket_rbuf} .= $msg; } elsif ($!{EWOULDBLOCK} || $!{EAGAIN}) { } elsif ($self->_socket->eof) { $log->info("connection closed"); $self->_reset; } else { $log->info("unexpected error from recv: $!"); if (++$self->{read_fail_count} > 3) { $log->info("too many read failures (".$self->read_fail_cou +nt."), will reconnect"); $self->_reset; } } return undef; } sub _reset { my $self= shift; $self->_socket_rbuf(''); $self->_socket->close if $self->_socket; $self->_socket(undef); $self->_set_read_fail_count(0); $self->_set_last_connect_ts(undef); # Reset data fields $self->_version_packet(undef); $self->_devices_packet(undef); $self->_sky_packet(undef); $self->_tpv_packet(undef); } sub process_message { my ($self, $msg)= @_; my $class= delete $msg->{class}; defined $class or die "Mesage missing 'class'"; if ($class eq 'DEVICES') { $self->_devices_packet($msg); } elsif ($class eq 'DEVICE') { $self->_devices_packet({ devices => [ $msg ]}); } elsif ($class eq 'VERSION') { $self->_version_packet($msg); } elsif ($class eq 'SKY') { $self->_sky_packet($msg); } elsif ($class eq 'TPV') { $self->_tpv_packet($msg); $self->_set_last_location_ts(time_mon); } } 1;
|
---|
Replies are listed 'Best First'. | |
---|---|
Re^2: Total fault tolerance for perl GPS program
by Anonymous Monk on May 03, 2024 at 11:50 UTC | |
by NERDVANA (Priest) on May 04, 2024 at 03:52 UTC | |
by Anonymous Monk on May 04, 2024 at 07:12 UTC |