package IPWE;


use warnings;
use strict;
use Carp;
use LWP::UserAgent;
use HTML::TableExtract;

my $ua = LWP::UserAgent->new;
$ua->timeout(20);
$ua->env_proxy;


# Constructor and initialization
sub new {
	my $class = shift;
	my $self = {@_};
	bless($self, $class);
	$self->_init;
	return $self;
}

sub _init {
	my $self = shift;

	for (1..9) {
		$self->{sensor}->{$_} = IPWE::Sensor->_new();
	}	

	my $i = 1;
	until ($self->update) {
		if ($i >= 5) {
			die "Could not retrieve sensor information after 5 trials\n" .
			    "Please check if the IPWE-URL is correct.\n" ;
		} 
		sleep 2;
		$i++;
	};

#	carp "New object created";
}

# Object accessor methods

sub url { 
	my $self = shift;
	return $self->{url};
}

sub update {
	my $self = shift;
	my $rc = 0;
	my $response = $ua->get($self->url . "/ipwe.cgi");
        if ($response->is_success) {

                my $content = $response->content;

		my $te = HTML::TableExtract->new(
						  headers => [qw(Sensortyp
							         Adresse
							         Beschreibung
							         Temperatur
							         Luftfeuchtigkeit
							         Windgeschwindigkeit
							         Regenmenge
								)
						  	     ]
						);


                $te->parse($content);
                # Examine all matching tables
                foreach my $ts ($te->tables) {
#			print "Table (", join(',', $ts->coords), "):\n";
			my $sensor = 1;
			my $raining = "";
			foreach my $row ($ts->rows) {

				@$row[0] =~ s/[\n]//g;
				@$row[1] =~ s/[\n]//g;
				@$row[2] =~ s/[\n]//g;
				@$row[3] =~ s/[a-z°%\/\s]//gi;
				@$row[4] =~ s/[a-z°%\/\s]//gi;
				@$row[5] =~ s/[a-z°%\/\s]//gi;

				$raining = 1 if (@$row[6] =~ /\#/);

				@$row[6] =~ s/[a-z°%\/\s\#]//gi;

				$self->{sensor}->{$sensor}->_update(type        => @$row[0],
								    address     => @$row[1],
								    description => @$row[2],
								    temperature => @$row[3],
								    humidity    => @$row[4],
								    windspeed   => @$row[5],
								    rain        => @$row[6],
								    rain_beginning     => $raining,
								    );

                  		$sensor++;
                  	}
                }

		$self->{timestamp} = time;
		$rc = 1;

	} else {
		print "Can not acces IPWE 1: " . $response->status_line . "\n";
	}

	return $rc;

}

sub sensor { 
	my $self = shift;
	my $nr = shift;

	my @sensors = ();
	if ($nr) {
		return $self->{sensor}->{$nr};
	} else {
		foreach (sort keys(%{$self->{sensor}})) {
			push @sensors, $self->{sensor}->{$_};
		}
		return @sensors;
	}
}

sub timestamp { 
	my $self = shift;
	return $self->{timestamp};
}

sub print { 
	my $self = shift;
	my ($sensor, $type, $addr, $desc, $temp, $humi, $wind, $rain, $rainbegin);

	select(STDOUT);		# make sure STDOUT is the selected file handle
	$^ = "IPWE_TOP";	# top-of-page format for selected output (STDOUT)
	$~ = "IPWE";		# report format for selected output (STDOUT)
	$= = 22;

	for $_ (1..9)  {
		$sensor  = $_;
		$type    = $self->{sensor}->{$_}->{type};
		$addr    = $self->{sensor}->{$_}->{address};
		$desc    = $self->{sensor}->{$_}->{description};
		$temp    = $self->{sensor}->{$_}->{temperature};
		$humi    = $self->{sensor}->{$_}->{humidity};
		$wind    = $self->{sensor}->{$_}->{windspeed};
		$rain    = $self->{sensor}->{$_}->{rain};
		$rainbegin = $self->{sensor}->{$_}->{rain_beginning};
		write;
	}

format IPWE_TOP =
--------------------------------------------------------------------------------------------------------
| Sensor | Sensor Type | Address | Description | Temperature | Humidity | Wind speed | Rain | Rain be- |
|        |             |         |             |     °C      |     %    |    km/h    |      | ginning  |
--------------------------------------------------------------------------------------------------------
.

format IPWE =
| @||||| | @|||||||||| | @|||||| | @|||||||||| | @|||||||||| | @||||||| | @||||||||| | @||| | @||||||| |
$sensor, $type, $addr, $desc, $temp, $humi, $wind, $rain, $rainbegin
--------------------------------------------------------------------------------------------------------
.

}

package IPWE::Sensor;

sub _new {
	my $class = shift;
	my $self = {@_};
	$self->{type}               = "";
	$self->{address}            = "";
	$self->{description}        = "";
	$self->{temperature}        = "";
	$self->{humidity}           = "";
	$self->{windspeed}          = "";
	$self->{rain}               = "";
	$self->{rain_beginning}   = "";
	$self->{rain_increased} = "";
	$self->{raining}            = "";
	$self->{new_values} = 0;

	$self->{old}->{temperature}        = "";
	$self->{old}->{humidity}           = "";
	$self->{old}->{windspeed}          = "";
	$self->{old}->{rain}               = "";
	$self->{old}->{rain_beginning}   = "";

	bless($self, $class);
	return $self;
}

sub _update {
	my $self = shift;
	my %attr = @_;

	# save old values
	$self->{old}->{temperature}        = $self->{temperature};
	$self->{old}->{humidity}           = $self->{humidity};
	$self->{old}->{windspeed}          = $self->{windspeed};
	$self->{old}->{rain}               = $self->{rain};
	$self->{old}->{rain_beginning}   = $self->{rain_beginning};

	$self->{type}             = $attr{type};
	$self->{address}          = $attr{address};
	$self->{description}      = $attr{description};
	$self->{temperature}      = $attr{temperature};
	$self->{humidity}         = $attr{humidity};
	$self->{windspeed}        = $attr{windspeed};
	$self->{rain}             = $attr{rain};
	$self->{rain_beginning} = $attr{rain_beginning};


   my $rain_old = my $rain = 0;
   $rain_old = $self->{old}->{rain} if ($self->{old}->{rain});
   $rain = $self->{rain}            if ($self->{rain});

	if ($rain_old < $rain) {
		$self->{rain_increased} = 1;
	} else {
		$self->{rain_increased} = "";
	}


	if ($self->{rain_beginning}) {
		$self->{raining} = 1;
	} else {
		$self->{raining} = "0";
	}


	$self->{new_values} = 0;
	foreach (qw(temperature humidity windspeed rain rain_beginning)) {
		if ($self->{old}->{$_} ne $self->{$_}) {
			$self->{new_values} = 1;
		}
	}
}

sub type { 
	my $self = shift;
	return $self->{type};
}

sub address { 
	my $self = shift;
	return $self->{address};
}

sub description { 
	my $self = shift;
	return $self->{description};
}

sub temperature { 
	my $self = shift;
	my $unit = shift;
        if (! defined $unit) {
		return $self->{temperature};
        } elsif ($unit eq "Fahrenheit") {
                return $self->{temperature} * 1.8 + 32;
        } elsif ($unit eq "Kelvin") {
                return $self->{temperature} + 273.15;
        } elsif ($unit eq "Rankine") {
                return $self->{temperature} * 1.8 + 491.67;
	} else {
		print "Invalid Unit\n";
	}

}

sub humidity { 
	my $self = shift;
	return $self->{humidity};
}

sub windspeed { 
	my $self = shift;
	my $unit = shift;
	if (! defined $unit) {
		return $self->{windspeed};
	} elsif ($unit eq "m/s") {
		return $self->{windspeed} / 3.6;
	} elsif ($unit eq "mph") {
		return $self->{windspeed} / 1.609344;
	} elsif ($unit eq "kn") {
		return $self->{windspeed} / 1.852;
	} else {
		print "Invalid Unit\n";
	}
}

sub rain { 
	my $self = shift;
	return $self->{rain};
}

sub rain_beginning { 
	my $self = shift;
	return $self->{rain_beginning};
}

sub raining_reported { 
	my $self = shift;
	return $self->{rain_beginning};
}

sub rain_increased { 
	my $self = shift;
	return $self->{rain_increased};
}

sub raining_calculated { 
	my $self = shift;
	return $self->{rain_increased};
}

sub raining { 
	my $self = shift;
	return $self->{raining};
}

sub new_values { 
	my $self = shift;
	return $self->{new_values};
}

sub beaufort {
	my $self = shift;
	return 0  if ($self->{windspeed} < 1.08);
	return 1  if (1.08 >= $self->{windspeed} && $self->{windspeed} < 5.76);
	return 2  if (5.76 >= $self->{windspeed} && $self->{windspeed} < 12.24);
	return 3  if (12.24 >= $self->{windspeed} && $self->{windspeed} < 19.8);
	return 4  if (19.8 >= $self->{windspeed} && $self->{windspeed} < 28.8);
	return 5  if (28.8 >= $self->{windspeed} && $self->{windspeed} < 38.88);
	return 6  if (38.88 >= $self->{windspeed} && $self->{windspeed} < 50.04);
	return 7  if (50.04 >= $self->{windspeed} && $self->{windspeed} < 61.92);
	return 8  if (61.92 >= $self->{windspeed} && $self->{windspeed} < 74.88);
	return 9  if (74.88 >= $self->{windspeed} && $self->{windspeed} < 88.2);
	return 10 if (88.2 >= $self->{windspeed} && $self->{windspeed} < 102.6);
	return 11 if (102.6 >= $self->{windspeed} && $self->{windspeed} < 117.72);
	return 12 if (117.72 >= $self->{windspeed});
}

sub beaufort_bezeichnung {
	my $self = shift;
	return "Windstille"         if ($self->{windspeed} < 1.08);
	return "leiser Zug"         if (1.08 >= $self->{windspeed} && $self->{windspeed} < 5.76);
	return "leichte Brise"      if (5.76 >= $self->{windspeed} && $self->{windspeed} < 12.24);
	return "schwache Brise"     if (12.24 >= $self->{windspeed} && $self->{windspeed} < 19.8);
	return "maessige Brise"     if (19.8 >= $self->{windspeed} && $self->{windspeed} < 28.8);
	return "frische Brise"      if (28.8 >= $self->{windspeed} && $self->{windspeed} < 38.88);
	return "starker Wind"       if (38.88 >= $self->{windspeed} && $self->{windspeed} < 50.04);
	return "steifer Wind"       if (50.04 >= $self->{windspeed} && $self->{windspeed} < 61.92);
	return "stuermischer Wind"  if (61.92 >= $self->{windspeed} && $self->{windspeed} < 74.88);
	return "Sturm"              if (74.88 >= $self->{windspeed} && $self->{windspeed} < 88.2);
	return "schwerer Sturm"     if (88.2 >= $self->{windspeed} && $self->{windspeed} < 102.6);
	return "orkanartiger Sturm" if (102.6 >= $self->{windspeed} && $self->{windspeed} < 117.72);
	return "Orkan"              if (117.72 >= $self->{windspeed});
}

sub beaufort_description {
	my $self = shift;
	return "Calm"            if ($self->{windspeed} < 1.08);
	return "Light air"       if (1.08 >= $self->{windspeed} && $self->{windspeed} < 5.76);
	return "Light breeze"    if (5.76 >= $self->{windspeed} && $self->{windspeed} < 12.24);
	return "Gentle breeze"   if (12.24 >= $self->{windspeed} && $self->{windspeed} < 19.8);
	return "Moderate breeze" if (19.8 >= $self->{windspeed} && $self->{windspeed} < 28.8);
	return "fresh breeze"    if (28.8 >= $self->{windspeed} && $self->{windspeed} < 38.88);
	return "Strong breeze"   if (38.88 >= $self->{windspeed} && $self->{windspeed} < 50.04);
	return "Near gale"       if (50.04 >= $self->{windspeed} && $self->{windspeed} < 61.92);
	return "Gale"            if (61.92 >= $self->{windspeed} && $self->{windspeed} < 74.88);
	return "Strong gale"     if (74.88 >= $self->{windspeed} && $self->{windspeed} < 88.2);
	return "Storm"           if (88.2 >= $self->{windspeed} && $self->{windspeed} < 102.6);
	return "Violent storm"   if (102.6 >= $self->{windspeed} && $self->{windspeed} < 117.72);
	return "Hurricane"       if (117.72 >= $self->{windspeed});
}

sub print_temperature   { 
	my $self = shift;
	my $sensor_nr = shift;
	if ($sensor_nr) {
		print "$self->{sensor}->{$sensor_nr}->{temperature}\n";
	} else {
		foreach (sort keys(%{$self->{sensor}})) {
			print $self->{sensor}->{$_}->{temperature} . "\n";
		}
	}
}

sub print { 
	my $self = shift;
	my ($type, $addr, $desc, $temp, $humi, $wind, $rain, $rainbegin);
	$type   = $self->{type};
	$addr   = $self->{address};
	$desc   = $self->{description};
	$temp   = $self->{temperature};
	$humi   = $self->{humidity};
	$wind   = $self->{windspeed};
	$rain   = $self->{rain};
	$rainbegin = $self->{rain_beginning};

	 
	select(STDOUT);		# make sure STDOUT is the selected file handle
	$^ = "SENSOR_TOP";	# top-of-page format for selected output (STDOUT)
	$~ = "SENSOR";		# report format for selected output (STDOUT)
	$= = 6;

	write;

format SENSOR_TOP =
-----------------------------------------------------------------------------------------------
| Sensor Type | Address | Description | Temperature | Humidity | Wind speed | Rain | Rain be- |
|             |         |             |     °C      |     %    |    km/h    |      | ginning  |
-----------------------------------------------------------------------------------------------
.

format SENSOR =
| @|||||||||| | @|||||| | @|||||||||| | @|||||||||| | @||||||| | @||||||||| | @||| | @||||||| |
$type, $addr, $desc, $temp, $humi, $wind, $rain, $rainbegin
-----------------------------------------------------------------------------------------------
.

}

1; 

=head2 NAME

IPWE - Query an IPWE 1 (IP-Wetterdatenempfaenger / IP weather data receiver)

=head2 VERSION

This document refers to version 0.41 of IPWE
released 26.04.2010

=head2 SYNOPSIS

 use IPWE;
 my $url = "http://192.168.1.100";

 my $ipwe = IPWE->new(url => $url);

 # print raw data for the whole IPWE (all sensors):
 $ipwe->print;

 # to get the sensor object for sensor 9:
 my $s = $ipwe->sensor(9);

 # print raw data for the current sensor object:
 $s->print;

 # let's get the humidity of the current sensor object:
 my $humidity = $s->humidity();

 # do something if it's raining:
 if ($s->raining) {
	# close_roof_window();	
 }

 # Retrieve new information from the IPWE hardware device:
 $ipwe->update;

 # print temperature for all sensors:
 foreach ($ipwe->sensor) {
 	print $_->temperature . "\n";
 }


 # for the lazy, instead of doing 
 my $s = $ipwe->sensor(9);
 my $temperature = $s->temperature();
 # you can also write:
 my $temperature = $ipwe->sensor(9)->temperature();


=head2 DESCRIPTION

The IPWE 1 (IP-Wetterdatenempfaenger) is an electronic appliance from ELV (L<http://www.elv.de>) which is able to receive weather information from up to nine local sensors. 

This module provides an object oriented API to access the IPWE device and retrieve its sensor values.

While using this module you will have to deal with 10 objects - one of type "IPWE" for the IPWE device itself and nine of type "IPWE::Sensor" (one for each sensor). 

=head2 CONSTRUCTOR METHODS



=over

=item $ipwe = IPWE->new(url => $url)

This method constructs a new IPWE object and returns it. During initialization the nine IPWE::Sensor objects will be created internally. There is no constructor method for IPWE:Sensor objects (of course there is a private method to construct them, otherwise we could not create IPWE::Sensor objects at all). A special method "$ipwe->sensor()" exists to return the sensor objects. See below for details.

=back

=head2 IPWE OBJECT METHODS



=over

=item $ipwe->update

This method retrieves the current information from the IPWE hardware device and stores it into nine IPWE::Sensor objects (one object for each sensor). Return values: 1 if update was successful, 0 if unsuccessful.

=back

=over

=item $s = $ipwe->sensor($nr)

=item @s = $ipwe->sensor

This method returns an IPWE::Sensor object. The first form returns the IPWE::Sensor object for the sensor identified by $nr. Possible values for $nr are digits from 1-9.

The second form without any arguments returns a list of all nine sensor objects.

=back

=over

=item $ipwe->timestamp

This method returns the timestamp of the last update.

=back

=over

=item $ipwe->url

This method just returns the url the IPWE-object was constructed with.

=back

=over

=item $ipwe->print

This prints out a nice table which shows all sensors and their values. Values shown are exactly what IPWE 1 reports via its web frond-end or telnet interface (adjust your terminal size, so that the output fits on your screen).

=back

=head2 IPWE::Sensor OBJECT METHODS

These are the methods to be used in order to get the weather information from a particular sensor:

=over

=item $s->type

Returns the type of the sensor to be queried.

=back

=over

=item $s->address

Returns the address of the sensor to be queried.

=back

=over

=item $s->description

Returns the sensor description if available.

=back

=over

=item $s->temperature()

=item $s->temperature("Fahrenheit")

=item $s->temperature("Kelvin")

=item $s->temperature("Rankine")

Returns the temperature if the sensor is able to report it. With no argument (default) the unit in °Celsius (°C).
Possible other units are: "Fahrenheit", "Kelvin", "Rankine"

=back

=over

=item $s->humidity

Returns the humidity if the sensor is able to report it.

=back

=over

=item $s->windspeed

=item $s->windspeed("m/s)

=item $s->windspeed("mph")

=item $s->windspeed("kn")

Returns the wind speed if the sensor is able to report it.
With no argument (default) the unit is km/h (kilometers per hour).
Possible other units are: "m/s" (meters per second),
"mph" (miles per hour), "kn" (knots)


=back

=over

=item $s->beaufort

Returns the wind speed in "Beaufort". 

=back

=over

=item $s->beaufort_bezeichnung

Returns the Beaufort wind speed in text form (german), like "frische Briese", "orkanartiger Sturm", etc. 

=back

=over

=item $s->beaufort_description

Returns the Beaufort wind speed in text form (english), like "Fresh breeze", "Violent storm", etc. 

=back

=over

=item $s->rain

Returns the amount of rain if the sensor is able to report it. The unit is "mm/24h" which equals to "l/sqm/24h" (liter per square meter in 24 hours).

=back

=over

=item $s->rain_beginning

Returns 1 (true) at rain beginning (the hash sign '#' preceeds the rain value in the web front-end of IPWE 1), otherwise the empty string "" will be returned.

=back

=over

=item $s->rain_increased

Returns 1 (true) if the amount of rain has increased in comparison to the previously measured value, otherwise the empty string "" will be returned.

=back

=over

=item $s->raining

Returns 1 (true) if either $s->rain_beginning or $s->rain_increased returns 1, otherwise the empty string "" will be returned.

=back

=over

=item $s->new_values

Returns 1 (true) if the measured values for the specified sensor have changed after execution of $ipwe->update, otherwise 0 will be returned. This can be useful e.g. if you only want to know whether new sensor values are available or not in order to control your program flow.

=back

=over

=item $s->print

This prints out a nice table which shows the information about the requested sensor. Values shown are exactly what IPWE 1 reports via its web frond-end or telnet interface (adjust your terminal size, so that the output fits on your screen).

=back

=head2 DEPRECATED IPWE::Sensor OBJECT METHODS

Those are deprecated methods which still exist for backward compatibility. Do not use them any longer, they might disappear in futur releases:

=over

=item $s->raining_reported

This method is deprecated. Use $s->rain_beginning instead which does exactly the same.

=back

=over

=item $s->raining_calculated

This method is deprecated. Use $s->rain_increased instead which does exactly the same.

=back




=head2 REQUIRED MODULES

LWP::UserAgent; HTML::TableExtract;


=head2 AUTHOR

Thomas Hoerndlein

=head2 COPYRIGHT

Copyright (c) 2009, Thomas Hoerndlein. All rights reserved, some wrongs reversed. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.
