Saturday, March 1, 2008

LaCrosse WS2308 Weather Station

I installed a LaCrosse WS2308 weather station with the idea of having my CentOS 5 Linux server uploading the data from the WS2308 to Weather Underground (www.weatherunderground.com). If you are a techie, you've gotta see the coolness of logging into Weather Underground and seeing live weather data at your house. My station's link:

http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=KALONEON3

The WS2308 comes with Windows software, not much help on CentOS (maybe it'd work with Wine?) and connects to the PC via serial cable. LaCrosse is kind enough to offer a free high quality serial to USB adapter available upon request. Luckily, my server is old enough to have serial ports (although I did order the free USB adapter, I couldn't get it to work. dmesg showed it identified but I couldn't communicate with the device).

I tried several software packages and settled on a perl script called wu-upload.pl. This script uses the module Device::LaCrosse::WS23xx to query the WS2308. I decided to update the script to extract additional information as well as a simultaneous upload to a MySQL database.

Here's the updated wu-upload.pl script, unfortunately blogger.com wraps long lines so for the script to work you'll need to account for that):

#!/usr/bin/perl
#
# Weather Underground PWS Upload Script for Lacrosse WS23xx weather stations over serial device
#
# Contributed by Kenneth Brown 12/2007
#
# Use settings below -- must edit idline, freq (to match cron frequency), and possibly serial
#
# Example cron entry to fire off uploads (if you install this script into /usr/local/bin)
# 0,10,20,30,40,50 * * * * /usr/local/bin/wu-upload.pl >> wu-log.log
#
# usage - cron this for at least a one minute run, my serial port takes between 5-10 seconds to report the values on average.#
# perl/c + modules installed on FC7 x86 w/ standard development libraries, tested on usb-serial connection
# GPL - original code, K Brown 11/30/07
#
# Updated by FlakRat
# - Changed from collecting local time to gmtime, that's apparently what WeatherUnderground is expecting
# - Added a $debug option that will provide more verbose logging
# - Modified verbose output to mask the password
# - Added 2 settings that will output a mysql insert statement to stdout, and a second that will insert the data
# into a mysql database. This has the advantage of importing into a local db and uploading to WeatherU during
# the same command execution

### these modules are on cpan.org :

use LWP::UserAgent;
use HTTP::Request;
use Device::LaCrosse::WS23xx;
use Mysql;

### define wu 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 $idline = "ID=&PASSWORD="; ### plaintext password over http - insecure !
my $freq = 300; ### update frequency in seconds
#my $serial = "/dev/ttyUSB0"; ### serial port, in this case a keyspan serial->usb
my $serial = "/dev/ttyS0"; ### serial port, in this case a keyspan serial->serial
##########################################

my $nosend = 0; ### 1 for testing, don't upload
my $verbose = 0; ### 1 for testing cmd line, or write to stdout for sending to a log
my $debug = 0; ### 1 for debugging cmd line more than just verbose, or write to stdout for sending to a log
my $mysqlout = 0; ### 1 to output to stdout a SQL INSERT statement, this can be piped into a file for future insertion into MySQL
my $mysqlinsert = 1; ### 1 to insert the data into MySQL

### MySQL Info
my $host = "localhost";
my $database = "weather";
my $tablename = "weather_history";
my $mysqluser = "";
my $mysqlpass = "";

### get the data
my $ws = Device::LaCrosse::WS23xx->new($serial)
or die "Cannot communicate with $serial: $!\n";
#my $ws = Device::LaCrosse::WS23xx->new($serial,trace => '/tmp/wu-upload.trace')
# 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"));
#@then = localtime(time);
# localtime returns CST on my system, we want UTC
@then = gmtime(time);

$dateutc=sprintf("%4d-%02d-%02d %02d:%02d:%02d",$then[5]+1900,$then[4]+1,$then[3],$then[2],$then[1],$then[0]);
print "dateutc: $dateutc\n" if $debug;
$dateutc = "&dateutc=$dateutc";
$winddir = $ws->get("Wind_Direction");
print "winddir: $winddir\n" if $debug;
$windspeedmph = $ws->get("Wind_Speed","mph");
#$windspeedmph = $ws->get("Wind_Speed");
print "windspeedmph: $windspeedmph\n" if $debug;
#$windgustmph = $ws->get("Wind_Gust","mph");
#print "windgustmph: $windgustmph\n" if $debug;
$tempf = $ws->get("Outdoor_Temperature","F");
print "tempf: $tempf\n" if $debug;
$rainin = $ws->get("Rain_1hour","in");
print "rainin: $rainin\n" if $debug;
$dailyrainin = $ws->get("Rain_24hour","in");
print "dailyrainin: $dailyrainin\n" if $debug;
$baromin = $ws->get("Absolute_Pressure","inHg");
print "baromin: $baromin\n" if $debug;
$dewptf = $ws->get("Dewpoint","F");
print "dewptf: $dewptf\n" if $debug;
$humidity = $ws->get("Outdoor_Humidity");
print "humidity: $humidity\n" if $debug;

# MySQL
my $timestamp_gmt = sprintf("%4d%02d%02d%02d%02d%02d",$then[5]+1900,$then[4]+1,$then[3],$then[2],$then[1],$then[0]);
my $rec_date = sprintf("%4d-%02d-%02d",$then[5]+1900,$then[4]+1,$then[3]);
my $rec_time = sprintf("%02d:%02d:%02d",$then[2],$then[1],$then[0]);
my $temp_in = $ws->get("Indoor_Temperature","F");
my $temp_out = $ws->get("Outdoor_Temperature","F");
my $dewpoint = $ws->get("Dewpoint","F");
my $rel_hum_in = $ws->get("Indoor_Humidity");
my $rel_hum_out = $ws->get("Outdoor_Humidity");
my $windspeed = $ws->get("Wind_Speed","mph");
my $wind_angle = "";
my $wind_direction = $ws->get("Wind_Direction");
my $wind_chill = $ws->get("Windchill","F");
my $rain_1h = $ws->get("Rain_1hour","in");
my $rain_24h = $ws->get("Rain_24hour","in");
my $rain_total = $ws->get("Rain_Total","in");
my $abs_pressure = $ws->get("Absolute_Pressure","inHg");
my $rel_pressure = $ws->get("Relative_Pressure","inHg");
my $tendency = $ws->get("Tendency");
my $forecast = $ws->get("Forecast");
my $myquery =
"INSERT INTO $tablename " .
"(timestamp_gmt,rec_date,rec_time,temp_in,temp_out,dewpoint,rel_hum_in,rel_hum_out,windspeed,wind_direction,wind_chill,rain_1h,rain_24h,rain_total,abs_pressure,rel_pressure,tendency,forecast) " .
"VALUES($timestamp_gmt,\"$rec_date\",\"$rec_time\",$temp_in,$temp_out,$dewpoint,$rel_hum_in,$rel_hum_out,$windspeed,$wind_direction,$wind_chill,$rain_1h,$rain_24h,$rain_total,$abs_pressure,$rel_pressure,\"$tendency\",\"$forecast\");";
print "$myquery\n" if $mysqlout;

if ($mysqlinsert) {
# PERL MYSQL CONNECT()
$connect = Mysql->connect($host, $database, $mysqluser, $mysqlpass);

# SELECT DB
$connect->selectdb($database);

# EXECUTE THE QUERY FUNCTION
$execute = $connect->query($myquery);
}

my $url =
$idline .
"&dateutc=$dateutc" .
"&winddir=$winddir" .
"&windspeedmph=$windspeedmph" .
"&tempf=$tempf" .
"&rainin=$rainin".
"&dailyrainin=$dailyrainin".
"&baromin=$baromin" .
"&dewptf=$dewptf" .
"&humidity=$humidity" .
"&softwaretype=customPerl&action=updateraw&realtime=1&rtfreq=$freq";

##### change above realtime var if you change the url, only the new one supports it

my $now = `date`;
chomp($now);
my $urlmask = "$url";
$urlmask =~ s/PASSWORD=[a-zA-Z0-9]*&/PASSWORD=xxxxxxxxxx&/;
print "$now: send $urlmask\n" if $verbose;

unless ( $nosend ) {
$ua = LWP::UserAgent->new;
$req = HTTP::Request->new(GET => "$rturl$url");
$resp = $ua->simple_request($req);
$response = $resp->content;
}

my $now = `date`;
chomp($now);
# print "$response\n" if $verbose;
print "$now: $response\n" if $verbose;
die "$response\n" unless ( $response =~ /success/i ) ;

exit;

###### data section contains constants,units for input and output

1;

=skip

Device::LaCrosse::WS23xx
get($;$) func param 1

wind_unit
LCD_contrast
Forecast
Tendency
Indoor_Temperature 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
Outdoor_Temperature 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
Dewpoint 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
Indoor_Humidity %
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 %
Outdoor_Humidity %
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
Rain_1hour 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
Wind_Speed m/s
Wind_Direction 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


Here is the URL used in the uploading (if you go here without parameters
you will get a brief usage):
http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php
http://rtupdate.wunderground.com/weatherstation/updateweatherstation.php ## real time updates ( preferred )

Here is the usage that will be displayed with blank parameters:
usage
action [action=updateraw]
ID [ID as registered by wunderground.com]
PASSWORD [PASSWORD registered with this ID]
dateutc - [YYYY-MM-DD HH:MM:SS (mysql format)]
winddir - [0-360]
windspeedmph - [mph]
windgustmph - [windgustmph ]
humidity - [%]
tempf - [temperature F]
rainin - [rain in (hourly)] -- the accumulated rainfall in the past 60 mins
dailyrainin - [rain so far today in localtime]
baromin - [barom in]
dewptf- [dewpoint F]
weather - [text] -- metar style (+RA)
clouds - [text] -- SKC, FEW, SCT, BKN, OVC
soiltempf - [temp F]
soilmoisture - [%]
leafwetness - [%]
solarradiation - [MJ/m^2]
UV - [index]
visibility - [nm]
softwaretype - [text] ie: vws or weatherdisplay
#realtime http only
realtime=1
rtfreq - frequency in seconds for nominal update


The MySQL table is defined below. I create a new database called "weather" and create the weather_history table in the new database.

DROP TABLE IF EXISTS `weather_history`;
CREATE TABLE `weather_history` (
`timestamp_gmt` bigint(14) NOT NULL default '0',
`rec_date` date NOT NULL default '0000-00-00',
`rec_time` time NOT NULL default '00:00:00',
`temp_in` decimal(3,1) NOT NULL default '0.0',
`temp_out` decimal(3,1) NOT NULL default '0.0',
`dewpoint` decimal(3,1) NOT NULL default '0.0',
`rel_hum_in` tinyint(3) NOT NULL default '0',
`rel_hum_out` tinyint(3) NOT NULL default '0',
`windspeed` decimal(3,1) NOT NULL default '0.0',
`wind_angle` decimal(3,1) NOT NULL default '0.0',
`wind_direction` char(3) NOT NULL default '',
`wind_chill` decimal(3,1) NOT NULL default '0.0',
`rain_1h` decimal(3,1) NOT NULL default '0.0',
`rain_24h` decimal(3,1) NOT NULL default '0.0',
`rain_total` decimal(4,1) NOT NULL default '0.0',
`abs_pressure` decimal(4,1) NOT NULL default '0.0',
`rel_pressure` decimal(4,1) NOT NULL default '0.0',
`tendency` varchar(7) NOT NULL default '',
`forecast` varchar(6) NOT NULL default '',
UNIQUE KEY `timestamp_gmt` (`timestamp_gmt`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

1 comment:

StarcityEnterprises.com said...

La Crosse Products are available here also, I simply rely upon www.starcityenterprises.com site for buying Weather Stations, Anemometer, Brethlyzers Battery Chargers etc. They provide efficient service and delivery.