#!/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 = "MY_STATION"; # 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
my %tid = (  # Set up your transmitter ID's for wireless stations
  0 => "ISS",  # Integrated Sensor Suite - Factory default
  1 => "ID 2",
  2 => "ID 3",
  3 => "ID 4",
  4 => "ID 5",
  5 => "ID 6",
  6 => "ID 7",
  7 => "ID 8"
);
my $alarm_interval = 300;
##########################################################################
#                          END LOCAL CONFIG OPTIONS                      #
##########################################################################

=head1 NAME

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

=head1 SYNOPSIS

vanprod [ -dnt ]

=head1 DESCRIPTION


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 initial 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.

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.


=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.  

B<my %tid >  This is where you can set up some meaningful strings for the up to 8 transmitters allowed with the wireless Vantage Pro.  

B<my $alarm_interval = 300;>  This controls how often you want messages written to syslog in the event of a console alarm.  This number represents the number of passes through the data collection loop.  On most systems this will be appoximately 1.1 seconds of real time.  Your results will depend on system load, available memory, processor speed, etc.  There is a sleep call in the main loop which prevents more than one pass per second. 

=cut

my $VERSION = '1.2'; # 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;
my $bartrend = "";
my $trend = "";
my @bar_vals = ();
my $old_bar = 0;
my $bar_diff = 0;
my $forecasticon = "";
my $forecastrule = "";
my $tid_0_low = 0;
my $tid_1_low = 0;
my $tid_2_low = 0;
my $tid_3_low = 0;
my $tid_4_low = 0;
my $tid_5_low = 0;
my $tid_6_low = 0;
my $tid_7_low = 0;
my $alarm0 = 0;
my $alarm1 = 0;
my $alarm2 = 0;
my $bar_fall_alarm = 0;
my $bar_rise_alarm = 0;
my $low_intemp_alarm = 0;
my $hi_intemp_alarm = 0;
my $low_inhum_alarm = 0;
my $hi_inhum_alarm = 0;
my $con_rate_alarm = 0;
my $m15_rain_alarm = 0;
my $h24_rain_alarm = 0;
my $storm_rain_alarm = 0;
my $low_outtemp_alarm = 0;
my $hi_outtemp_alarm = 0;
my $windsp_alarm = 0;
my $avgwindsp_alarm = 0;
my $low_outdew_alarm = 0;
my $hi_outdew_alarm = 0;
my $heat_alarm = 0;
my $chill_alarm = 0;
my $stormrain = 0;
my $bit = 0;
my $stormstart = 0;
my $stormyear = 0;
my $stormmonth = 0;
my $stormday = 0;
# 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
	sleep(1);  # No more than once per second, please
};
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.");
};
$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
$bartrend = hex("$data[4]");
$trend = "NA";
if($bartrend == 196){$trend = "FR";}; # Falling rapidly
if($bartrend == 236){$trend = "FS";}; # Falling slowly
if($bartrend == 0){$trend = "ST";}; # Steady
if($bartrend == 20){$trend = "RS";}; # Rising slowly
if($bartrend == 60){$trend = "RR";}; # Rising rapidly
$bar = hex("$data[9]$data[8]");
if($bar < 26000 || $bar > 32000){
	syslog("notice", "Barometer value out of range."); 
}else{
  $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;
  };
  if($bartrend == 80){
# This is Rev. A firmware which doesn't provide this value,
# So, we'll calculate it.
	push(@bar_vals, $bar); # Place current value on top of stack
	$old_bar = $bar_vals[0]; # Read bottom value from stack
	$bar_diff = $bar - $old_bar;
	if($#bar_vals > 11998){ shift(@bar_vals); };
	if($bar_diff < -.05){ $trend = "FR"; };
	if($bar_diff >= -.05 && $bar_diff < 0){ $trend = "FS"; };
	if($bar_diff == 0){ $trend = "ST"; };
	if($bar_diff > 0 && $bar_diff <= .05){ $trend = "RS"; };
	if($bar_diff > .05){ $trend = "RR"; };
  };
};
$intemp = hex("$data[11]$data[10]");
$intemp = sprintf("%.1f", ($intemp / 10));
if($intemp < 32 || $intemp > 140){
	syslog("notice", "Indoor temperature out of range.");
}else{
  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.");
}else{
  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."); 
}else{
  $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."); 
}else{
  if($windsp > $gust){ $gust = $windsp; };
  if($windsp > $wu_gust){ $wu_gust = $windsp; };
};
$avgwindsp = hex("$data[16]") unless(hex("$data[16]") > 150);
$winddir = hex("$data[18]$data[17]");
if($winddir > 360){

  syslog("notice", "Wind direction out of range.");
}else{
# 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 && $windsp < 151){
        $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.");
}else{
  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;
  };
};
$stormrain = hex("$data[48]$data[47]");
$stormrain = sprintf("%.2f", ($stormrain / 100));
$stormmonth = 0;
$stormday = 0;
$stormyear = 2000;
if((hex("$data[50]")) < 255 && (hex("$data[49]")) < 255){
  $bit = hex("$data[50]");
  if($bit & 128){ $stormmonth = $stormmonth + 8; };
  if($bit & 64){ $stormmonth = $stormmonth + 4; };
  if($bit & 32){ $stormmonth = $stormmonth + 2; };
  if($bit & 16){ $stormmonth = $stormmonth + 1; };
  if($bit & 8){ $stormday = $stormday + 16; };
  if($bit & 4){ $stormday = $stormday + 8; };
  if($bit & 2){ $stormday = $stormday + 4; };
  if($bit & 1){ $stormday = $stormday + 2; };
  $bit = hex("$data[49]");
  if($bit & 128){ $stormday = $stormday + 1; };
  $stormyear += $bit & 64;
  $stormyear += $bit & 32;
  $stormyear += $bit & 16;
  $stormyear += $bit & 8;
  $stormyear += $bit & 4;
  $stormyear += $bit & 2;
  $stormyear += $bit & 1;
  $stormstart = "$stormmonth/$stormday/$stormyear";
}else{
  $stormstart = "00/00/00";
};
$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) unless($newrain > .1);
$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));
# For the alarm bits, I increment a flag and take action (initally just writing to syslog)
# based on the value of the flag.  This way action is not taken every time through the
# loop but rather at an interval defined in the console.  Hopefully this approach allows sufficient
# flexability for future enhancements (i.e. doing something besides putting a message
# in the syslog and defining "silent" alarms)
$alarm0 = hex("$data[71]");
$alarm1 = hex("$data[72]");
$alarm2 = hex("$data[73]");
if($alarm0 & 1){$bar_fall_alarm++; }else{$bar_fall_alarm = 0; };
if($alarm0 & 2){$bar_rise_alarm++; }else{$bar_rise_alarm = 0; };
if($alarm0 & 4){$low_intemp_alarm++; }else{$low_intemp_alarm = 0; };
if($alarm0 & 8){$hi_intemp_alarm++; }else{$hi_intemp_alarm = 0; };
if($alarm0 & 16){$low_inhum_alarm++;}else{$low_inhum_alarm = 0; };
if($alarm0 & 32){$hi_inhum_alarm++;}else{$hi_inhum_alarm = 0; };
# bit 6 is the alarm clock (aka time alarm) on the console I'm ignoring it here
# bit 7 is not used
if($alarm1 & 1){$con_rate_alarm++; }else{$con_rate_alarm = 0; };
if($alarm1 & 2){$m15_rain_alarm++; }else{$m15_rain_alarm = 0; };
if($alarm1 & 4){$h24_rain_alarm++; }else{$h24_rain_alarm = 0; };
if($alarm1 & 8){$storm_rain_alarm++; }else{$storm_rain_alarm = 0; };
# bit 4 is the daily ET alarm I'm ignoring it
# bits 5 - 7 are not used
if($alarm2 & 1){$low_outtemp_alarm++; }else{$low_outtemp_alarm = 0; };
if($alarm2 & 2){$hi_outtemp_alarm++; }else{$hi_outtemp_alarm = 0; };
if($alarm2 & 4){$windsp_alarm++; }else{$windsp_alarm = 0; };
if($alarm2 & 8){$avgwindsp_alarm++; }else{$avgwindsp_alarm = 0; };
if($alarm2 & 16){$low_outdew_alarm++; }else{$low_outdew_alarm = 0; };
if($alarm2 & 32){$hi_outdew_alarm++; }else{$hi_outdew_alarm = 0; };
if($alarm2 & 64){$heat_alarm++; }else{$heat_alarm = 0; };
if($alarm2 & 128){$chill_alarm++; }else{$chill_alarm = 0; };
if($bar_fall_alarm == 1){syslog("warning", "Falling barometer alarm on Vantage Pro console."); };
if($bar_fall_alarm > 0 && ($bar_fall_alarm % $alarm_interval) == 0){syslog("warning", "Falling barometer alarm on Vantage Pro console."); };
if($bar_rise_alarm == 1){syslog("warning", "Rising barometer alarm on Vantage Pro console."); };
if($bar_rise_alarm > 0 && ($bar_rise_alarm % $alarm_interval) == 0){syslog("warning", "Rising barometer alarm on Vantage Pro console."); };
if($low_intemp_alarm == 1){syslog("warning", "Low indoor temperature alarm on Vantage Pro console."); };
if($low_intemp_alarm > 0 && ($low_intemp_alarm % $alarm_interval) == 0){syslog("warning", "Low indoor temperature alarm on Vantage Pro console."); };
if($hi_intemp_alarm == 1){syslog("warning", "High indoor temperature alarm on Vantage Pro console."); };
if($hi_intemp_alarm > 0 && ($hi_intemp_alarm % $alarm_interval) == 0){syslog("warning", "High indoor temperature alarm on Vantage Pro console."); };
if($low_inhum_alarm == 1){syslog("warning", "Low indoor humidity alarm on Vantage Pro console."); };
if($low_inhum_alarm > 0 && ($low_inhum_alarm % $alarm_interval) == 0){syslog("warning", "Low indoor humidity alarm on Vantage Pro console."); };
if($hi_inhum_alarm == 1){syslog("warning", "High indoor humudity alarm on Vantage Pro console."); };
if($hi_inhum_alarm > 0 && ($hi_inhum_alarm % $alarm_interval) == 0){syslog("warning", "High indoor humudity alarm on Vantage Pro console."); };
if($con_rate_alarm == 1){syslog("warning", "High rain rate alarm on Vantage Pro console."); };
if($con_rate_alarm > 0 && ($con_rate_alarm % $alarm_interval) == 0){syslog("warning", "High rain rate alarm on Vantage Pro console."); };
if($m15_rain_alarm == 1){syslog("warning", "15 minute rain (flash flood) alarm on Vantage Pro console."); };
if($m15_rain_alarm > 0 && ($m15_rain_alarm % $alarm_interval) == 0){syslog("warning", "15 minute rain (flash flood) alarm on Vantage Pro console."); };
if($h24_rain_alarm == 1){syslog("warning", "24 hour rain total alarm on Vantage Pro console."); };
if($h24_rain_alarm > 0 && ($h24_rain_alarm % $alarm_interval) == 0){syslog("warning", "24 hour rain total alarm on Vantage Pro console."); };
if($storm_rain_alarm == 1){syslog("warning", "Storm rain total alarm on Vantage Pro console."); };
if($storm_rain_alarm > 0 && ($storm_rain_alarm % $alarm_interval) == 0){syslog("warning", "Storm rain total alarm on Vantage Pro console."); };
if($low_outtemp_alarm == 1){syslog("warning", "Low outdoor temperature alarm on Vantage Pro console."); };
if($low_outtemp_alarm > 0 && ($low_outtemp_alarm % $alarm_interval) == 0){syslog("warning", "Low outdoor temperature alarm on Vantage Pro console."); };
if($hi_outtemp_alarm == 1){syslog("warning", "High outdoor temperature alarm on Vantage Pro console."); };
if($hi_outtemp_alarm > 0 && ($hi_outtemp_alarm % $alarm_interval) == 0){syslog("warning", "High outdoor temperature alarm on Vantage Pro console."); };
if($windsp_alarm == 1){syslog("warning", "High wind gust alarm on Vantage Pro console."); };
if($windsp_alarm > 0 && ($windsp_alarm % $alarm_interval) == 0){syslog("warning", "High wind gust alarm on Vantage Pro console."); };
if($avgwindsp_alarm == 1){syslog("warning", "High wind speed alarm on Vantage Pro console."); };
if($avgwindsp_alarm > 0 && ($avgwindsp_alarm % $alarm_interval) == 0){syslog("warning", "High wind speed alarm on Vantage Pro console."); };
if($low_outdew_alarm == 1){syslog("warning", "Low outdoor dewpoint alarm on Vantage Pro console."); };
if($low_outdew_alarm > 0 && ($low_outdew_alarm % $alarm_interval) == 0){syslog("warning", "Low outdoor dewpoint alarm on Vantage Pro console."); };
if($hi_outdew_alarm == 1){syslog("warning", "High outdoor dewpoint alarm on Vantage Pro console."); };
if($hi_outdew_alarm > 0 && ($hi_outdew_alarm % $alarm_interval) == 0){syslog("warning", "High outdoor dewpoint alarm on Vantage Pro console."); };
if($heat_alarm == 1){syslog("warning", "High heat index alarm on Vantage Pro console."); };
if($heat_alarm > 0 && ($heat_alarm % $alarm_interval) == 0){syslog("warning", "High heat index alarm on Vantage Pro console."); };
if($chill_alarm == 1){syslog("warning", "Low wind chill alarm on Vantage Pro console."); };
if($chill_alarm > 0 && ($chill_alarm % $alarm_interval) == 0){syslog("warning", "Low wind chill alarm on Vantage Pro console."); };
$trans_bat = hex("$data[87]"); 
if($trans_bat & 1){ $tid_0_low++; }else{$tid_0_low = 0; };
if($trans_bat & 2){ $tid_1_low++; }else{$tid_1_low = 0; };
if($trans_bat & 4){ $tid_2_low++; }else{$tid_2_low = 0; };
if($trans_bat & 8){ $tid_3_low++; }else{$tid_3_low = 0; };
if($trans_bat & 16){ $tid_4_low++; }else{$tid_4_low = 0; };
if($trans_bat & 32){ $tid_5_low++; }else{$tid_5_low = 0; };
if($trans_bat & 64){ $tid_6_low++; }else{$tid_6_low = 0; };
if($trans_bat & 128){ $tid_7_low++; }else{$tid_7_low = 0; };
if($tid_0_low == 1){syslog("notice", "The $tid{0} transmitter battery is low."); };
if($tid_0_low > 0 && ($tid_0_low % $alarm_interval) == 0){syslog("notice", "The $tid{0} transmitter battery is low."); };
if($tid_1_low == 1){syslog("notice", "The $tid{1} transmitter battery is low."); };
if($tid_1_low > 0 && ($tid_1_low % $alarm_interval) == 0){syslog("notice", "The $tid{1} transmitter battery is low."); };
if($tid_2_low == 1){syslog("notice", "The $tid{2} transmitter battery is low."); };
if($tid_2_low > 0 && ($tid_2_low % $alarm_interval) == 0){syslog("notice", "The $tid{2} transmitter battery is low."); };
if($tid_3_low == 1){syslog("notice", "The $tid{3} transmitter battery is low."); };
if($tid_3_low > 0 && ($tid_3_low % $alarm_interval) == 0){syslog("notice", "The $tid{3} transmitter battery is low."); };
if($tid_4_low == 1){syslog("notice", "The $tid{4} transmitter battery is low."); };
if($tid_4_low > 0 && ($tid_4_low % $alarm_interval) == 0){syslog("notice", "The $tid{4} transmitter battery is low."); };
if($tid_5_low == 1){syslog("notice", "The $tid{5} transmitter battery is low."); };
if($tid_5_low > 0 && ($tid_5_low % $alarm_interval) == 0){syslog("notice", "The $tid{5} transmitter battery is low."); };
if($tid_6_low == 1){syslog("notice", "The $tid{6} transmitter battery is low."); };
if($tid_6_low > 0 && ($tid_6_low % $alarm_interval) == 0){syslog("notice", "The $tid{6} transmitter battery is low."); };
if($tid_7_low == 1){syslog("notice", "The $tid{7} transmitter battery is low."); };
if($tid_7_low > 0 && ($tid_7_low % $alarm_interval) == 0){syslog("notice", "The $tid{7} transmitter battery is low."); };
$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++; };
if($con_bat < $con_bat_min){ $con_bat_min = $con_bat; };
if($con_bat_low == 1){syslog("notice", "The console batteries are low.  Minimum voltage has been $con_bat_min V."); };
if($con_bat_low > 0 && ($con_bat_low % $alarm_interval) == 0){syslog("notice", "The console batteries are low.  Minimum voltage has been $con_bat_min V."); };
$forecasticon = hex("$data[90]");
$forecastrule = hex("$data[91]");
$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
#
unless($outtemp < -400 || $outtemp > 1500 || $outhum > 100){ 
  $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;
  };
};
unless($inhum > 100 || $intemp < 32 || $intemp > 140){
  $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
#
unless($outtemp < -400 || $outtemp > 1500){
  $chill = 35.74+(0.6215*$outtemp)-(35.75*($avgwindsp**0.16))+(0.4275*$outtemp*($avgwindsp**0.16));
  if($avgwindsp < 3 || $outtemp > 50){ $chill = $outtemp; };
  $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
#
unless($outtemp < -400 || $outtemp > 1500 || $outhum > 100){
  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 = $outtemp; };
  if($outtemp > 79 && $outhum < 40){ $hi = $outtemp; };
  $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 that value is not processed or used in any calculations.

In the case of the barometric trend, if you have Rev. B firmware on your console, then the trend provided by the console is used.  If you have Rev. A  firmware, then the barometric trend is not provided and the daemon calculates it for you.  The daemon will automatically detect which level of firmware is on your station.

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 and transmitter battery voltages and will write a notice to the syslog if the battery voltage drops too low.  This message will repeat at a rate controlled by the $alarm_interval variable until the condition is corrected.

The daemon now monitors what I think are the common alarms that may be set on your console.  The daemon will log a warning to your syslog when any alarms are triggered.  These messages will continue at a rate controlled by the $alarm_interval variable.  The alarms monitored are:  falling barometer, rising barometer, low indoor temperature, high indoor temperature, low indoor humidity, high indoor humidity, high rain rate, 15 minute rainfall (aka flash flood alarm), 24 hour rain total, storm total rainfall, low outdoor temperature, high outdoor temperature, wind gust, wind speed (average), low outdoor dewpoint, high outdoor dewpoint, high heat index, low wind chill.  

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, and alarms for these sensors are ignored.

=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 independently 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,$trend,$forecasticon,$forecastrule,$stormstart,$stormrain";
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, barometric trend, forecast icon, forecast rule, storm start date, storm total rainfall.

Below is a translation of the forecast rule number to the text messages displayed on the Vantage Pro console.  Only the rule number is recorded in the current observations file.  Thanks to Tim Miller for providing these and doing some work on the barometric trend and forecast rule captures.

   0 = "Mostly clear and cooler."
   1 = "Mostly clear with little temperature change."
   2 = "Mostly clear for 12 hours with little temperature change."
   3 = "Mostly clear for 12 to 24 hours and cooler."
   4 = "Mostly clear with little temperature change."
   5 = "Partly cloudy and cooler."
   6 = "Partly cloudy with little temperature change."
   7 = "Partly cloudy with little temperature change."
   8 = "Mostly clear and warmer."
   9 = "Partly cloudy with little temperature change."
  10 = "Partly cloudy with little temperature change."
  11 = "Mostly clear with little temperature change."
  12 = "Increasing clouds and warmer. Precipitation possible within 24 to 48 hours."
  13 = "Partly cloudy with little temperature change."
  14 = "Mostly clear with little temperature change."
  15 = "Increasing clouds with little temperature change. Precipitation possible within 24 hours."
  16 = "Mostly clear with little temperature change."
  17 = "Partly cloudy with little temperature change."
  18 = "Mostly clear with little temperature change."
  19 = "Increasing clouds with little temperature change. Precipitation possible within 12 hours."
  20 = "Mostly clear with little temperature change."
  21 = "Partly cloudy with little temperature change."
  22 = "Mostly clear with little temperature change."
  23 = "Increasing clouds and warmer. Precipitation possible within 24 hours."
  24 = "Mostly clear and warmer. Increasing winds."
  25 = "Partly cloudy with little temperature change."
  26 = "Mostly clear with little temperature change."
  27 = "Increasing clouds and warmer. Precipitation possible within 12 hours. Increasing winds."
  28 = "Mostly clear and warmer. Increasing winds."
  29 = "Increasing clouds and warmer."
  30 = "Partly cloudy with little temperature change."
  31 = "Mostly clear with little temperature change."
  32 = "Increasing clouds and warmer. Precipitation possible within 12 hours. Increasing winds."
  33 = "Mostly clear and warmer. Increasing winds."
  34 = "Increasing clouds and warmer."
  35 = "Partly cloudy with little temperature change."
  36 = "Mostly clear with little temperature change."
  37 = "Increasing clouds and warmer. Precipitation possible within 12 hours. Increasing winds."
  38 = "Partly cloudy with little temperature change."
  39 = "Mostly clear with little temperature change."
  40 = "Mostly clear and warmer. Precipitation possible within 48 hours."
  41 = "Mostly clear and warmer."
  42 = "Partly cloudy with little temperature change."
  43 = "Mostly clear with little temperature change."
  44 = "Increasing clouds with little temperature change. Precipitation possible within 24 to 48 hours."
  45 = "Increasing clouds with little temperature change."
  46 = "Partly cloudy with little temperature change."
  47 = "Mostly clear with little temperature change."
  48 = "Increasing clouds and warmer. Precipitation possible within 12 to 24 hours."
  49 = "Partly cloudy with little temperature change."
  50 = "Mostly clear with little temperature change."
  51 = "Increasing clouds and warmer. Precipitation possible within 12 to 24 hours. Windy."
  52 = "Partly cloudy with little temperature change."
  53 = "Mostly clear with little temperature change."
  54 = "Increasing clouds and warmer. Precipitation possible within 12 to 24 hours. Windy."
  55 = "Partly cloudy with little temperature change."
  56 = "Mostly clear with little temperature change."
  57 = "Increasing clouds and warmer. Precipitation possible within 6 to 12 hours."
  58 = "Partly cloudy with little temperature change."
  59 = "Mostly clear with little temperature change."
  60 = "Increasing clouds and warmer. Precipitation possible within 6 to 12 hours. Windy."
  61 = "Partly cloudy with little temperature change."
  62 = "Mostly clear with little temperature change."
  63 = "Increasing clouds and warmer. Precipitation possible within 12 to 24 hours. Windy."
  64 = "Partly cloudy with little temperature change."
  65 = "Mostly clear with little temperature change."
  66 = "Increasing clouds and warmer. Precipitation possible within 12 hours."
  67 = "Partly cloudy with little temperature change."
  68 = "Mostly clear with little temperature change."
  69 = "Increasing clouds and warmer. Precipitation likley."
  70 = "Clearing and cooler. Precipitation ending within 6 hours."
  71 = "Partly cloudy with little temperature change."
  72 = "Clearing and cooler. Precipitation ending within 6 hours."
  73 = "Mostly clear with little temperature change."
  74 = "Clearing and cooler. Precipitation ending within 6 hours."
  75 = "Partly cloudy and cooler."
  76 = "Partly cloudy with little temperature change."
  77 = "Mostly clear and cooler."
  78 = "Clearing and cooler. Precipitation ending within 6 hours."
  79 = "Mostly clear with little temperature change."
  80 = "Clearing and cooler. Precipitation ending within 6 hours."
  81 = "Mostly clear and cooler."
  82 = "Partly cloudy with little temperature change."
  83 = "Mostly clear with little temperature change."
  84 = "Increasing clouds with little temperature change. Precipitation possible within 24 hours."
  85 = "Mostly cloudy and cooler. Precipitation continuing."
  86 = "Partly cloudy with little temperature change."
  87 = "Mostly clear with little temperature change."
  88 = "Mostly cloudy and cooler. Precipitation likely."
  89 = "Mostly cloudy with little temperature change. Precipitation continuing."
  90 = "Mostly cloudy with little temperature change. Precipitation likely."
  91 = "Partly cloudy with little temperature change."
  92 = "Mostly clear with little temperature change."
  93 = "Increasing clouds and cooler. Precipitation possible and windy within 6 hours."
  94 = "Increasing clouds with little temperature change. Precipitation possible and windy within 6 hours."
  95 = "Mostly cloudy and cooler. Precipitation continuing. Increasing winds."
  96 = "Partly cloudy with little temperature change."
  97 = "Mostly clear with little temperature change."
  98 = "Mostly cloudy and cooler. Precipitation likely. Increasing winds."
  99 = "Mostly cloudy with little temperature change. Precipitation continuing. Increasing winds."
 100 = "Mostly cloudy with little temperature change. Precipitation likely. Increasing winds."
 101 = "Partly cloudy with little temperature change."
 102 = "Mostly clear with little temperature change."
 103 = "Increasing clouds and cooler. Precipitation possible within 12 to 24 hours possible wind shift to the W, NW, or N."
 104 = "Increasing clouds with little temperature change. Precipitation possible within 12 to 24 hours possible wind shift to the W, NW, or N."
 105 = "Partly cloudy with little temperature change."
 106 = "Mostly clear with little temperature change."
 107 = "Increasing clouds and cooler. Precipitation possible within 6 hours possible wind shift to the W, NW, or N."
 108 = "Increasing clouds with little temperature change. Precipitation possible within 6 hours possible wind shift to the W, NW, or N."
 109 = "Mostly cloudy and cooler. Precipitation ending within 12 hours possible wind shift to the W, NW, or N."
 110 = "Mostly cloudy and cooler. Possible wind shift to the W, NW, or N."
 111 = "Mostly cloudy with little temperature change. Precipitation ending within 12 hours possible wind shift to the W, NW, or N."
 112 = "Mostly cloudy with little temperature change. Possible wind shift to the W, NW, or N."
 113 = "Mostly cloudy and cooler. Precipitation ending within 12 hours possible wind shift to the W, NW, or N."
 114 = "Partly cloudy with little temperature change."
 115 = "Mostly clear with little temperature change."
 116 = "Mostly cloudy and cooler. Precipitation possible within 24 hours possible wind shift to the W, NW, or N."
 117 = "Mostly cloudy with little temperature change. Precipitation ending within 12 hours possible wind shift to the W, NW, or N."
 118 = "Mostly cloudy with little temperature change. Precipitation possible within 24 hours possible wind shift to the W, NW, or N."
 119 = "Clearing, cooler and windy. Precipitation ending within 6 hours."
 120 = "Clearing, cooler and windy."
 121 = "Mostly cloudy and cooler. Precipitation ending within 6 hours. Windy with possible wind shift to the W, NW, or N."
 122 = "Mostly cloudy and cooler. Windy with possible wind shift o the W, NW, or N."
 123 = "Clearing, cooler and windy."
 124 = "Partly cloudy with little temperature change."
 125 = "Mostly clear with little temperature change."
 126 = "Mostly cloudy with little temperature change. Precipitation possible within 12 hours. Windy."
 127 = "Partly cloudy with little temperature change."
 128 = "Mostly clear with little temperature change."
 129 = "Increasing clouds and cooler. Precipitation possible within 12 hours, possibly heavy at times. Windy."
 130 = "Mostly cloudy and cooler. Precipitation ending within 6 hours. Windy."
 131 = "Partly cloudy with little temperature change."
 132 = "Mostly clear with little temperature change."
 133 = "Mostly cloudy and cooler. Precipitation possible within 12 hours. Windy."
 134 = "Mostly cloudy and cooler. Precipitation ending in 12 to 24 hours."
 135 = "Mostly cloudy and cooler."
 136 = "Mostly cloudy and cooler. Precipitation continuing, possible heavy at times. Windy."
 137 = "Partly cloudy with little temperature change."
 138 = "Mostly clear with little temperature change."
 139 = "Mostly cloudy and cooler. Precipitation possible within 6 to 12 hours. Windy."
 140 = "Mostly cloudy with little temperature change. Precipitation continuing, possibly heavy at times. Windy."
 141 = "Partly cloudy with little temperature change."
 142 = "Mostly clear with little temperature change."
 143 = "Mostly cloudy with little temperature change. Precipitation possible within 6 to 12 hours. Windy."
 144 = "Partly cloudy with little temperature change."
 145 = "Mostly clear with little temperature change."
 146 = "Increasing clouds with little temperature change. Precipitation possible within 12 hours, possibly heavy at times. Windy."
 147 = "Mostly cloudy and cooler. Windy."
 148 = "Mostly cloudy and cooler. Precipitation continuing, possibly heavy at times. Windy."
 149 = "Partly cloudy with little temperature change."
 150 = "Mostly clear with little temperature change."
 151 = "Mostly cloudy and cooler. Precipitation likely, possibly heavy at times. Windy."
 152 = "Mostly cloudy with little temperature change. Precipitation continuing, possibly heavy at times. Windy."
 153 = "Mostly cloudy with little temperature change. Precipitation likely, possibly heavy at times. Windy."
 154 = "Partly cloudy with little temperature change."
 155 = "Mostly clear with little temperature change."
 156 = "Increasing clouds and cooler. Precipitation possible within 6 hours. Windy."
 157 = "Increasing clouds with little temperature change. Precipitation possible within 6 hours. Windy"
 158 = "Increasing clouds and cooler. Precipitation continuing. Windy with possible wind shift to the W, NW, or N."
 159 = "Partly cloudy with little temperature change."
 160 = "Mostly clear with little temperature change."
 161 = "Mostly cloudy and cooler. Precipitation likely. Windy with possible wind shift to the W, NW, or N."
 162 = "Mostly cloudy with little temperature change. Precipitation continuing. Windy with possible wind shift to the W, NW, or N."
 163 = "Mostly cloudy with little temperature change. Precipitation likely. Windy with possible wind shift to the W, NW, or N."
 164 = "Increasing clouds and cooler. Precipitation possible within 6 hours. Windy with possible wind shift to the W, NW, or N."
 165 = "Partly cloudy with little temperature change."
 166 = "Mostly clear with little temperature change."
 167 = "Increasing clouds and cooler. Precipitation possible within 6 hours possible wind shift to the W, NW, or N."
 168 = "Increasing clouds with little temperature change. Precipitation possible within 6 hours. Windy with possible wind shift to the W, NW, or N."
 169 = "Increasing clouds with little temperature change. Precipitation possible within 6 hours possible wind shift to the W, NW, or N."
 170 = "Partly cloudy with little temperature change."
 171 = "Mostly clear with little temperature change."
 172 = "Increasing clouds and cooler. Precipitation possible within 6 hours. Windy with possible wind shift to the W, NW, or N."
 173 = "Increasing clouds with little temperature change. Precipitation possible within 6 hours. Windy with possible wind shift to the W, NW, or N."
 174 = "Partly cloudy with little temperature change."
 175 = "Mostly clear with little temperature change."
 176 = "Increasing clouds and cooler. Precipitation possible within 12 to 24 hours. Windy with possible wind shift to the W, NW, or N."
 177 = "Increasing clouds with little temperature change. Precipitation possible within 12 to 24 hours. Windy with possible wind shift to the W, NW, or N."
 178 = "Mostly cloudy and cooler. Precipitation possibly heavy at times and ending within 12 hours. Windy with possible wind shift to the W, NW, or N."
 179 = "Partly cloudy with little temperature change."
 180 = "Mostly clear with little temperature change."
 181 = "Mostly cloudy and cooler. Precipitation possible within 6 to 12 hours, possibly heavy at times. Windy with possible wind shift to the W, NW, or N."
 182 = "Mostly cloudy with little temperature change. Precipitation ending within 12 hours. Windy with possible wind shift to the W, NW, or N."
 183 = "Mostly cloudy with little temperature change. Precipitation possible within 6 to 12 hours, possibly heavy at times. Windy with possible wind shift to the W,
 NW, or N."
 184 = "Mostly cloudy and cooler. Precipitation continuing."
 185 = "Partly cloudy with little temperature change."
 186 = "Mostly clear with little temperature change."
 187 = "Mostly cloudy and cooler. Precipitation likely. Windy with possible wind shift to the W, NW, or N."
 188 = "Mostly cloudy with little temperature change. Precipitation continuing."
 189 = "Mostly cloudy with little temperature change. Precipitation likely."
 190 = "Partly cloudy with little temperature change."
 191 = "Mostly clear with little temperature change."
 192 = "Mostly cloudy and cooler. Precipitation possible within 12 hours, possibly heavy at times. Windy."
 193 = "FORECAST REQUIRES 3 HOURS OF RECENT DATA"
 194 = "Mostly clear and cooler."
 195 = "Mostly clear and cooler."
 196 = "Mostly clear and cooler."

Here is a listing of the forecast icon numbers.  Note that I did not necessarily list each specific icon component.  (i.e. Rain instead of Cloud and rain and Part Sun instead of cloud and Sun)

  2 - Cloud
  3 - Rain
  6 - Part Sun
  7 - Part Sun and Rain
  8 - Sun
 12 - Snow
 13 - Rain and Snow
 16 - Part Sun and Snow
 17 - Part Sun, Rain, and Snow

=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>

20,22,46,29.95,54.8,16,19,NW,52,37.5,54.8,54.8,0.00,0.00,0.61,12.99,07:04,18:41,76.1,24,36.7,ST,6,75,00/00/00,0.00

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 alarms for these sensors are ignored.  Feel free to send me your requests for what you would like to see added or changed.  My e-mail address is stsander@sblan.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 piece 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.

B<1.2 - >	Improved sanity checks for out of range values.
	Integrated work done by Tim Miller on barometric trend and forecast rules.
	Calculate the barometric trend for stations with Rev. A firmware.  (auto-detected)
	Added monitoring for the various transmitter battery levels for wireless stations.
	Modified the wind chill calculation to follow the formula and guidelines from the NWS.  (I had left some cruft in the code from the earliest days of the program.)
	Added alarm bit processing.
	The storm total rainfall and storm start date are now recorded.

=head1 TO DO

A brief listing of some of the plans I currently have:

Modify the configuration interface to use a vanprod.conf file.
Allow you to define actions to be associated with an alarm.  Example:  exec'ing another program or script.
Allow you to set "silent alarms" using the .conf file.
Other things you may think of that I haven't.


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@sblan.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
