#!/usr/bin/perl
# This is a major re-write of the original daemon I wrote, although
# quite a bit of code was re-used where it made sense.
##########################################################################
#                                                                        #
#                       LOCAL CONFIG OPTIONS                             #
#    Set the variables below to suit your preference and needs.          #
#    See the embedded documentation, use perldoc, or run pod2man         #
#    as outlined in the README and then read the resulting man page      #
#    for further details about these settings.                           #
#                                                                        #
##########################################################################
my $tty = "/dev/ttyS1";
my $facility = "local0"; # which facility to use for syslog
my $name = "vanprod"; # name of the executable for syslog and wu
my $directory = "/some/path/to/weather"; # working directory
my $piddir = "/var/run"; # directory to write pid file $name.pid
my $num_updates = 12; # number of time per hour to update wu.
my $wu_id = "MYSTATION"; # your station id on weatherunderground
my $wu_password = "mypassword"; # your weatherunderground password
my $oldobs = 7; # how many of the daily observations files to keep
my $db = "mysql"; # type of database we're talking to
my $db_name = "weather"; # name of $db database
my $db_user = "stan"; # name of db user that can add records
my $db_password = "mypassword"; # password for user above
my $db_table = "reports"; # name of db table for values to be inserted
##########################################################################
#                          END LOCAL CONFIG OPTIONS                      #
##########################################################################

=head1 NAME

vanprod - Perl daemon to communicate with a Davis Vantage Pro weather station.

=head1 SYNOPSIS

vanprod [ -tdn ]

=head1 DESCRIPTION

B<-t>  Test mode.  vanprod will not become a daemon.  It will test for correct communication with the weather station, print the results on stdout and exit.

B<-d>  Debug mode.  This is intended only for short term diagnosis of problems you may be having.  vanprod will become a daemon, but it will be EXTREMELY chatty in writing information into the syslog at the debug priority.  You may also use this in conjuction with -t for inital testing and debugging.

B<-n>  No database updates.  vanprod will not try to load the DBI module or make any updates to a database.  Text files as described below are not affected by this option.



=head2 Local Configuration Options

There is a section near the beginning of the daemon source where you will need to set several variables to suit your needs.  Currently there are no command line overrides for these settings.  Perhaps in a future release this will be changed.

B<my $tty = "/dev/ttyS1";> This is the serial port where your weather station is connected.

B<my $facility = "local0";> This is the syslog facility that the daemon will use for messages.  Make sure that what you put here is configured in your syslog.conf file.

B<my $name = "vanprod";>  I put that in just in case you didn't like the name I picked for the daemon.  This name is associated with every syslog message the daemon generates as well as every update to weather underground if you use that feature.  It is also used for the pid file. (See below)

B<my $directory = "/some/path/to/weather";>  This is the directory where the daemon will write the output files it generates.  Details on these files and their contents are described below.

B<my $piddir = "/var/run";>  This is the directory where the daemon will write a file containing it's pid.  The name of the file is $name.pid.

B<my $num_updates = 12;>  This is the number of times per hour to post updates to weather underground.  The default setting is every 5 minutes.  For every 10 minutes set this to 6, for every 15 set it to 4, etc.  If you set this to anything less than 0 or greater than 30 it will revert to the default of 12.  If you do not want to update weather underground at all set this to 0. 

B<my $wu_id = "MY_STATION";>  Set this to your station id assigned to you by weather underground.

B<my $wu_password = "mypassword";>   Set this to your weather underground password.

B<my $oldobs = 7;>  How many days old a dailyobs file should be to be deleted when a new day is started.  Details on what this file contains are below.  Note that this value will only be used when the daemon is running at midnight and starting a new day as part of it's own internal processes.  If you would like to keep these files forever, you can set this to -1.  The dailyobs file is typically between 70 and 75 KB for a 24 hour period.  


B<my $db = "mysql";>  Type of database you want to use to store your daily extremes.  The daemon will store the minimum and maximum values and the time they occurred in the database.  

B<my $db_name = "weather";>  The name of the database to use.

B<my $db_user = "stan";>  Name of database user that can add records to the database you specified on the previous line.

B<my $db_password = "mypassword";> Password for the database user described above.

B<my $db_table = "reports";>  Name of database table that contains the values we track and store.  The following commands should set things up for you:

	mysql> CREATE TABLE `reports` (
	`date` DATE NOT NULL ,
	`hi_temp` INT( 3 ) ,
	`hi_temp_time` TIME,
	`lo_temp` INT( 3 ) ,
	`lo_temp_time` TIME,
	`peak_wind` INT( 3 ) ,
	`peak_direct` CHAR( 3 ) ,
	`peak_time` TIME,
	`min_wind_chill` INT( 3 ) ,
	`wind_chill_time` TIME,
	`year_rain` DECIMAL( 4, 2 ) ,
	`max_rain_rate` DECIMAL( 4, 2 ) ,
	`max_rain_time` TIME,
	`max_barometer` DECIMAL( 4, 2 ) ,
	`max_press_time` TIME,
	`min_barometer` DECIMAL( 4, 2 ) ,
	`min_press_time` TIME,
	`max_humid` INT( 3 ) ,
	`max_humid_time` TIME,
	`min_humid` INT( 3 ) ,
	`min_humid_time` TIME,
	`max_dew` INT( 3 ) ,
	`max_dew_time` TIME,
	`min_dew` INT( 3 ) ,
	`min_dew_time` TIME,
	`max_heat_index` INT( 3 ) ,
	`heat_index_time` TIME,
	`daily_rain` DECIMAL( 4, 2 ) ,
	`max_intemp` INT( 3 ) ,
	`max_intemp_time` TIME ,
	`min_intemp` INT( 3 ) ,
	`min_intemp_time` TIME ,
	`max_inhumid` INT( 3 ) ,
	`max_inhumid_time` TIME,
	`min_inhumid` INT( 3 ) ,
	`min_inhumid_time` TIME,
	`max_indew` INT( 3 ) ,
	`max_indew_time` TIME,
	`min_indew` INT( 3 ) ,
	`min_indew_time` TIME,
	PRIMARY KEY ( `date` )
	);

The routine that uploads this information to the database currently uses an sql insert command, so the ordering of the fields is important.  

=cut

my $VERSION = '1.1'; # our version for WU updates
use Getopt::Std;
my %opts = ();
getopts('tdn', \%opts);
use strict;
use Device::Davis 1.1;
use Sys::Syslog qw(:DEFAULT setlogsock);
use POSIX;
use POSIX qw(:errno_h :fcntl_h strftime);
if($num_updates){ use LWP;};
unless($opts{n}){ use DBI;};
use Date::Calc qw(Add_Delta_Days);
use Date::Manip qw(ParseDate UnixDate ParseDateString);
my $fd = "";  # File descriptor variable
my $count = 0;  #counter variables
my $bytes = "";
my @data = ();  # Array for responses from weather station
my $bar = "";
my $min_bar = "32.00";
my $min_bar_time = "";
my $max_bar = "28.00";
my $max_bar_time = "";
my $outtemp = "";
my $intemp = "";
my $min_outtemp = "200";
my $min_outtemp_time = "";
my $max_outtemp = "-100";
my $max_outtemp_time = "";
my $min_intemp = "200";
my $min_intemp_time = "";
my $max_intemp = "-100";
my $max_intemp_time = "";
my $windsp = "";
my $max_windsp = "-1";
my $max_windsp_time = "";
my $avgwindsp = "";
my $winddir = "";
my $max_winddir = "";
my @winddirs = ();  
my $dir = 0;
my $sumdir = 0;
my $avgwinddir = 0;
my $compdir = "";
my $outhum = "";
my $min_outhum = "101";
my $min_outhum_time = "";
my $max_outhum = "-1";
my $max_outhum_time = "";
my $inhum = "";
my $min_inhum = "101";
my $min_inhum_time = "";
my $max_inhum = "-1";
my $max_inhum_time = "";
my $rate = 0;
my $max_rate = "-1";
my $max_rate_time = "";
my $dayrain = "";
my $monrain = "";
my $yearrain = "";
my $sunrise = "";
my $sunset = "";
my @digits = ();  # Array for making sunrise and sunset look pretty
my $dewpt = "";
my $max_dewpt = "-100";
my $max_dewpt_time = "";
my $min_dewpt = "100";
my $min_dewpt_time = "";
my $indewpt = "";
my $max_indewpt = "-100";
my $max_indewpt_time = "";
my $min_indewpt = "100";
my $min_indewpt_time = "";
my $tempc = ""; # Variable for C temperature for formulas that use it
my $ews = "";   # Variable used in dewpoint calc
my $num1 = "";  # Variable used in dewpoint calc
my $num2 = "";  # Variable used in dewpoint calc
my $chill = "";
my $min_chill = "200";
my $min_chill_time = "";
my $hi = "";  # Heat Index
my $max_hi = "-100";
my $max_hi_time = "";
my $new_extreme = 0;
my $pid = ""; # Variable for forking to become daemon
my $time_to_die = 0; # Flag for graceful exit
my $hour = ""; # Variables for time based functions
my $minute = 0;
my $sec = "";
my $prev_minute = -1;
my $update_done = 0;  # flag
my %update_minute = ();
my $index = 0;
my $dateutc = "";  # Current date and time UTC
my $ua = new LWP::UserAgent; # For Weatherunderground
my $wu_gust = 0; # Max gust between wu reports
my $gust = 0; # Max gust between daily obs writes
my $db_update_done = 0; # flag
my $new_day = 0;
my $date = "";
my $line = "";
my $dbh = ""; # Database handle
my $sth = ""; # Database statement handle
my $month = "";
my $day = "";
my $year = "";
my $datestr = "";
my $crc = 0;
my @rainfall = ();
my $rainamt = 0;
my $newrain = 0;
my $prev_dayrain = 0;
my $msbyte = "";
my $lsbyte = "";
my $clock_set = 0;
my $trans_bat = 0;
my $con_bat = 0;
my $con_bat_low = 0; # flag for low battery warning
my $con_bat_min = 4.5;
# Open the tty
unless(-r "$tty"){die "You do not have read permission for $tty\n";};
unless(-w "$tty"){die "You do not have write permission for $tty\n";};
$fd = station_open($tty) or die "Cannot open $tty $!\n";

=pod

B<Baud Rates>

The factory default baud rate for the Vantage Pro weather station is 19200.  If you need to use a different setting, you may change it in the Davis module source file, Davis.xs.

=cut

if($opts{t}){
$SIG{ALRM} = \&time_out; # set up timeout trap on serial port IO
test();
exit;
};
# Become a daemon
$pid = fork();
if($pid){ exit; };
if(!defined $pid){ die "Couldn't fork -- $!\n";};
# Disassociate with our controlling terminal and stop being part of our
# previous process group.
POSIX::setsid() or die "Can't start new daemon session:  $!\n";
# If we made it this far, we are now a daemon process
# We should do all our reporting via syslog from now on.
setlogsock("unix");  # talk to our local running syslogd
# Connect up with syslogd, log $name as our process name, along with our pid
openlog("$name", "pid", "$facility");
# Record our pid in a file for ease in signaling.
open(PIDFILE, ">$piddir/$name.pid") or syslog("err", "Exiting can't write pid file $piddir/$name.pid %m") and die;
print PIDFILE $$;
close(PIDFILE);
syslog("info", "$name started.");
while($minute < 60){ # initialize a hash with 60 elemets for WU updates
        $update_minute{$minute} = 0; # flag set to off for each minute
        $minute++;
};
if($num_updates){ # if we should be updating weather underground
	$index = 0;
# Sanity check on variable setting
	if($num_updates > 30 || $num_updates < 0){ $num_updates = 12; };
	until($index == $num_updates){
# Identify which minute(s) during the hour to update
		$minute = (60/$num_updates)*$index;
		$minute = sprintf("%.0f", "$minute");
# Set a flag for that minute in the hash
		$update_minute{$minute} = 1;
		if($opts{d}){syslog("debug", "Weatherunderground update at $minute past the hour");};
		$index++;
	};
}else{
	if($opts{d}){syslog("debug", "No weatherunderground updates");};
};
initialize();
until($time_to_die == 1){ # Main loop of the daemon
        $sec = (localtime)[0];
        $minute = (localtime)[1];
        $hour = (localtime)[2];
	if($num_updates){ # if we should be updating weather underground
		if($update_minute{$minute}){ # test the flag for this minute
			update_wu() unless $update_done; 
			$wu_gust = 0;  # reset variable for gust between reports
			$update_done = 1;  # set flag to indicate we're done
		}else{
			$update_done = 0; # reset flag for next update
		};
	};
# update the db unless the option was given to disable it.  The flag will 
# never bet set to 0 if this option is present. Also do new day init.
	if($minute == 0 && $hour == 0 && $db_update_done == 0){ 
		update_db(); 
		$db_update_done = 1;
		};
	if($minute == 1 && $hour == 0){ $db_update_done = 0 unless $opts{n};};
	if($minute == 0 && $hour == 0 && $new_day == 0){ start_day();};
	if($minute == 1 && $hour == 0){ $new_day = 0; };
	if($minute == 0 && $hour == 12 && $clock_set == 0){ set_clock();};
	if($minute == 1 && $hour == 12){ $clock_set = 0;};
	get_data(); # The data collection subroutine
};
close(DAILY);
syslog("info", "$name exiting.");
closelog();
POSIX::close($fd);
exit;
sub initialize{
chdir($directory);
if($opts{n}){ $db_update_done = 1;}; # disable db updates if option is set
# Load previous extreme values that were stored if any
$date = strftime "%m.%d.%Y", localtime time;
if(-e "$directory/extremes.$date"){
	if($opts{d}){syslog("debug", "Initializing variables with values from $directory/extremes.$date");};
        open(EXTREMES, "<$directory/extremes.$date");
        $line = <EXTREMES>;
        chomp($line);
        @data = split /,/, $line;
        $min_bar = $data[0];
        $min_bar_time = $data[1];
        $max_bar = $data[2];
        $max_bar_time = $data[3];
        $min_outtemp = $data[4];
        $min_outtemp_time = $data[5];
        $max_outtemp = $data[6];
        $max_outtemp_time = $data[7];
        $max_windsp = $data[8];
        $max_winddir = $data[9];
        $max_windsp_time = $data[10];
        $min_outhum = $data[11];
        $min_outhum_time = $data[12];
        $max_outhum = $data[13];
        $max_outhum_time = $data[14];
        $min_dewpt = $data[15];
        $min_dewpt_time = $data[16];
        $max_dewpt = $data[17];
        $max_dewpt_time = $data[18];
        $min_chill = $data[19];
        $min_chill_time = $data[20];
        $max_hi = $data[21];
        $max_hi_time = $data[22];
        $max_rate = $data[23];
        $max_rate_time = $data[24];
	$prev_dayrain = $data[25];
	$yearrain = $data[26];
        $min_intemp = $data[27];
        $min_intemp_time = $data[28];
        $max_intemp = $data[29];
        $max_intemp_time = $data[30];
        $min_inhum = $data[31];
        $min_inhum_time = $data[32];
        $max_inhum = $data[33];
        $max_inhum_time = $data[34];
        $min_indewpt = $data[35];
        $min_indewpt_time = $data[36];
        $max_indewpt = $data[37];
        $max_indewpt_time = $data[38];
        close(EXTREMES);
}; # end if
open(DAILY, ">>$directory/dailyobs.$date") or syslog("notice", "Cannot write to daily obervations file $directory/dailyobs.$date %m");
# Trap signals and handle them gracefully
$SIG{INT} = \&sigterm;
$SIG{TERM} = \&sigterm;
$SIG{HUP} = \&initialize;
$SIG{CHLD} = 'IGNORE';
$SIG{ALRM} = \&time_out; # set up timeout trap on serial port IO
$ua->agent("$name/$VERSION" . $ua->agent);  #for W.U.
}; # end initialize

=pod

B<Signals>

B<INT> or B<TERM> will cause the daemon to exit gracefully.

B<HUP> Will cause the daemon to re-read the values from the extremes file.  This is helpful in case something gets wacky and you need to correct the value of the variable while the daemon is running.

=cut

sub time_out{
# Cause subroutine to die in response to alarm, which allows jumping back into
# script at line following close of eval block.
if($opts{d}){syslog("debug", "Received SIGALM");};
die "Time out reading serial port.";
};
sub sigterm{   # Set a flag to gracefully exit
$time_to_die = 1;
};
sub test{
print "Weather station should be connected to $tty\n";
$count = 0;
wake_up();
print "Sending TEST\n";
$bytes = put_string($fd, "TEST\n");
if($opts{d}){print "Wrote $bytes bytes to $tty\n"; };
$data[0] = get_char($fd); # \n character
$data[1] = get_char($fd); # \r character
$data[2] = get_char($fd);
$data[3] = get_char($fd);
$data[4] = get_char($fd);
$data[5] = get_char($fd);
$data[6] = get_char($fd); # \n character
$data[7] = get_char($fd); # \r character
if($opts{d}){print "Received @data from $tty\n"; };
my $response = pack("C*", $data[2], $data[3], $data[4], $data[5]);
print "Received $response.  That is a ";
if($response eq 'TEST'){
	print "correct response.\n";
}else{
	print "unexpected or incorrect response.\n";
};
};
sub wake_up{
POSIX::tcflush($fd, TCIOFLUSH);
if($count > 3){ # Connection error condition
	if(-e "$piddir/$name.pid"){  # if we are a daemon
		syslog("err", "Connection error.  Waiting 30 seconds for retry.");
		sleep(30);
		$count = 0; # reset error condition for next series of tries
		wake_up();
	}else{  # we are not a daemon
		die "Connection error.\n";
	};
};
$bytes = put_string($fd, "\n");
if($opts{d}){
	if(-e "$piddir/$name.pid"){  # if we are a daemon
		syslog("debug", "Wrote $bytes bytes to $tty");
	}else{
		print "Wake up wrote $bytes bytes to $tty\n";
	};
};
@data = ();
eval{
alarm(1);  # 1 second timeout
$data[0] = get_char($fd);
$data[1] = get_char($fd);
alarm(0);  # reset timeout
}; # end eval
if($@ =~ /Time out reading serial port/){
	$count++;
	wake_up();
};
if($opts{d}){
	if(-e "$piddir/$name.pid"){  # if we are a daemon
		syslog("debug", "Wake up returned @data"); 
	}else{
		print "Wake up returned @data\n";
	};
};
if($data[0] == 10 && $data[1] == 13){ # Test for valid responses
	POSIX::tcflush($fd, TCIOFLUSH);
	return;
}else{ # Response was not what was expected so try again
	$count++;
	wake_up();
};
};
sub update_wu{  # Update weatherunderground
# This subroutine forks it own process after grabbing the current values of the
# variables expected to change.  This boosts overall performance of the daemon
my $avgwinddir = $avgwinddir;
my $avgwindsp = $avgwindsp;
my $wu_gust = $wu_gust;
my $outhum = $outhum;
my $outtemp = $outtemp;
my $rate = $rate;
my $dayrain = $dayrain;
my $bar = $bar;
my $dewpt = $dewpt;
my $pid = fork();
if($pid){ return; };
if(!defined $pid){ syslog("notice", "Couldn't fork update_wu routine %m");};
my $dateutc = strftime "%Y-%m-%d %H:%M:%S", gmtime time;
my $req = new HTTP::Request GET =>"http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?action=updateraw&ID=$wu_id&PASSWORD=$wu_password&dateutc=$dateutc&winddir=$avgwinddir&windspeedmph=$avgwindsp&windgustmph=$wu_gust&humidity=$outhum&tempf=$outtemp&rainin=$rate&dailyrainin=$dayrain&baromin=$bar&dewptf=$dewpt&softwaretype=$name";
my $res = $ua->request($req);
# Test whether we succeeded
if ($res->is_success) {
        if($opts{d}){syslog("debug", "Weatherunderground upload successful.");};
	if($pid == 0){exit;}; # exit if we are a child else return
} else {
# Get content of message returned and log it.
        my $response = $res->content;
        syslog("notice", "Weatherunderground upload failed.  $response");
	if($pid == 0){exit 1;}; # exit if we are a child else return
};
};
sub start_day{
# Close out daily observations file, and open new one for current day.
# Delete current obs file from previous day now that we are at or past midnight.
# Also delete dailyobs file from $oldobs days ago.
$datestr = UnixDate($date, "%m.%d.%Y");  #reformat date to match what is on file names.
unlink("$directory/currentobs.$datestr") or syslog("notice", "Cannot remove old current observations file $directory/currentobs.$datestr %m");
unlink("$directory/extremes.$datestr") or syslog("notice", "Cannot remove old extremes file $directory/extremes.$datestr %m");
close(DAILY);
$date = strftime "%m.%d.%Y", localtime time;
open(DAILY, ">$directory/dailyobs.$date") or syslog("notice", "Cannot create daily observations file $directory/dailyobs.$date %m");
# Remove dailyobs file from $oldobs days ago
($year, $month, $day) = Add_Delta_Days(((localtime)[5] + 1900), ((localtime)[4] + 1), (localtime)[3], -$oldobs);
$date = ParseDate("$year $month $day");
$datestr = UnixDate($date, "%m.%d.%Y");
unlink("$directory/dailyobs.$datestr") or syslog("notice", "Cannot remove old daily observations file $directory/dailyobs.$datestr %m");
# Reset date variable to today 
$date = strftime "%m.%d.%Y", localtime time;
# Reset min and max values so current values next loop will be our starting
# point
$min_bar = 32.00;
$max_bar = 28.00;
$min_outtemp = 200;
$max_outtemp = -100;
$max_windsp = -1;
$min_outhum = 101;
$max_outhum = -1;
$min_dewpt = 200;
$max_dewpt = -100;
$min_chill = 200;
$max_hi = -100;
$max_rate = -1;
$min_intemp = 200;
$max_intemp = -100;
$min_inhum = 101;
$max_inhum = -1;
$min_indewpt = 200;
$max_indewpt = -100;
# set flag 
$new_day = 1;
if($opts{d}){syslog("debug", "Initialized new day");};
};
sub set_clock{
# I had originally included this as part of the new day routine, then I discovered
# that the act of setting the time at midnight apparently interfered with the station
# console beginning a new day correctly.
# syncronize weather station time with computer's time
wake_up();
$sec = (localtime)[0];
$minute = (localtime)[1];
$hour = (localtime)[2];
$year = (localtime)[5];
$month = ((localtime)[4] + 1);
$day = (localtime)[3];
# Calculate the crc
$crc = crc_accum($crc, $sec);
$crc = crc_accum($crc, $minute);
$crc = crc_accum($crc, $hour);
$crc = crc_accum($crc, $day);
$crc = crc_accum($crc, $month);
$crc = crc_accum($crc, $year);
$msbyte = $crc >> 8;
$lsbyte = $crc << 24;
$lsbyte = $lsbyte >> 24;
$bytes = put_string($fd, "SETTIME\n");
if($opts{d}){ syslog("debug", "Wrote $bytes bytes to $tty for SETTIME"); };
@data = ();
eval{
alarm(1);  # 1 second timeout
$data[0] = get_char($fd);
alarm(0);  # reset timeout
}; # end eval
if($@ =~ /Time out reading serial port/){
        syslog("notice", "Serial port timeout waiting for ack to SETTIME command.");
};
if($data[0] == 6){
        $bytes = 0;
        $bytes += put_unsigned($fd, $sec);
        $bytes += put_unsigned($fd, $minute);
        $bytes += put_unsigned($fd, $hour);
        $bytes += put_unsigned($fd, $day);
        $bytes += put_unsigned($fd, $month);
        $bytes += put_unsigned($fd, $year);
        $bytes += put_unsigned($fd, $msbyte);
        $bytes += put_unsigned($fd, $lsbyte);
        $bytes += put_string($fd, "\n");
        if($opts{d}){ syslog("debug", "Wrote $bytes bytes to $tty for time values"); };
        @data = ();
        eval{
        alarm(1);  # 1 second timeout
        $data[0] = get_char($fd);
        alarm(0);  # reset timeout
        }; # end eval
        if($@ =~ /Time out reading serial port/){
                syslog("notice", "Serial port timeout waiting for ack to SETTIME values.");
        };
        if($data[0] == 6){
                syslog("info", "Weather station time sync'd with system time.");
        }else{
                syslog("notice", "Unable to sync weather station time with system.  Received a response of $data[0] from station.");
        };
}else{
        syslog("notice", "Unable to attempt time sync.  No ack to SETTIME command received from station.");
};
if($con_bat_low){ syslog("warn", "Console batteries are low.  Minimum voltage was $con_bat_min."); };
$con_bat_low = 0;
$clock_set = 1;  # set this flag so we only make 1 attempt per 24 hours.  
};
sub update_db{   # update mysql database
# this subroutine will fork into a child process after grabbing the values
# of the variables that are expected to change.  
my $min_outtemp = $min_outtemp;
my $min_outtemp_time = $min_outtemp_time;
my $max_outtemp = $max_outtemp;
my $max_outtemp_time = $max_outtemp_time;
my $min_dewpt = $min_dewpt;
my $max_dewpt = $max_dewpt;
my $min_chill = $min_chill;
my $max_hi = $max_hi;
my $max_windsp = $max_windsp;
my $max_winddir = $max_winddir;
my $max_windsp_time = $max_windsp_time;
my $min_chill = $min_chill;
my $min_chill_time = $min_chill_time;
my $yearrain = $yearrain;
my $max_rate = $max_rate;
my $max_rate_time = $max_rate_time;
my $max_bar = $max_bar;
my $max_bar_time = $max_bar_time;
my $min_bar = $min_bar;
my $min_bar_time = $min_bar_time;
my $max_outhum = $max_outhum;
my $max_outhum_time = $max_outhum_time;
my $min_outhum = $min_outhum;
my $min_outhum_time = $min_outhum_time;
my $max_dewpt_time = $max_dewpt_time;
my $min_dewpt_time = $min_dewpt_time;
my $max_hi_time = $max_hi_time;
my $dayrain = $dayrain;
my $min_intemp = $min_intemp;
my $min_intemp_time = $min_intemp_time;
my $max_intemp = $max_intemp;
my $max_intemp_time = $max_intemp_time;
my $min_indewpt = $min_indewpt;
my $max_indewpt = $max_indewpt;
my $max_inhum = $max_inhum;
my $max_inhum_time = $max_inhum_time;
my $min_inhum = $min_inhum;
my $min_inhum_time = $min_inhum_time;
my $max_indewpt_time = $max_indewpt_time;
my $min_indewpt_time = $min_indewpt_time;
my $pid = fork();
if($pid){ return; };
if(!defined $pid){ syslog("notice", "Couldn't fork update_db routine %m");};
# get yesterdays date and format it for the database
($year, $month, $day) = Add_Delta_Days(((localtime)[5] + 1900), ((localtime)[4] + 1), (localtime)[3], -1);
my $date = ParseDate("$year $month $day");
my $datestr = UnixDate($date, "%Y-%m-%d");
# round values to nearest integer
$min_outtemp = sprintf("%.0f", $min_outtemp);
$max_outtemp = sprintf("%.0f", $max_outtemp);
$min_dewpt = sprintf("%.0f", $min_dewpt);
$max_dewpt = sprintf("%.0f", $max_dewpt);
$min_intemp = sprintf("%.0f", $min_intemp);
$max_intemp = sprintf("%.0f", $max_intemp);
$min_indewpt = sprintf("%.0f", $min_indewpt);
$max_indewpt = sprintf("%.0f", $max_indewpt);
$min_chill = sprintf("%.0f", $min_chill);
$max_hi = sprintf("%.0f", $max_hi);
eval{
$dbh = DBI->connect("DBI:$db:$db_name","$db_user","$db_password",{
        PrintError => 0,
        RaiseError => 1});
# prepare variables for database entry
$datestr = $dbh->quote($datestr);
$max_outtemp = $dbh->quote($max_outtemp);
$max_outtemp_time = $dbh->quote($max_outtemp_time);
$min_outtemp = $dbh->quote($min_outtemp);
$min_outtemp_time = $dbh->quote($min_outtemp_time);
$max_windsp = $dbh->quote($max_windsp);
$max_winddir = $dbh->quote($max_winddir);
$max_windsp_time = $dbh->quote($max_windsp_time);
$min_chill = $dbh->quote($min_chill);
$min_chill_time = $dbh->quote($min_chill_time);
$yearrain = $dbh->quote($yearrain);
$max_rate = $dbh->quote($max_rate);
$max_rate_time = $dbh->quote($max_rate_time);
$max_bar = $dbh->quote($max_bar);
$max_bar_time = $dbh->quote($max_bar_time);
$min_bar = $dbh->quote($min_bar);
$min_bar_time = $dbh->quote($min_bar_time);
$max_outhum = $dbh->quote($max_outhum);
$max_outhum_time = $dbh->quote($max_outhum_time);
$min_outhum = $dbh->quote($min_outhum);
$min_outhum_time = $dbh->quote($min_outhum_time);
$max_dewpt = $dbh->quote($max_dewpt);
$max_dewpt_time = $dbh->quote($max_dewpt_time);
$min_dewpt = $dbh->quote($min_dewpt);
$min_dewpt_time = $dbh->quote($min_dewpt_time);
$max_hi = $dbh->quote($max_hi);
$max_hi_time = $dbh->quote($max_hi_time);
$dayrain = $dbh->quote($dayrain);
$max_intemp = $dbh->quote($max_intemp);
$max_intemp_time = $dbh->quote($max_intemp_time);
$min_intemp = $dbh->quote($min_intemp);
$min_intemp_time = $dbh->quote($min_intemp_time);
$max_inhum = $dbh->quote($max_inhum);
$max_inhum_time = $dbh->quote($max_inhum_time);
$min_inhum = $dbh->quote($min_inhum);
$min_inhum_time = $dbh->quote($min_inhum_time);
$max_indewpt = $dbh->quote($max_indewpt);
$max_indewpt_time = $dbh->quote($max_indewpt_time);
$min_indewpt = $dbh->quote($min_indewpt);
$min_indewpt_time = $dbh->quote($min_indewpt_time);
$sth = $dbh->prepare("insert into $db_table values ($datestr,$max_outtemp,$max_outtemp_time,$min_outtemp,$min_outtemp_time,$max_windsp,$max_winddir,$max_windsp_time,$min_chill,$min_chill_time,$yearrain,$max_rate,$max_rate_time,$max_bar,$max_bar_time,$min_bar,$min_bar_time,$max_outhum,$max_outhum_time,$min_outhum,$min_outhum_time,$max_dewpt,$max_dewpt_time,$min_dewpt,$min_dewpt_time,$max_hi,$max_hi_time,$dayrain,$max_intemp,$max_intemp_time,$min_intemp,$min_intemp_time,$max_inhum,$max_inhum_time,$min_inhum,$min_inhum_time,$max_indewpt,$max_indewpt_time,$min_indewpt,$min_indewpt_time)");
$sth->execute;
$sth->finish();
$dbh->disconnect;
};  #end of eval
# log error if we caught one
if($@){ 
	syslog("notice", "Database error:  $@"); 
	if($pid == 0){ exit 2;};
}else{
	if($opts{d}){syslog("debug", "Updated $db database.");};
	if($pid == 0){exit;}; # exit only if we are a child process else return
};
};
sub get_data {
$count = 0;
wake_up();
$bytes = put_string($fd, "LOOP 1\n");
if($opts{d}){syslog("debug", "Wrote $bytes bytes to $tty requesting loop packet"); };
$index = 0; # Byte counter
$crc = 0; 
eval{
alarm(2); # Set timeout timer
while($index < 100){
        $data[$index] = get_char($fd);
	if($index){$crc = crc_accum($crc, $data[$index]);};
        $data[$index] = sprintf("%02x", "$data[$index]"); # Convert to hex
        $index++;
};
alarm(0); # Cancel timeout timer
}; #  End of eval
if($@ =~ /Time out reading serial port/){
         syslog("notice", "Serial port time out.  Leaving get_data subroutine.");
         return;
};
if($opts{d}){syslog("debug", "Received LOOP packet.");};
if($crc){
	syslog("notice", "Loop packet failed crc check.");
	POSIX::tcflush($fd, TCIOFLUSH);
	return;
};
# Translate values returned by loop command and store extreme values
$bar = hex("$data[9]$data[8]");
if($bar < 26000 || $bar > 32000){ syslog("notice", "Barometer value out of range."); return; };
$bar = sprintf("%.2f", ($bar / 1000));
if($bar < $min_bar){
        $min_bar = $bar;
        $min_bar_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
if($bar > $max_bar){
        $max_bar = $bar;
        $max_bar_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
$intemp = hex("$data[11]$data[10]");
if($intemp < 320 || $intemp > 1400){ syslog("notice", "Indoor temperature out of range."); return; };
$intemp = sprintf("%.1f", ($intemp / 10));
if($intemp < $min_intemp){
	$min_intemp = $intemp;
	$min_intemp_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
if($intemp > $max_intemp){
	$max_intemp = $intemp;
	$max_intemp_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
$inhum = hex("$data[12]");
if($inhum > 100){ syslog("notice", "Indoor humidity out of range."); return; };
if($inhum < $min_inhum){
        $min_inhum = $inhum;
        $min_inhum_time = strftime "%H:%M:%S", localtime time;
        $new_extreme = 1;
};
if($inhum > $max_inhum){
        $max_inhum = $inhum;
        $max_inhum_time = strftime "%H:%M:%S", localtime time;
        $new_extreme = 1;
};
$outtemp = hex("$data[14]$data[13]");
# Thanks to Douglas Weaver for finding this bug and providing the fix.
# if $outtemp's top bit is set, then it is really negative 
# (hex() doesn't appear to sign extend).  Need to turn $outtemp to a negative
if($outtemp & 0x8000){ $outtemp -= 0x10000; };
if($outtemp < -400 || $outtemp > 1500){ syslog("notice", "Outdoor temperature out of range."); return; };
$outtemp = sprintf("%.1f", ($outtemp / 10));
if($outtemp < $min_outtemp){
        $min_outtemp = $outtemp;
        $min_outtemp_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
if($outtemp > $max_outtemp){
        $max_outtemp = $outtemp;
        $max_outtemp_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
$windsp = hex("$data[15]");
if($windsp > 150){syslog("notice", "Wind speed out of range."); return; };
if($windsp > $gust){ $gust = $windsp; };
if($windsp > $wu_gust){ $wu_gust = $windsp; };
$avgwindsp = hex("$data[16]");
$winddir = hex("$data[18]$data[17]");
# compute the average wind direction for the last 300 passes through the loop
push(@winddirs, $winddir);
$sumdir = 0;
foreach $dir (@winddirs){ $sumdir = $dir + $sumdir; };
$avgwinddir = $sumdir/($#winddirs + 1);
$avgwinddir = sprintf("%.0f", $avgwinddir);
if($#winddirs > 298){ shift(@winddirs); };
# convert degrees to 16 point compass rose directions
if($winddir < 12){$compdir = "N"};
if($winddir > 11 && $winddir < 35){$compdir = "NNE"};
if($winddir > 34 && $winddir < 57){$compdir = "NE"};
if($winddir > 56 && $winddir < 79){$compdir = "ENE"};
if($winddir > 78 && $winddir < 102){$compdir = "E"};
if($winddir > 101 && $winddir < 124){$compdir = "ESE"};
if($winddir > 123 && $winddir < 147){$compdir = "SE"};
if($winddir > 146 && $winddir < 169){$compdir = "SSE"};
if($winddir > 168 && $winddir < 192){$compdir = "S"};
if($winddir > 191 && $winddir < 214){$compdir = "SSW"};
if($winddir > 213 && $winddir < 237){$compdir = "SW"};
if($winddir > 236 && $winddir < 259){$compdir = "WSW"};
if($winddir > 258 && $winddir < 282){$compdir = "W"};
if($winddir > 281 && $winddir < 304){$compdir = "WNW"};
if($winddir > 303 && $winddir < 327){$compdir = "NW"};
if($winddir > 326 && $winddir < 349){$compdir = "NNW"};
if($winddir > 348){$compdir = "N"};
if($windsp > $max_windsp){
        $max_windsp = $windsp;
        $max_windsp_time = strftime "%H:%M:%S", localtime time;
        $max_winddir = $compdir;
	$new_extreme = 1;
};
$outhum = hex("$data[34]");
if($outhum > 100){ syslog("notice", "Outdoor humidity out of range."); return; };
if($outhum < $min_outhum){
        $min_outhum = $outhum;
        $min_outhum_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
if($outhum > $max_outhum){
        $max_outhum = $outhum;
        $max_outhum_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
$dayrain = hex("$data[52]$data[51]");
$dayrain = sprintf("%.2f", ($dayrain / 100));
# I want to calculate my own rain rate.  Folks outside of the climate of the
# US desert Southwest may wonder why, but folks who live in similar climates
# will understand.  
# total the dayrain for the last 4000 passes through the loop
# and then subtract the oldest value from this to get the amount of
# rainfall in approximately the past hour.
$newrain = $dayrain - $prev_dayrain;
$prev_dayrain = $dayrain;
if($newrain < 0){ 
	$newrain = 0; 
	if($dayrain > 0){ $newrain = $dayrain; };
};
push(@rainfall, $newrain);
$rate = 0;
foreach $rainamt (@rainfall){ $rate = $rainamt + $rate; };
if($#rainfall > 3998){ shift(@rainfall); };
if($rate > $dayrain){ $rate = $dayrain; };
$rate = sprintf("%.2f", $rate);
if($rate > $max_rate){
        $max_rate = $rate;
        $max_rate_time = strftime "%H:%M:%S", localtime time;
        $new_extreme = 1;
};
$monrain = hex("$data[54]$data[53]");
$monrain = sprintf("%.2f", ($monrain / 100));
$yearrain = hex("$data[56]$data[55]");
$yearrain = sprintf("%.2f", ($yearrain / 100));
$trans_bat = hex("$data[87]");
$con_bat = hex("$data[89]$data[88]");
$con_bat = (($con_bat * 300) / 512) / 100;
$con_bat = sprintf("%.2f", $con_bat);
if($con_bat < 3.75){ $con_bat_low = 1; };
if($con_bat < $con_bat_min){ $con_bat_min = $con_bat; };
$sunrise = hex("$data[93]$data[92]");
$sunrise = sprintf("%04d", $sunrise);
$sunset = hex("$data[95]$data[94]");
@digits = split /(\d\d)/, $sunrise;
$sunrise = "$digits[0]$digits[1]:$digits[2]$digits[3]";
@digits = split /(\d\d)/, $sunset;
$sunset = "$digits[0]$digits[1]:$digits[2]$digits[3]";
#
# Calculate Dewpoints
#
$tempc = (5/9)*($outtemp-32);
$ews = $outhum*0.01*exp((17.502*$tempc)/(240.9+$tempc));
$num1 = 240.9*(log($ews));
$num2 = 17.5-(log($ews));
$dewpt = $num1/$num2;
$dewpt = (9/5)*$dewpt+32;
$dewpt = sprintf("%.1f", $dewpt);
if($dewpt < $min_dewpt){
        $min_dewpt = $dewpt;
        $min_dewpt_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
if($dewpt > $max_dewpt){
        $max_dewpt = $dewpt;
        $max_dewpt_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
$tempc = (5/9)*($intemp-32);
$ews = $inhum*0.01*exp((17.502*$tempc)/(240.9+$tempc));
$num1 = 240.9*(log($ews));
$num2 = 17.5-(log($ews));
$indewpt = $num1/$num2;
$indewpt = (9/5)*$indewpt+32;
$indewpt = sprintf("%.1f", $indewpt);
if($indewpt < $min_indewpt){
        $min_indewpt = $indewpt;
        $min_indewpt_time = strftime "%H:%M:%S", localtime time;
        $new_extreme = 1;
};
if($indewpt > $max_indewpt){
        $max_indewpt = $indewpt;
        $max_indewpt_time = strftime "%H:%M:%S", localtime time;
        $new_extreme = 1;
};
#
# Calculate wind chill
#
$chill = 35.74+(0.6215*$outtemp)-(35.75*($avgwindsp**0.16))+(0.4275*$outtemp*($avgwindsp**0.16));
if($avgwindsp < 3 || $outtemp > 65){ $chill = $outtemp; };
if($avgwindsp > 10 && $outtemp < 72){ $chill = 35.74+(0.6215*$outtemp)-(35.75*($avgwindsp**0.16))+(0.4275*$outtemp*($avgwindsp**0.16)); };
$chill = sprintf("%.1f", $chill);
if($chill < $min_chill){
        $min_chill = $chill;
        $min_chill_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};
#
# Calculate Heat Index
#
 if($outtemp > 79 && $outhum > 39){
        $hi = -42.379+(2.04901523*$outtemp)+(10.14333127*$outhum)-(0.22475541*$outtemp*$outhum)-(0.00683783*($outtemp**2))-(0.05481717*($outhum**2))+(0.00122874*($outtemp**2)*$outhum)+(0.00085282*$outtemp*($outhum**2))-(0.000001998*($outtemp**2)*($outhum**2));
 };
if($outtemp < 79.1){ $hi = $chill; };
if($outtemp > 79 && $outhum < 40){ $hi = $chill; };
$hi = sprintf("%.1f", $hi);
if($hi > $max_hi){
        $max_hi = $hi;
        $max_hi_time = strftime "%H:%M:%S", localtime time;
	$new_extreme = 1;
};

=head2 Operational Characteristics

Whenever possible the daemon collects the data provided by the weather station.  Some values like wind chill, heat index, and dewpoint are not provided and the daemon calculates these for you.  The daemon also checks the crc values on each packet received from the weather station, and if an error is detected, a notice level message is sent to the syslog and the packet is not processed.  If an obviously out of range value is detected within the packet, a notice is sent to the syslog and the remainder of the packet is not processed.

In the case of wind direction, it takes the degree reading from the weather station and translates it into a more human friendly form that will be written into the database and the current and extremes files.  Examples:  NE instead of 45 degrees, ESE instead of 105 degrees, etc.  Also for weather underground updates and for the daily obs file, it calculates the average direction for the wind from the last 300 samples read from the station.  The gust that is reported to the weather underground is the highest wind speed observed since the previous weather underground report.  The average wind speed (which is read from the station) is used as the basis for calculating the wind chill. 

The daemon does it's own calculation for the rainfall rate even though this is provided by the weather station.  I live in the Southwestern desert areas of the US, so this type of calculation makes sense for my climate.  It isn't likely to be that big of a deal or make that much difference in an area that receives more precipitation.  The calculation that I use does NOT require 2 "dumps" of the rain sensor within 15 minutes in order to register a rate like the rate provided on the console.  It also will not jump ridiculously high if you have a shower that dumps the sensor several times in a short period and then ends 5 minutes later.

Because the database updates and the correct function of the rainfall rate calculations depend on a close syncronization between the time on the weather station and the time on the computer system, the daemon will syncronize the weather station console time with the computer system time at noon each day. 

The daemon also keeps track of the console battery voltage and will write a warning to the syslog at noon each day after it sets the clock if the battery voltage has dropped too low at any time in the previous 24 hours or since the daemon started.  

The daemon will fork a child process to post the updates to weather underground and perform the database updates.  If the fork calls fail, the daemon will still perform these functions, just in a single threaded manner.

UV, leaf moisture, soil moisture, evapotranspiration, extra temperature and humidity readings from additional sensors, solar radiation, storm rainfall, storm start date, alarms, and forecast icons displayed are ignored along with the barometer trend provided by the Rev B. firmware.

=cut

# record our current extremes in a file clobbering old file.
if($new_extreme){
	$date = strftime "%m.%d.%Y", localtime time;
	open(EXTREMES, ">$directory/extremes.$date") or syslog("notice", "Cannot write to extremes file $directory/extremes.$date %m");
	print EXTREMES "$min_bar,$min_bar_time,$max_bar,$max_bar_time,$min_outtemp,$min_outtemp_time,$max_outtemp,$max_outtemp_time,$max_windsp,$max_winddir,$max_windsp_time,$min_outhum,$min_outhum_time,$max_outhum,$max_outhum_time,$min_dewpt,$min_dewpt_time,$max_dewpt,$max_dewpt_time,$min_chill,$min_chill_time,$max_hi,$max_hi_time,$max_rate,$max_rate_time,$dayrain,$yearrain,$min_intemp,$min_intemp_time,$max_intemp,$max_intemp_time,$min_inhum,$min_inhum_time,$max_inhum,$max_inhum_time,$min_indewpt,$min_indewpt_time,$max_indewpt,$max_indewpt_time";
	close(EXTREMES);
	$new_extreme = 0;
};

=pod

The daemon will generate 3 files in the directory that you specify in the Local Configuration section.  These files are intended for use by other scripts or programs as opposed to being human readable even though they are plain text files.  This way tasks such as graphing or supplying information to a web page can be done indepedently of the daemon.  A brief description of each file's name, and contents follows.

F<extremes.mm.dd.yyyy>  This file contains the minimum and maximum values observed for a day.  The daemon always begins a new day at midnight.  This file always contains a single line of comma seperated values in this order:  minimum barometer and the time of observation, maximum barometer and the time of observation, minimum outdoor temp and time, maximum outdoor temp and time, maximum wind speed, direction and time, minimum outdoor humidity and time, maximum outdoor humidity and time, minimum outdoor dewpoint and time, maximum outdoor dewpoint and time, minimum wind chill and time, maximum heat index and time, maximum rainfall rate and time, and the daily rainfall and year to date rainfall totals, minimum indoor temp and time, maximum indoor temp and time, minumum indoor humidity and time, maximum indoor humidity and time, minimum indoor dewpoint and time, and maximum indoor dewpoint and time.

=cut

# record current observations in a file clobbering old file.  Append to daily file.
open(CURRENT, ">$directory/currentobs.$date") or syslog("notice", "Cannot write to current obervations file $directory/currentobs.$date %m");
print CURRENT "$hour,$minute,$sec,$bar,$outtemp,$avgwindsp,$windsp,$compdir,$outhum,$dewpt,$chill,$hi,$rate,$dayrain,$monrain,$yearrain,$sunrise,$sunset,$intemp,$inhum,$indewpt";
close(CURRENT);

=pod

F<currentobs.mm.dd.yyyy>  This file contains the following values from the most recent sample read from the station.  The values are on a single line and comma seperated in the order listed:  Hour of the day, minute, and second, barometer reading, outdoor temp, average wind speed, current wind speed, direction, outdoor humidity, outdoor dewpoint, wind chill, heat index, rainfall rate, daily rain, month to date rain, year to date rain, sunrise, sunset, indoor temp, indoor humidity, indoor dewpoint.

=cut

if($prev_minute != $minute){
        print DAILY "$hour,$minute,$bar,$outtemp,$avgwindsp,$gust,$avgwinddir,$outhum,$dewpt,$chill,$hi,$intemp,$inhum,$indewpt\n";
        select(DAILY);
        $| = 1;  # force the buffer to flush
        $prev_minute = $minute;  # only write once per minute
        $gust = 0; # reset counters
};
}; # end sub

=pod

F<dailyobs.mm.dd.yyyy>  This file is appended to once per minute and contains the then current observations for that minute on a single line.  The values are comma seperated and in the following order:  hour, minute, barometer, outdoor temp, average wind speed, wind gust for the previous 60 seconds, the average direction, outdoor humidity, outdoor dewpoint, wind chill, heat index, indoor temp, indoor humidity, and indoor dewpoint.

=head1 EXAMPLES

Here are some examples of the contents of the files output by the daemon:

B<extremes.mm.dd.yy>

29.61,00:00:06,29.89,12:15:35,47.9,07:25:58,68.1,13:43:07,21,ESE,11:06:36,43,14:13:55,95,07:55:36,44.3,14:14:38,53.8,10:39:08,47.9,07:25:58,68.1,13:43:07,0.08,00:04:42,0.02,6.36,69.7,06:35:07,75.7,11:35:31,40,13:36:16,52,07:03:43,49.0,11:35:31,51.3,07:03:43

B<currentobs.mm.dd.yyyy>

14,42,4,29.86,67.9,9,7,ESE,44,45.2,67.9,67.9,0.00,0.02,1.14,6.36,07:07,18:35,75.7,39,49.0

B<dailyobs.mm.dd.yyyy>

14,33,29.86,67.9,10,12,128,43,44.6,67.9,67.9,75.7,39,49.0
14,34,29.86,67.9,10,12,130,43,44.6,67.9,67.9,75.7,39,49.0
14,35,29.86,68.1,10,11,131,44,45.4,68.1,68.1,75.7,39,49.0
14,36,29.86,68.3,10,12,132,43,45.0,68.3,68.3,75.7,39,49.0
14,37,29.86,68.1,10,17,132,43,44.8,68.1,68.1,75.7,39,49.0
14,38,29.86,67.9,10,14,133,44,45.2,67.9,67.9,75.7,39,49.0
14,39,29.86,67.9,10,14,134,43,44.6,67.9,67.9,75.7,39,49.0
14,40,29.86,67.8,10,13,132,45,45.7,67.8,67.8,75.7,39,49.0
14,41,29.86,67.8,10,12,129,44,45.1,67.8,67.8,75.7,39,49.0
14,42,29.86,67.9,9,14,125,44,45.2,67.9,67.9,75.7,39,49.0

=head1 BUGS AND LIMITATIONS

The weather underground updates are currently limited to no more than once every 2 minutes.  All of the values for sensors that are not on my station as well as all of the alarms are currently ignored.  The barometer trend provided by Rev B of the firmware is also ignored.  Feel free to send me your requests for what you would like to see added or changed.  My e-mail address is stsander@earthlink.net.  Even if you don't have any specific requests, I'd love to hear from you just to let me know that you have decided to use this daemon.  Like most any other peice of software there is probably an "undocumented feature" that I have overlooked.  If you find one of those, be sure to let me know about it.

=head1 CHANGES

B<1.1 - >	Added monitoring for the console battery level.
	Added monitoring of indoor temperature, humidity, and dewpoint.
	Added sanity checking for values that are obviously out of range.

Now for some PAUSE stuff.

=head1 README

This daemon is written to communicate with a Davis Vantage Pro Weather Staion.

It will store the daily extreme values and their time of occurance in a database and update the weather underground at a user configurable interval between once per hour and every 2 minutes, or you can disable this feature altogether.   

It will generate 3 comma seperated text files as it runs.  These files are intended for use by other scripts.

Example of installation:

	cp vanprod-$VERSION /usr/local/sbin/vanprod
	pod2man /usr/local/sbin/vanprod /usr/local/man/man1/vanprod.1

Create a script to start the daemon for you at boot, and you should be all set.

B<UPGRADING FROM PREVIOUS VERSIONS>

B<To upgrade from a 0.x or 1.0 version:>  Beginning with version 1.1, you will need to modify the database table if you have been using that feature in any of the 0.x or 1.0 versions.  You will need to add columns for the minimum and maximum indoor temperature, humidity, and dewpoint as well as the time of occurance for each.  See the section on the database setup for additional details.  

=head1 PREREQUISITES

The daemon requires the following at a minimum in order to run:

Device::Davis
Getopt::Std
Sys::Syslog
POSIX
Date::Calc
Date::Manip
LWP

=head1 COREQUISITES

C<DBI> for database updates.

=pod OSNAMES

any Unix or Linux variant

=pod SCRIPT CATEGORIES

Networking
Web

=head1 SEE ALSO

perl(1) perlfunc(1) Device::Davis(3) POSIX(3) LWP(3) DBI(3) Date::Manip(1) Date::Calc(3) Getopt::Std(3) Sys::Syslog(3) syslog.conf(5) http://www.mysql.com/doc/

=head1 COPYRIGHT AND LICENSE

Copyright 2004 by Stan Sander.  stsander@earthlink.net  You are welcome to send me any requests for new features or enhancements.

Vanprod is free software.  You may redistribute
it and/or modify it under the same terms as Perl itself.

=cut
