#!/usr/bin/perl ### these modules are on cpan.org : use LWP::UserAgent; use HTTP::Request; use Device::LaCrosse::WS23xx; =head1 B: Weather Underground Personal Weather Station Upload Script =head2 For Lacrosse WS23xx weather stations over serial device This script communications over a serial connection to a compatible LaCrosse Weather Station device, then formats that information and reports it to Weather Underground's ( http://www.wunderground.com ) Personal Weather Station data collection service. This script takes 5-10 seconds to run, most of that time is to obtain the response from serial port connected LaCrosse weather station. You can run this script as a Unix cron command once per minute. If you are using a wireless connection from the LaCrosse base station to the outside devices, the base station will report 'weird' data when the wireless connection is unstable, recent additions will prevent uploading of the inappropriate data. The perl/c + modules installed on FC7 x86 w/ standard development libraries, tested on usb-serial connection, as well as on Mac OS X 10.4 & 10.5 using the standard Perl install pulling the modules in via CPAN with the weather station connected over a KeySpan serial-to-USB converter. =head2 Unix Cron set-up Example cron entries to fire off uploads (expects this script installed in /usr/local/bin) with these scripts installed as a Unix service. They could also be installed under a user's cron with files located in a home directory, etc. I =over 1 * * * * * /usr/local/bin/wu-upload.pl >> /var/log/wu-cron.log =back I =over 1 0,5,10,15,20,25,30,35,40,45,50,55 * * * * /usr/local/bin/wu-upload.pl >> /var/log/wu-log.log =back =head2 Control Variables I B - set to 1 to NOT send data (testing), normally to 0 to send data. B - set to 1 to send more information to standard out I B - Weather Underground URL for real-time updates B - Weather Underground Personal Weather Station Identification String B - Weather Underground Password for PWS ( I this is sent over HTTP as clear text) B - Frequency of updates to Weather Underground [in seconds] I B - path to the serial port device B - Path to XML encoded output '/var/log/wu.log'; =cut my $nosend = 1; ### 1 for testing, don't upload my $verbose = 1; ### 1 for testing cmd line, or write to stdout for sending to a log ### define Weather Underground sites my $wuurl = "http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?"; ## standard update ( deprecated) my $rturl = "http://rtupdate.wunderground.com/weatherstation/updateweatherstation.php?"; ## real-time update ########################################## ### specify these items -> my $id = "WUIdStr123" ### WU station identifier; my $passwd = "wupassword"; ### plaintext password over http - insecure ! my $freq = 60; ### update frequency #my $serial = "/dev/ttyUSB0"; ### serial port, a serial->usb converter my $serial = "/dev/tty.KeySerial1"; # keyspan serial->usb port on Mac OS X # Added local log file in xml format # with internal temperature/humidity # record of response from wunderground # as well as data detected as bogus my $doXML = 0; ## set to 1 to log to file specified below my $xmlLog = "/var/log/wu.log"; my $doCSV = 0; ## set to 1 to log to file specified below my $csvLog = "/Library/WebServer/Documents/$id.csv"; ########################################## # modifications Bill Caloccia 14 Jul 2008 =head2 Data Validation for Wireless connections The La Crosse 2317 Weather Station Pro has the capability of connecting to the outdoor instruments via a wireless connection, advertised as working up to 330 feet, however in my experience it can be dodgy at distances far less than the advertised maximum. When this wireless connection is not functioning this program receives bogus data from the base station over the serial line as there is no valid data from the outside instruments. The following have been observed from the La Crosse 2317 base station under these conditions: B or/and B I adding checking for those conditions as well as bounds checking checking for other values, either based on accepted values or extremes. In the event that the data is determined to be invalid, the parameter will not be reported to Weather Underground, however it will be logged, as will the detection of it being incorrect. B B positive number values from 0 to 113 mph, inclusive B positive number values from 0 to 360 degrees, inclusive B positive number values from 0 to 100 percent, inclusive B and B report if humidity is valid and these do not both equal -22F B should not exceed the current temperature B allow values from 25-35 inHg, inclusive B allow values from 0 to 10"/hour inclusive (maximum recorded for my area is 7.5" per hour) =cut ### get the data my $ws = Device::LaCrosse::WS23xx->new($serial) or die "Cannot communicate with $serial: $!\n"; die "wireless connection failure\n" unless ( $ws->get("Outdoor_Temperature","F")); #@then = localtime($ws->get("Date/Time_last_record_datetime")); # time produced was local time, not UTC as desired by wunderground # modified to use host time, with gmtime my $thenTS = $ws->get("Date/Time_last_record_datetime"); @then = gmtime($thenTS); $dateutc=sprintf("%4d-%02d-%02d %02d:%02d:%02d",$then[5]+1900,$then[4]+1,$then[3],$then[2],$then[1],$then[0]); # This seems to report a time which is only updated once per hour on # the LaCrosse 2317 with WWV receiver # So switched to using host time (which should be synchronized with NTP) # NTP - Network Time Protocol my $nowTS = time; my @now = gmtime($nowTS); my $nowutc = sprintf("%4d-%02d-%02d %02d:%02d:%02d",$now[5]+1900,$now[4]+1,$now[3],$now[2],$now[1],$now[0]); my $dt = $nowTS - $thenTS; # seconds drift from LaCrosse to host my $bogus = 0; my $bogoStr = ""; my $url = "&dateutc=$nowutc" ; # use local host derive time to WU $dewPtF = $ws->get("Dewpoint","F"); $tempF = $ws->get("Outdoor_Temperature","F"); $humidity = $ws->get("Outdoor_Humidity"); my $humidBad = 0; ## non-numeric is bogus if ($humidity < 0 || $humidity > 100 || $humidity =~ /[^0-9]/ ) { $bogus += 1; $bogoStr = $bogoStr."humidity "; $humidBad = 1; } else { $url = $url . "&humidity=$humidity" ; } if ($humidBad && $tempF == -22.0 && $dewPtF == -22.0 ) { ## -22 on both is bogus $bogus += 1; $bogoStr = $bogoStr."dewPt+tempF "; ## and near local extreme } else { if ( $dewPtF > $tempF ) { $bogus = 1; $bogoStr = $bogoStr."dewPt "; } else { $url = $url . "&dewptf=$dewPtF" ; } $url = $url . "&tempf=$tempF" ; } $windspeedmph = $ws->get("Wind_Speed","mph"); $winddir = $ws->get("Wind_Direction"); if ($windspeedmph < 0 || $windspeedmph > 113 ) { # 114.1 is observed bogus value $bogus += 1; $bogoStr = $bogoStr."windSpeed "; } else { $url = $url . "&windspeedmph=$windspeedmph" ; if ($winddir < 0 || $winddir > 360) { # valid values [0..360] $bogus += 1; $bogoStr = "windDir "; } else { $url = $url. "&winddir=$winddir"; } } $rainIn = $ws->get("Rain_1hour","in"); if ($rainIn < 0 || $rainIn > 10 ) { ### 10"/hr seems excesive for my area $bogus += 1; $bogoStr = $bogoStr."rainIn "; } else { $url = $url . "&rainin=$rainIn"; } $baromin = $ws->get("Absolute_Pressure","inHg");## mean sea-level 29.92inHg if ($baromin < 25 || $baromin > 35 ) { ## typhoon tip 25.69inHg $bogus += 1; $bogoStr = $bogoStr."baromIn "; } else { $url = $url . "&baromin=$baromin" ; } print $bogoStr . "\n" if $bogus; print "send $url\n" if ($bogus || $verbose); $url = $url . "&softwaretype=customPerl&action=updateraw&realtime=1&rtfreq=$freq"; ##### change above realtime var if you change the url, only the new one supports it unless ( $nosend ) { my $idline = "ID=".$id."&PASSWORD=".$passwd ; $ua = LWP::UserAgent->new; if ($verbose) { print "$rturl$idline$url";} $req = HTTP::Request->new(GET => "$rturl$idline$url"); $resp = $ua->simple_request($req); $response = $resp->content; } chomp($response); print "$response\n" if $verbose; $inTempF = $ws->get("Indoor_Temperature","F"); $inHumidity = $ws->get("Indoor_Humidity"); $tendency = $ws->get("Tendency"); $forecast = $ws->get("Forecast"); if ($bogus) { $bogoStr = "bogusData='".$bogoStr."'"; } print "$nowutc station='$id' tempF='$tempF' dewPtF='$dewPtF' humidity='$humidity' windDir='$winddir' windSpeedMPH='$windspeedmph' rainIn='$rainIn' baromIn='$baromin' response='$response' $bogoStr inTempF='$inTempF' inHumidity='$inHumidity' tendency='$tendency' forecast='$forecast' dt='$dt' \\>\n" if $verbose; if ($doXML) { open (XMLlog, ">> ". $xmlLog) || die "failed to open log" ; print XMLlog "\n" ; } if ($doCSV) { open (CSVlog, ">> ". $csvLog) || die "failed to open log" ; print CSVlog "$nowutc,$tempF,$dewPtF,$humidity,$windspeedmph,$winddir,$rainIn,$baromin,$tendency,$forecast,$inTempF,$inHumidity,$response,$bogoStr\n"; } die "$response\n" unless ( $response =~ /success/i ) ; exit; =head1 Weather Underground Personal Weather Station Data collection Here is the preferred URL used for the real-time uploading of your collected weather data to Weather Underground (July 2008): I If you go here without parameters you will get a brief usage): Here is the usage (July 2008) displayed with blank parameters: B B B [B] B [ID as registered by wunderground.com] B [PASSWORD registered with this ID] B - [YYYY-MM-DD HH:MM:SS (mysql format)] B - [0-360] B - [mph] B - [windgust mph] B - [%] B - [temperature F] B - [rain in] B - [daily rain in accumulated] B - [barom in] B - [dewpoint F] B - [text] -- metar style (+RA) B - [text] -- SKC, FEW, SCT, BKN, OVC B - [text] ie: vws or weatherdisplay B I GPL - original code K Brown 11/30/07 I Kenneth Brown 12/2007 I Bill Caloccia 20/07/2008 I Library by 2007 by Eduardo Santiago =head1 Device::LaCrosse::WS23xx version 0.06 Device::LaCrosse::WS23xx provides a simple interface for reading data from La Crosse Technology WS-2300 series weather stations. It is based on the Open2300 project, but differs in several respects =head1 Device::LaCrosse::WS23xx get($;$) func param 1 wind_unit LCD_contrast B B B C Min_Indoor_Temperature C Max_Indoor_Temperature C Min_Indoor_Temperature_datetime time_t Max_Indoor_Temperature_datetime time_t Low_Alarm_Indoor_Temperature C High_Alarm_Indoor_Temperature C B C Min_Outdoor_Temperature C Max_Outdoor_Temperature C Min_Outdoor_Temperature_datetime time_t Max_Outdoor_Temperature_datetime time_t Low_Alarm_Outdoor_Temperature C High_Alarm_Outdoor_Temperature C Windchill C Min_Windchill C Max_Windchill C Min_Windchill_datetime time_t Max_Windchill_datetime time_t Low_Alarm_Windchill C High_Alarm_Windchill C B C Min_Dewpoint C Max_Dewpoint C Min_Dewpoint_datetime time_t Max_Dewpoint_datetime time_t Low_Alarm_Dewpoint C High_Alarm_Dewpoint C B % Min_Indoor_Humidity % Max_Indoor_Humidity % Min_Indoor_Humidity_datetime time_t Max_Indoor_Humidity_datetime time_t Low_Alarm_Indoor_Humidity % High_Alarm_Indoor_Humidity % B % Min_Outdoor_Humidity % Max_Outdoor_Humidity % Min_Outdoor_Humidity_datetime time_t Max_Outdoor_Humidity_datetime time_t Low_Alarm_Outdoor_Humidity % High_Alarm_Outdoor_Humidity % Rain_24hour mm Max_Rain_24hour mm Max_Rain_24hour_datetime time_t B mm Max_Rain_1hour mm Max_Rain_1hour_datetime time_t Rain_Total mm Rain_Total_datetime time_t Min__wind m/s Max__wind m/s Min_Date/Time_wind_datetime time_t Max_Date/Time_wind_datetime time_t B m/s B degrees Low_wind_alarm_setting m/s High_wind_alarm_setting m/s Connection_Type Countdown_time_to_next_datBinary seconds Absolute_Pressure hPa Relative_Pressure hPa Pressure_Correction hPa Min_Absolute_Pressure hPa Min_Relative_Pressure hPa Max_Absolute_Pressure hPa Max_Relative_Pressure hPa Min_Pressure_datetime time_t Max_Pressure_datetime time_t Low_Alarm_Pressure hPa High_Alarm_Pressure hPa History_saving_interval minutes Countdown_to_next_saving minutes Date/Time_last_record_datetime time_t Pointer_to_last_written_Record Number_of_Records Only a few reasonable UNIT conversions are available get($;$) function param 2: From To ---- -- C F hPa inHh, mmHg m/s kph, mph, kt mm in B Copyright (C) 2007 by Eduardo Santiago This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.8 or, at your option, any later version of Perl 5 you may have available. =cut