Datalogger.PL polls TCP, UDP, and serial streams for line delimited data and logs it to a file with optional timestamps and source notation.

Usage


	  usage: ./Datalogger.PL [options]

    options: 
      -p 
      --port         ports or sources (see below)
                               default: T192.168.0.19:4000 S/dev/ttyUSB0
      -f 
      --filename  default: data.log
      -c       
      --configFile  default: 
      -T
      --timestamping           turn on timestamping; default 0
      -P
      --logport                turn on port logging; default 0
      -v
      --verbose                turn on verbose output to STDERR
      -m
      --monolithic             specify monolithic log file rather than interval
      -i 
      --interval      interval for each log file
                               default: 3600 
      -d []
      --subdirectory []   use subdirectory organization of log files
                                  default: 
      -h              this help text


Ports (sources) can be any combination of TCP, UDP, and serial devices.

TCP ports are specified by prefacing the IP address:port with 'T' as in:
   T192.168.0.101:400 or Tserver_address.net:400
 where the number following the colon is the port number, in this case 400.

UDP ports  are specified by prefacing the IP address:port with 'U' as in:
   U192.168.0.101:400 or Userver_address.net:401
 where the number following the colon is the port number, in this case, 401.

Serial devices are specified by prefacing the device name with a 'S' and
 appending the communications parameters as in:
   S/dev/ttyUSB0:9600,8,1,N or Scom1:9600,8,1,N
 where the communications parameters are, in this case, 9600 baud, 8 bits,
 1 stop bit, no parity 

Multiple ports may be listed as separate options, as in: 
  -p Tdata_server.net:400 -p S/dev/ttyS0
 or as a single, quoted, space delimited option, as in:
  -p "Tdata_server.net:400 S/dev/ttyS0"

Perl Code

#!/usr/bin/perl
# $Id: DataLogger.PL,v 1.5 2007/07/02 08:43:30 ben Exp ben $
# a port scanning client for logging data from B&B Vlinx ports
# and serial ports (added November, 2006)
# copywrite Ben Smith, S/V Mother of Perl, 2007
# designed specifically for the Linkstation but may work
# elsewhere
# 28 May, 2006 - added subdirectory log organization
#########################################################

use strict;
use warnings;
use IO::Socket::INET;
use IO::Select;
use Getopt::Long;
use Time::HiRes qw(gettimeofday);

# defaults
my $program_name = $0;
my $fileStartTime = 0;
my $logFileName = "data.log";
my @inputs;
my @defaultInputs = qw(T192.168.0.19:4000 S/dev/ttyUSB0);
my $timeStamp = 0;
my $portLog = 0;
my $verbose = 0;
my $fileInterval = 60 * 60;
my $monolithic = 0;
my $help = 0;
my $subDir = ''; # use subdirectory organization of log files
my $address = '';
my $configFile = '';
my $debug = 0;

if(-r $configFile) {
    # read in previous settings
    if(open SETTINGS, "<$configFile") {
	my @settings = <SETTINGS>;
	if(! eval @settings) {
	    warn("could not evaluate $configFile: $!\n");
	}
    }
}


GetOptions("port|source|input|p=s" => \@inputs, # the list of command line 
                                                # specified inputs to log
	   "filename|f=s" => \$logFileName,
	   "configFile|c=s" => \$configFile,
	   "timestamp|T" => \$timeStamp,
	   "logport|P" => \$portLog,
	   "verbose|v" => \$verbose,
	   "monolithic|m" => \$monolithic,
	   "interval|i=n" => \$fileInterval,
	   "subdirectory|d=s" => \$subDir,
           "x=n" => \$debug,
	   "help|h" => \$help,
	   );
	   
# set default inputs if none specified
print STDERR join(" ",@inputs),"\n" if $debug & 1;
@inputs = @defaultInputs unless @inputs;

# process command line arguments

my $selector = IO::Select->new();


if($help) {
    print STDERR <<"EOT";
  usage: $program_name [options]

    options: 
      -p <portlist>
      --port <portlist>        ports or sources (see below)
                               default: @inputs
      -f <logFileName>
      --filename <logFileName> default: $logFileName
      -c <configFileName>      
      --configFile <configFileName> default: $configFile
      -T
      --timestamping           turn on timestamping; default $timeStamp
      -P
      --logport                turn on port logging; default $portLog
      -v
      --verbose                turn on verbose output to STDERR
      -m
      --monolithic             specify monolithic log file rather than interval
      -i <seconds>
      --interval <seconds>     interval for each log file
                               default: $fileInterval 
      -d [<subdir>]
      --subdirectory [<subdir>]   use subdirectory organization of log files
                                  default: $subDir
      -h              this help text


Ports (sources) can be any combination of TCP, UDP, and serial devices.

TCP ports are specified by prefacing the IP address:port with 'T' as in:
   T192.168.0.101:400 or Tserver_address.net:400
 where the number following the colon is the port number, in this case 400.

UDP ports  are specified by prefacing the IP address:port with 'U' as in:
   U192.168.0.101:400 or Userver_address.net:401
 where the number following the colon is the port number, in this case, 401.

Serial devices are specified by prefacing the device name with a 'S' and
 appending the communications parameters as in:
   S/dev/ttyUSB0:9600,8,1,N or Scom1:9600,8,1,N
 where the communications parameters are, in this case, 9600 baud, 8 bits,
 1 stop bit, no parity 

Multiple ports may be listed as separate options, as in: 
  -p Tdata_server.net:400 -p S/dev/ttyS0
 or as a single, quoted, space delimited option, as in:
  -p "Tdata_server.net:400 S/dev/ttyS0"
EOT
exit;
}

# figure the port list

# break down the multiple input port specs into a list of individual inputs
my @portList;
for my $input (@inputs){ # look at each input element to see if it might
                     # consist of a space seperated list that needs 
                     # to be split into parts
    @portList = (@portList, split(/\s+/,$input)) ; 
}

my %inputStructs; # a structure of info about the input ports

# try and open each of the inputs in the input port list
foreach my $port (@portList){
    $port =~ tr/a-z/A-Z/; # make uppercase
    if (my($type,$address,$settings) = ($port =~ m/(^.)([^:]+):(.*)/)) {
	print STDERR "$port: $type $address $settings\n" if $debug & 1;


	# TCP
	if($type  eq 'T'){ # tcp socket port
	    my $socket = IO::Socket::INET->new(
					       PeerAddr => $address,
					       PeerPort => $settings,
					       Proto    => "tcp",
					       Type     => SOCK_STREAM);
	    if($socket) {
		$selector->add($socket); # add to IO::Select list
		$inputStructs{$socket} = $settings; # build an association
	    }
	}

	#UDP
	if($type eq 'U'){ # udp socket port
	    my $socket = IO::Socket::INET->new(
					       PeerAddr => $address,
					       PeerPort => $settings,
					       Proto    => "udp",
					       Type     => SOCK_STREAM);
	    if($socket) {
		$selector->add($socket); # add to IO::Select list
		$inputStructs{$socket} = $settings; # build an association
	    }
	}

	#SERIAL
	if($type eq 'S'){ # serial port
	    if(-r $address) {
		my($baud,$bits,$stop,$parity) = split(/\,/,$settings);
		my($parityStr, $stopStr);
		
		# assuming we have a **IX OS with stty
		if(open( my $serialFH,"<$address") ) {
		    if($bits == 8) {
			if($parity eq 'E') {
			    $parityStr = "-parodd";
			} else {
			    $parityStr = "parodd";
			}
		    } else { # not parity bit
			$parityStr = "parenb";
		    }
		    
		    if($stop == 1) {
			$stopStr = "-cstopb";
		    } else {
			$stopStr = "cstopb";
		    }
		    system("stty -F $address $baud $parityStr $stopStr");
		    $selector->add($serialFH);
		    $inputStructs{$serialFH} = $address;
		}
	    }
	}
	
    }
} # end of inputs setup

if (! $selector->count() ){
    my $noPortStr = "\t" . join("\n\t",@portList) . "\n";
    die "cannot make any connections to specified input:\n${noPortStr}";
}

if($monolithic) { # just open the logfile this once, if it is to be monolithic
    OpenLogFile();
}

# handle interupt and quit signals
$SIG{INT} = \&CloseAll;
$SIG{QUIT} = \&CloseAll;

# poll the (succesful and ready) connections for input
while(1) {

    # check to see if we should open a new log file
    my $wholeSecs = time();
    if(! $monolithic && (( $wholeSecs - $fileStartTime) > $fileInterval) ) {
	OpenLogFile($wholeSecs);
	$fileStartTime = $wholeSecs;
    }

    # read the port
    foreach my $port ($selector->can_read) {
	# read from the socket
	my $data = readline($port);
	chomp $data;

	my $output = "";
	
	#prepend portNo if requested
	if($portLog) {
	    $output .= "$inputStructs{$port}\t";
	}
	    
	#prepend timestamp if requessted
	if($timeStamp) {
	    my $secs = gettimeofday();
	    my $timestr = sprintf("%013.3f",$secs);
	    $output .= "$timestr\t";
	}

	# concat actual string
	$output .= "$data\n";

	print LOGFILE $output;
	print STDERR $output if $verbose;
    }
}

sub OpenLogFile { # names and open the LOGFILE 
    my $secs = shift;
    my $filename = ($monolithic) ? $logFileName : 
	MakeFileName($logFileName,$secs);

    close LOGFILE;
    open(LOGFILE, ">$filename") || 
	die "cannot open \"$filename\" for logging: $!\n";
}


sub MakeFileName {
    my ($filename,$insert) = @_;
    my $path;
    my $pathFilename;

    if($subDir){
	my($sec,$min,$hour,$mday,$month,$year) = localtime(time());
	++$month; # UNIX month starts at zero
	$year %= 100; # use only last two digits of year

	# make strings with elements
	$year = sprintf("year-%02d",$year);
	$month = sprintf("month-%02d",$month);
	$mday = sprintf("day-%02d",$mday);

	#create path
	$path = ($subDir) ? "$subDir/" : "./"; # start with specified 
                                       # subdir if specified

	print STDERR "subDir: $subDir\n" if $debug & 2;
	print STDERR "logging path: $path\n" if $debug & 2;

	foreach my $dir($year,$month,$mday) {
	    $path .= "$dir/";
	    mkdir $path unless -d $path;
	}
	$pathFilename = "${path}${filename}";
    } else {
	$pathFilename = $filename;
    }

    # break the filename into path+prefix and suffix
    my($prefix,$suffix)= ($pathFilename =~ m/(^.*)\.(.*$)/ ); #)

    # recombine using the insert			  
    return "${prefix}-${insert}.${suffix}";
 }
			  
 

sub CloseAll {
    my $filehandle = shift;
    my @ports = @_;

    print STDERR "Closing all sockets and log file\n" if $verbose;

    # close all the input
    foreach my $port (@ports) {
	
	close($port);
    }

    #close the output
    close($filehandle);

    exit;
}

#######################################################################
# $Log: DataLogger.PL,v $
# Revision 1.5  2007/07/02 08:43:30  ben
# cleaned up subdirectory logging
#
# Revision 1.4  2007/07/02 08:02:06  ben
# A little clean-up of variables and their initialization
#
# Revision 1.3  2007/07/01 22:33:26  ben
# simplified specification of ports and got the darn thing working again.

Home
 Art
 Writing
 Boat
 Photos
 Videos
 Hydrography
   nmea2xyzt
   NMEAfields
   Datalogger
    Source
  Cleaning
  Analysis
  GridPlot
 Blog
 SAACalendar