kkaiser has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks,

That is my first question and I hope to do everything properly.

I want to write an NMEA modul that reads the heading strings from a serial device. It is working properly but I'm using a tied filehandle for the strings and can't figure out how to autoflush the buffer. I have to destroy the object each time before I read. When I'm getting a value with readline I don't get the most recent value that I can see with minicom at the serial device.

On top I don't know if I created a proper filehandle in the module with gensym() and also don't know how to bless a global filehandle properly. I can't use the star notation (*)

Here's the module. I cut the part where I do checksum things etc. with the line. I'm also using the log4perl module but I replaced it with warn/die.

#!/usr/bin/perl # ### this module package GPS::NMEAserial; use strict; use warnings; # serial port communication use Device::SerialPort qw( :PARAM :STAT 0.07 ); # flush output of filehandle use IO::Handle; # create filehandles use Symbol qw( gensym ); sub new_nmea_port { my $class = shift; # open the serial port attached to control unit # $quiet parameter just for compatability my ( $port, $config_file, $quiet ) = @_; # this tests for an existing config file, # and creates one if necessary... if ( !-e $config_file ) { my $PortObj = new Device::SerialPort( $port, $quiet ) or die("Can't open $port: $!"); $PortObj->baudrate(4800); $PortObj->parity('none'); $PortObj->databits(8); $PortObj->handshake('none'); # when nmea data is available every second $PortObj->read_const_time(1100); $PortObj->read_char_time(700); $PortObj->stopbits(1); $PortObj->write_settings or undef $PortObj; $PortObj->save($config_file) or die("Can't save $config_file: $!"); # close serial port $PortObj->close or warn("Close $PortObj at $port failed: $!"); # destroy serial object # frees memory back to perl undef $PortObj; } else { print("$config_file already exists!"); } # tie filehandle to read/write from serial port (nmea device) my $fh = gensym(); tie( *$fh, 'Device::SerialPort', $config_file ) or die("Can't tie $port: $!"); my $self = bless { PortName => $port, FileHandle => $fh, ConfigurationFileName => $config_file }, $class; $self->{'FileHandle'}->autoflush(1); return $self; } # this shouldn't be necessary sub destroy_create_fh { my $self = shift; my $fh_self = $self->{'FileHandle'}; tie( *$fh_self, 'Device::SerialPort', $self->{'ConfigurationFileName'} ) or die("Can't tie $self->{'PortName'}: $!"); $self->{'FileHandle'} = $fh_self; } sub get_heading { my $self = shift; if ( !-e $self->{'PortName'} ) { warn("Device $self->{'PortName'} doesn't exist"); } $self->destroy_create_fh(); while ( my $line = readline( $self->{'FileHandle'} ) ) { print "Do something with $line"; return $line; } } 1;

Here's the script that calls the module. The device /dev/nmea is a symlink to /dev/ttyUSBX and created with an udev rule. The strings that appear look like: http://www.hemispheregps.com/gpsreference/GPHDT.htm

use strict; use warnings; use lib '/usr/local/lib'; use GPS::NMEAserial; my $nmea_unit = GPS::NMEAserial->new_nmea_port( '/dev/nmea', "/var/run/.nmea_unit.conf" ); my $heading_string = $nmea_unit->get_heading(); print "$heading_string\n"; sleep(10); my $heading_string2 = $nmea_unit->get_heading(); print "$heading_string2 is not most recent value\n";

I tried to leave it as small as possible. Mainly following part is bugging me.

# tie filehandle to read/write from serial port (nmea device) my $fh = gensym(); tie( *$fh, 'Device::SerialPort', $config_file ) or die("Can't tie $port: $!"); my $self = bless { PortName => $port, FileHandle => $fh, ConfigurationFileName => $config_file }, $class; $self->{'FileHandle'}->autoflush(1); return $self;

How can I create a filehandle for this module that it will always return me the last line from the serial port output? Or should I use a different approach for serial ports when using it in a module?

Replies are listed 'Best First'.
Re: serial port tied filehandle autoflush
by Anonymous Monk on Dec 04, 2014 at 13:58 UTC

    In my experience, getting serial ports working with the higher-level reading functions (also timeouts and graceful closing of the ports) can be quite finicky. In fact, in a recent script I wrote that does something similar to yours, I just read the port one byte at a time and do the buffering myself, it's not the most elegant but it's robust. The code resembles the "EXAMPLE" in Device::SerialPort.

    However, from perlvar:

    HANDLE->autoflush( EXPR ) ... This has no effect on input buffering. See getc for that.

    I don't see you writing to the port anywhere. So I don't think "autoflush" is the right term here, and perhaps you need to explain your problem better: In regards to what is being read/written from the port, what exactly is the expected behaviour, and what behaviour are you getting instead? Perhaps a short sample would help?

    How can I create a filehandle for this module that it will always return me the last line from the serial port output?

    By "last", do you mean "most recent"? I see a couple of options: Keep the port closed until you actually want to read; continually read the port and just remember the most recent line; or try the lookclear method from Device::SerialPort (although the docs are a little unclear to me whether lookclear will do what you need).

      Thanks for your response,

      the serial port is a heading compass. It will give you heading strings like:

      $HEHDT,123.49,T*12 $HEHDT,123.43,T*18 $HEHDT,123.43,T*18 $HEHDT,123.49,T*12 $HEHDT,123.49,T*12 $HEHDT,123.42,T*19

      According to the direction from true north (0°-359.9°). The last value is a checksum.

      I never write to the port. I just want the most recent heading (yes that's what I meant with last, sorry about that). The problem is the serial interface is rather fast. The output comes 10 times a second or one time a second.

      In my main routine I don't want the heading that fast. I just want it when I need it. When I call my method I don't get the most recent value I get a value from the buffer.

      There is many different sorts of compasses also some that output much more then just the heading. (GPS and other NMEA strings). I liked the tied filehandle approach (SerialPort::Port Methods for tied filehandles). My code already does what I want but I always retie or recreate the object. That way the first string I get is incomplete and thus invalid all the time plus the time I have to recreate the object, which is still fast but I don't believe it's the proper way.

      Here's my two different Log::Log4perl output:

      What I get:

      05.12.2014-08:46:19 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:271)> + Receiving ¤EHDT,217.62,T*1F on /dev/nmea 05.12.2014-08:46:19 DEBUG AA0009E (GPS/NMEAserial.pm:get_heading:276)> + Not receiving valid NMEA data or cut off string: ¤EHDT,217.62,T*1F ! 05.12.2014-08:46:19 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:271)> + Receiving $HEHDT,217.62,T*1F on /dev/nmea 05.12.2014-08:46:19 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:292)> + $VAR1 = bless( { '_CKS_' => '1F', 'TrueHeading' => '217.62', '_STRING_' => '$HEHDT,217.62,T*1F', '_calcCKS_' => '1F', '_ELEMENT_' => 'HDT', 'HDTT' => 'T', '_ERROR_' => undef, '_DEVICE_' => 'HE' }, 'NMEAdata' ); 05.12.2014-08:46:19 DEBUG AA0009E (GPS/NMEAserial.pm:get_heading:295)> + Received true heading: 217.62 ----------------------------------------------------- 05.12.2014-08:46:20 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:271)> + Receiving ¦L“ÉS‰•)ÓTHø$HEHDT,217.62,T*1F on /dev/nmea 05.12.2014-08:46:20 DEBUG AA0009E (GPS/NMEAserial.pm:get_heading:276)> + Not receiving valid NMEA data or cut off string: ¦L“ÉS‰&# +149;)ÓTHø$HEHDT,217.62,T*1F ! 05.12.2014-08:46:20 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:271)> + Receiving $HEHDT,217.62,T*1F on /dev/nmea 05.12.2014-08:46:20 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:292)> + $VAR1 = bless( { '_CKS_' => '1F', 'TrueHeading' => '217.62', '_STRING_' => '$HEHDT,217.62,T*1F', '_calcCKS_' => '1F', '_ELEMENT_' => 'HDT', 'HDTT' => 'T', '_ERROR_' => undef, '_DEVICE_' => 'HE' }, 'NMEAdata' ); 05.12.2014-08:46:20 DEBUG AA0009E (GPS/NMEAserial.pm:get_heading:295)> + Received true heading: 217.62 ----------------------------------------------------- Heading ACU: FE0080DA000000000000 Heading degrees: 217.62
      What I expect:
      05.12.2014-08:43:02 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:271)> + Receiving $HEHDT,123.47,T*1C on /dev/nmea 05.12.2014-08:43:02 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:292)> + $VAR1 = bless( { '_CKS_' => '1C', 'TrueHeading' => '123.47', '_STRING_' => '$HEHDT,123.47,T*1C', '_calcCKS_' => '1C', '_ELEMENT_' => 'HDT', 'HDTT' => 'T', '_ERROR_' => undef, '_DEVICE_' => 'HE' }, 'NMEAdata' ); 05.12.2014-08:43:02 DEBUG AA0009E (GPS/NMEAserial.pm:get_heading:295)> + Received true heading: 123.47 ----------------------------------------------------- 05.12.2014-08:43:04 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:271)> + Receiving $HEHDT,123.48,T*13 on /dev/nmea 05.12.2014-08:43:04 TRACE AA0009E (GPS/NMEAserial.pm:get_heading:292)> + $VAR1 = bless( { '_CKS_' => '13', 'TrueHeading' => '123.48', '_STRING_' => '$HEHDT,123.48,T*13', '_calcCKS_' => '13', '_ELEMENT_' => 'HDT', 'HDTT' => 'T', '_ERROR_' => undef, '_DEVICE_' => 'HE' }, 'NMEAdata' ); 05.12.2014-08:43:04 DEBUG AA0009E (GPS/NMEAserial.pm:get_heading:295)> + Received true heading: 123.48 ----------------------------------------------------- Heading ACU: FE00807B000000000000 Heading degrees: 123.48

      If there's more strings involved it just will take longer when there is only one dataset per second.

      So I would expect that I don't have to retie the serial port and not to get the first "bad" line all the time.

      If there's another way I could get rid of the filehandle I would be open for it. Important is just that I can use something similar to readline() to process all the strings I need. Like GPS and Heading for instance:

      $GPRMC,183729,A,3907.356,N,12102.482,W,000.0,360.0,080301,015.5,E*6F $GPRMB,A,,,,,,,,,,,,V*71 $GPGGA,183730,3907.356,N,12102.482,W,1,05,1.6,646.4,M,-24.1,M,,*75 $GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D $GPGSV,2,1,08,02,43,088,38,04,42,145,00,05,11,291,00,07,60,043,35*71 $GPGSV,2,2,08,08,02,145,00,09,46,303,47,24,16,178,32,26,18,231,43*77 $PGRME,22.0,M,52.9,M,51.0,M*14 $GPGLL,3907.360,N,12102.481,W,183730,A*33 $GPHDM,093.8,M*2B $PGRMZ,2062,f,3*2D $PGRMM,WGS 84*06 $GPBOD,,T,,M,,*47 $GPRTE,1,1,c,0*07 $GPRMC,183731,A,3907.482,N,12102.436,W,000.0,360.0,080301,015.5,E*67 $GPRMB,A,,,,,,,,,,,,V*71
      I need to go through all lines to find $GPRMC and $GPHDM which is what I want.

      Thanks for your help. It's really appreciated.

        Thanks for the additional information. To get the most recent line, there are the three previously mentioned approaches:

        1. Keep the port closed until you actually want to read. Advantage is that there is no CPU used while you don't want to read the port. Disadvantage is that the first line you read will very likely be partial, but since NMEA is checksummed you can identify and discard bad lines (or just always discard the first line). Since you want to read the port less than once per second, I wouldn't worry too much about the overhead of re-opening the port (unless it becomes a measurable issue).
        2. Continually read the port and remember the most recent line. Advantage is that you have full control over buffering and which line you keep and return to your program, and you won't get partial lines except for the very first one. Disadvantage is that some extra CPU is used to read and discard the unnecessary lines.
        3. Keep the port open, but clear the input buffers before reading. Not having used the tied interface before, I can't say for certain, but from the docs it sounds like Device::SerialPort's lookclear method might do what you want. Note that if the buffers are not line-based but byte-based, then after clearing the buffers it's very likely you'd get partial lines like in the first approach above. Another possible disadvantage to this method is that it's more wasteful than the first if you only read rarely, plus I don't know off the top of my head what the driver's behavior is if you let the input buffers fill up.
        So I would expect that I don't have to retie the serial port and not to get the first "bad" line all the time.

        In that case that limits you to the second or third approaches. (Although personally I don't mind getting and discarding garbled first lines when opening a serial port - that's always a side effect of receiving a continuous stream of data on a serial port.)

        My suggestion would be that (if you have the time for it) you could try out the third approach, and if that does turn out to have issues, go with the first or second approach depending on how often you want to read from the port (if often, I'd go with the second, if less often, the first).

Re: serial port tied filehandle autoflush
by Anonymous Monk on Dec 05, 2014 at 13:11 UTC
      I'm sorry about that it was my first approach to solve a problem like that. I added the link at stackoverflow.