The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

(our $ME = $0) =~ s|^.*/||;

use strict;
use warnings;

###############################################################################
# BEGIN user-customizable section

# Path to serial device.
my $Serial = '/dev/lacrosse';

# Path to output log file.  <YYYY> will be replaced with the current year,
# <MM> with the numeric month (01-12), <DD> with the day (01-31).  The
# base directory will be created if it doesn't exist.  See 'sub logfile'
# below for full details.
my $Log_File_Template = '/tmp/var/www/wx/data/<YYYY>/<MM>/<DD>';

# The fields we want, in the format we want.
#
# By now you've probably realized that Ed is fanatical, nay, neurotic
# about writing table-driven code.  All this could be written much more
# quickly with a simple printf "..." and a series of $ws->get()s.  But
# any attempt to edit that would end in disaster as the format sequences
# fall out of sync with the fields.  This is maintainable.
#
# The format below generates output looking like this:
#
#    <date> 67.8 37%  55.6 35% 30.41 Rising  247 @  0  0.0  0.0  0.0
#
# That's a format that Ed likes because it's easy to read at a glance,
# easy to identify what each number means, and easy to scan multiple
# lines at once because the columns match up even if the number widths change.
#
# It may not be a format you like.  If you want to change it, do so!
# The format of this table is:
#
#        <Field>       [units]    <printf format>
# where:
#        Field     is a name; see Device::LaCrosse::WS23xx::MemoryMap
#        [units]   is the desired units to which to convert
#        Format    is a standard printf format, possibly with other characters
#
# Note Format in particular.  For humidity, '%2d%%' results in ' 5%' or '10%'.
# The '@' in Wind_Direction gives lines like '270 @ 10'.  The only thing you
# can't have is trailing spaces; I implement those by padding the next field.
#
my $want = <<'END_WANT';
Indoor_Temperature       F      %4.1f
Indoor_Humidity                 %2d%%	# The '%' sign is for readability
Outdoor_Temperature      F      %5.1f	# 5.1f gives us an extra space, ibid.
Outdoor_Humidity                %2d%%
Relative_Pressure        inHg   %5.2f
Tendency                        %-7s
Wind_Direction                  %03d @	# '@' sign is again for readability
Wind_Speed               kt     %2.0f
Rain_1h                         %4.1f
Rain_24h                        %4.1f
Rain_Total                      %4.1f
END_WANT

# END   user-customizable section
###############################################################################

use Device::LaCrosse::WS23xx;
use Time::Piece;
use File::Path		qw(mkpath);
use File::Basename	qw(dirname);

my $ws = Device::LaCrosse::WS23xx->new( $Serial )
    or die "$ME: Cannot communicate with $Serial: $!\n";

my $now = localtime;

# All records start with the current date and time
my $logline = sprintf("%d-%02d-%02d %02d:%02d:%02d",
		      $now->year, $now->mon, $now->mday,
		      $now->hour, $now->min, $now->sec);

# The rest of the line is a space-separated formatted list; see above.
for my $line (split "\n", $want) {
    $line =~ s/\s+#.*$//;		# strip comments

    #           1   1   23   3   2 4   4
    $line =~ m!^(\S+)\s+((\S+)\s+)?(%.*)$!
	or die "$ME: Internal error: cannot grok formattine line '$line'";
    my @field = ($1);
    push @field, $3	if defined $2;
    my $format = $4;

    my $value = $ws->get(@field);
    $logline .= " " . sprintf($format, $value);
}

# Done.  Write the record to our log file.
my $logfile = logfile($now);
open LOG, '>>', $logfile
    or die "$ME: Cannot append to $logfile: $!\n";

# FIXME: a more careful implementation would do:
#   flock LOG, LOCK_EX;
#   seek  LOG, 0, SEEK_END;

print LOG $logline, "\n";
close LOG
    or die "$ME: Error writing to $logfile: $!\n";

exit 0;




sub logfile {
    my $t = shift;			# in: Time::Piece object

    # The substitutions we know how to make: each 'XX' below
    # will replace a given <XX> in the log file template.
    my %YMD = (
	YYYY =>                 $t->year,        # 2007
	YY   => sprintf("%02d", $t->yy),         #   07
	MMM  =>                 $t->monname,     #  Mar
	MM   => sprintf("%02d", $t->mon),        #   03
	DD   => sprintf("%02d", $t->mday),       #   05
    );

    # Do the substitution.  Barf if someone gets cute with '<NONESUCH>'
    (my $log = $Log_File_Template) =~ s{<([A-Z]+)>}{
	$YMD{$1} || die "Cannot interpret '<$1>'";
    }ge;

    # If parent directory doesn't exist, silently create it.
    if (! -d (my $dir = dirname($log))) {
	mkpath $dir, 0, 02755
	    or die "$ME: Cannot mkdir $dir: $!\n";
    }

    return $log;
}