Skip to content

Commit 708f8ab

Browse files
committed
Initial commit
0 parents  commit 708f8ab

35 files changed

+2697
-0
lines changed

LICENSE

+661
Large diffs are not rendered by default.

MANIFEST

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
MANIFEST
2+
MANIFEST.SKIP
3+
config.yml
4+
Makefile.PL
5+
lib/Wihlo.pm
6+
bin/app.pl
7+
t/002_index_route.t
8+
t/001_base.t
9+
environments/production.yml
10+
environments/development.yml
11+
views/index.tt
12+
views/layouts/main.tt
13+
public/404.html
14+
public/500.html
15+
public/dispatch.fcgi
16+
public/dispatch.cgi
17+
public/favicon.ico
18+
public/javascripts/jquery.js
19+
public/css/style.css
20+
public/css/error.css
21+
public/images/perldancer.jpg
22+
public/images/perldancer-bg.jpg

MANIFEST.SKIP

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
^\.git\/
2+
maint
3+
^tags$
4+
.last_cover_stats
5+
Makefile$
6+
^blib
7+
^pm_to_blib
8+
^.*.bak
9+
^.*.old
10+
^t.*sessions
11+
^cover_db
12+
^.*\.log
13+
^.*\.swp$
14+
MYMETA.*
15+
^.gitignore
16+
^.svn\/
17+
^Wihlo-

Makefile.PL

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use strict;
2+
use warnings;
3+
use ExtUtils::MakeMaker;
4+
5+
# Normalize version strings like 6.30_02 to 6.3002,
6+
# so that we can do numerical comparisons on it.
7+
my $eumm_version = $ExtUtils::MakeMaker::VERSION;
8+
$eumm_version =~ s/_//;
9+
10+
WriteMakefile(
11+
NAME => 'Wihlo',
12+
AUTHOR => q{YOUR NAME <[email protected]>},
13+
VERSION_FROM => 'lib/Wihlo.pm',
14+
ABSTRACT => 'YOUR APPLICATION ABSTRACT',
15+
($eumm_version >= 6.3001
16+
? ('LICENSE'=> 'perl')
17+
: ()),
18+
PL_FILES => {},
19+
PREREQ_PM => {
20+
'Test::More' => 0,
21+
'YAML' => 0,
22+
'Dancer2' => 0.11,
23+
},
24+
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
25+
clean => { FILES => 'Wihlo-*' },
26+
);

README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Wihlo
2+
=====
3+
4+
Wihlo was created out of my frustration with the lack of a simple, good quality Linux weather station application for the Davis Vantage Pro2. At the moment it is still very much a work in progress, and as such is not ready for general use. Please keep checking back here for updates, or if you want further information please email [email protected]
5+
6+
Features
7+
--------
8+
* Display configurable weather graphs in a web browser
9+
* Download data from a Vantage Pro2
10+
* Import data from wview
11+
12+
Dataloggers
13+
-----------
14+
This section lists any datalogger-specific notes. At the moment only the VantagePro2 is supported, but there are plans to add the Instromet dataloggers.
15+
16+
VantagePro2
17+
-----------
18+
19+
### Timezones
20+
Wihlo stores all times in the database as UTC. However, the VantagePro2 gives readings in local times. Converting them into UTC can be difficult, when the timezone information is not fully known.
21+
22+
* If possible, it is recommended that you set the VP2 datalogger for a named timezone, not an offset from GMT, and keep this consistent. This is because the time field contained in a VP2 record is a local time, with no information as to its timezone.
23+
* When downloading a set of records, Wihlo first asks the datalogger for its timezone. This information is used to convert the times of all the records received into a UTC time.
24+
* If the timezone is a named timezone (as recommended), then this is used to compensate for DST, if required.
25+
* If the timezone is simply an offset from GMT, then Wihlo queries the datalogger as to whether its internal clock is set to DST. If it is, then the time for each record is further adjusted to compensate. Therefore, when changing the local time on the VP2 to copmensate for DST, you should download all records before making the change, otherwise the time of the earlier records will be changed incorrectly for DST.
26+
* Because the VP2 uses the local time in a download record, sometimes a record will contain exactly the same time as a previous record (if the clocks have gone back by an hour). Wihlo tries to spot this, by monitoring for times in records received during such "go back" hours: the first record received will be assumed to be in DST; Wihlo will jump to standard time once a record is received that has a time earlier than one already received.
27+
* If importing from a wview database, it is assumed that the time in the wview database is already in UTC. It should be noted that wview calcuates UTC based on the local computer's timezone, not the timezone of the VP2 datalogger. Therefore, if the timezone on the computer that the VP2 is connected to is different to the timezone of the VP2 itself, then the times imported will probably be incorrect.
28+
29+
### Migrating from wview
30+
* The migration script assumes that the Wihlo database is empty, or that it does contain any readings for values being migrated. If existing readings do exist for the same times, then the migration will fail.
31+
* Wview stores all measurements as US units in the database. Whilst this works well for most measurements (as the VantagePro2 transmits most in US), it doesn't work well for metric rain collectors. Therefore, for metric rain collectors, Wihlo will convert the measurements back to metric (it queries the datalogger o migration for the rain collector size). This means that if you have a metric rain collector, some data is recorded in metric, and some in US units.
32+
* See above timezone section for notes about times migrated from wview

bin/app.pl

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env perl
2+
3+
use FindBin;
4+
use lib "$FindBin::Bin/../lib";
5+
6+
use Wihlo;
7+
Wihlo->dance;

bin/migrate-wview.pl

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/perl
2+
3+
use strict;
4+
use warnings;
5+
use Find::Lib '../lib' => 'Wihlo';
6+
use DateTime;
7+
use Dancer2 ':script';
8+
use Dancer2::Plugin::DBIC qw(schema resultset rset);
9+
10+
use DBI;
11+
use Device::VantagePro;
12+
13+
my %arg_hsh;
14+
$arg_hsh{baudrate} = config->{stations}->{vp}->{baudrate};
15+
$arg_hsh{port} = config->{stations}->{vp}->{device};
16+
use Data::Dumper;
17+
say STDERR Dumper %arg_hsh;
18+
19+
my $vp = new Device::VantagePro(\%arg_hsh);
20+
error "Failed to wake up device" && exit unless $vp->wake_up() == 1;
21+
22+
my $setup_bits = $vp->get_setup_bits;
23+
my $rain_collector_size = $setup_bits->{RainCollectorSize};
24+
# wview records rain in inches, even if the rain collector is metric.
25+
# Therefore, to prevent rounding errors, convert it back to metric
26+
# if the datalogger has a metric rain collector
27+
my $rainmult = $rain_collector_size == 0 ? 1 : 25.4;
28+
29+
my $dbh = DBI->connect("dbi:SQLite:dbname=/var/lib/wview/archive/wview-archive.sdb"
30+
,"","", { RaiseError => 1 },
31+
) or die $DBI::errstr;
32+
33+
my $sth = $dbh->prepare("SELECT * FROM archive");
34+
$sth->execute();
35+
36+
my $readings = $dbh->selectall_arrayref('SELECT * FROM archive', { Slice => {} });
37+
my @toadd;
38+
foreach my $r (@$readings)
39+
{
40+
my $new = {
41+
usunits => $r->{usUnits},
42+
usunitsrain => $rain_collector_size == 0 ? 1 : 0,
43+
datetime => DateTime->from_epoch(epoch=>$r->{dateTime}),
44+
intvl => $r->{interval},
45+
barometer => $r->{barometer},
46+
intemp => $r->{inTemp},
47+
outtemp => $r->{outTemp},
48+
# outtemphi => $r->{inTemp},
49+
# outtemplo => $r->{inTemp},
50+
inhumidity => $r->{inHumidity},
51+
outhumidity => $r->{outHumidity},
52+
windspeed => $r->{windSpeed},
53+
winddir => $r->{windDir},
54+
windgust => $r->{windGust},
55+
windgustdir => $r->{windGustDir},
56+
rainrate => $r->{rainRate} * $rainmult,
57+
rain => $r->{rain} * $rainmult,
58+
et => $r->{ET},
59+
radiation => $r->{radiation},
60+
# radiationmax => $r->{},
61+
uv => $r->{UV},
62+
# uvmax => $r->{},
63+
extratemp1 => $r->{extraTemp1},
64+
extratemp2 => $r->{extraTemp2},
65+
extratemp3 => $r->{extraTemp3},
66+
soiltemp1 => $r->{soilTemp1},
67+
soiltemp2 => $r->{soilTemp2},
68+
soiltemp3 => $r->{soilTemp3},
69+
soiltemp4 => $r->{soilTemp4},
70+
leaftemp1 => $r->{leafTemp1},
71+
leaftemp2 => $r->{leafTemp2},
72+
extrahumid1 => $r->{extraHumid1},
73+
extrahumid2 => $r->{extraHumid2},
74+
soilmoist1 => $r->{soilMoist1},
75+
soilmoist2 => $r->{soilMoist2},
76+
soilmoist3 => $r->{soilMoist3},
77+
soilmoist4 => $r->{soilMoist4},
78+
leafwet1 => $r->{leafWet1},
79+
leafwet2 => $r->{leafWet2},
80+
};
81+
push @toadd, $new;
82+
}
83+
84+
my $reading_rs = rset('Reading');
85+
$reading_rs->populate(\@toadd);
86+

bin/read.pl

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env perl
2+
3+
=pod
4+
Wihlo - Web-based weather logging and display software
5+
Copyright (C) 2014 A Beverley
6+
7+
This program is free software: you can redistribute it and/or modify
8+
it under the terms of the GNU Affero General Public License as
9+
published by the Free Software Foundation, either version 3 of the
10+
License, or (at your option) any later version.
11+
12+
This program is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
GNU Affero General Public License for more details.
16+
17+
You should have received a copy of the GNU Affero General Public License
18+
along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
=cut
20+
21+
use FindBin;
22+
use lib "$FindBin::Bin/../lib";
23+
24+
use Dancer2 ':script';
25+
use Dancer2::Plugin::DBIC qw(schema resultset rset);
26+
use Wihlo;
27+
use Device::VantagePro;
28+
use DateTime;
29+
use DateTime::Format::DBI;
30+
31+
my %arg_hsh;
32+
$arg_hsh{baudrate} = config->{stations}->{vp}->{baudrate};
33+
$arg_hsh{port} = config->{stations}->{vp}->{device};
34+
35+
my $vp = new Device::VantagePro(\%arg_hsh);
36+
error "Failed to wake up device" && exit unless $vp->wake_up() == 1;
37+
38+
my $timezone = $vp->get_timezone;
39+
my $arc_period = $vp->get_archive_period;
40+
my $setup_bits = $vp->get_setup_bits;
41+
my $rain_collector_size = $setup_bits->{RainCollectorSize};
42+
my $raindiv = $rain_collector_size == 2 ? 10
43+
: $rain_collector_size == 1 ? 5
44+
: 100;
45+
46+
my $data;
47+
if (my $dt = rset('Station')->find(1)->lastdata) # The last item of data retrieved
48+
{
49+
my ($dstamp, $tstamp) = split /,/, $dt;
50+
$data = $vp->do_dmpaft($dstamp,$tstamp);
51+
} else
52+
{
53+
# Otherwise get everything
54+
$data = $vp->do_dmpaft;
55+
}
56+
57+
unless ( @{$data} ) { info "No records retrieved" && exit };
58+
59+
my $dtf = schema->storage->datetime_parser; # XXXX Is this expensive? Move inside loop?
60+
my $changing = 0; my $lastdata;
61+
foreach my $d ( @{$data} )
62+
{
63+
# Don't use unixtime parameter, as it depends on
64+
# timezone of PC, not datalogger
65+
my $dt = DateTime->new(
66+
year => $d->{year},
67+
month => $d->{month},
68+
day => $d->{day},
69+
hour => $d->{hour},
70+
minute => $d->{min},
71+
time_zone => $timezone,
72+
);
73+
74+
# Monitor for a DST "ambiguous" time, when the clocks change.
75+
# For an hour when the clocks go back, the winter time minus
76+
# an hour equals the DST. See DateTime manual.
77+
if ($dt->clone->subtract( hours => 1 )->hms eq $dt->hms)
78+
{
79+
# Uh-oh, clocks are about to go back or have just gone back
80+
$changing = 1;
81+
82+
# If the time stamp of the current reading is greater than
83+
# the maximum received so far, then we're probably still in DST.
84+
# DateTime defaults to local standard time, so take an hour
85+
# off it to adjust to DST.
86+
my $maxdst = rset('Station')->find(1)->maxdst || 0;
87+
if ( $maxdst < $d->{time_stamp} )
88+
{
89+
$dt->subtract( hours => 1);
90+
# Update status
91+
rset('Station')->find(1)->update({ maxdst => $d->{time_stamp} });
92+
}
93+
94+
} elsif ($changing == 1)
95+
{
96+
# We've just finished a clock change. Reset status for next time.
97+
rset('Station')->find(1)->update({ maxdst => undef });
98+
}
99+
100+
# Store time as UTC in database
101+
$dt->set_time_zone( 'UTC' );
102+
103+
my $row =
104+
{
105+
# Return undef in event of dashed value
106+
usunits => 1,
107+
usunitsrain => $rain_collector_size == 0 ? 1 : 0,
108+
intvl => $arc_period,
109+
et => $d->{ET} == 0 ? undef : $d->{ET},
110+
rainrate => $d->{Rain_Rate_Clicks} / $raindiv,
111+
rain => $d->{Rain_Clicks} / $raindiv,
112+
intemp => $d->{Air_Temp_Inside} == 32767 ? undef : $d->{Air_Temp_Inside},
113+
outtemplo => $d->{Air_Temp_Lo} == 32767 ? undef : $d->{Air_Temp_Lo},
114+
outhumidity => $d->{Relative_Humidity} == 255 ? undef : $d->{Relative_Humidity},
115+
outtemp => $d->{Air_Temp} == 32767 ? undef : $d->{Air_Temp},
116+
radiationmax => $d->{Solar_Max} == 0 ? undef : $d->{Solar_Max},
117+
inhumidity => $d->{Relative_Humidity_Inside} == 255 ? undef : $d->{Relative_Humidity_Inside},
118+
windspeed => $d->{Wind_Speed} == 255 ? undef : $d->{Wind_Speed},
119+
barometer => $d->{Barometric_Press} == 0 ? undef : $d->{Barometric_Press},
120+
outtemphi => $d->{Air_Temp_Hi} == -32768 ? undef : $d->{Air_Temp_Hi},
121+
uv => $d->{UV} == 255 ? undef : $d->{UV},
122+
uvmax => $d->{UV_Max} == 0 ? undef : $d->{UV_Max},
123+
datetime => $dt,
124+
windgust => $d->{Wind_Gust_Max} == 0 ? undef : $d->{Wind_Gust_Max},
125+
windgustdir => $d->{Wind_Dir_Max} == 255 ? undef : $d->{Wind_Dir_Max} * 22.5,
126+
radiation => $d->{Solar} == 32767 ? undef : $d->{Solar},
127+
winddir => $d->{Wind_Dir} == 255 ? undef : $d->{Wind_Dir} * 22.5
128+
};
129+
if (rset('Reading')->search({ datetime => $dtf->format_datetime($row->{datetime}) })->count)
130+
{
131+
warning "Reading already exists for DTG $row->{datetime}. Not inserting.";
132+
} else
133+
{
134+
my $id = rset('Reading')->create($row);
135+
debug "Inserted record ID ".$id->id;
136+
}
137+
$lastdata = "$d->{date_stamp},$d->{time_stamp}"; # if $d->{unixtime} > $lastunix;
138+
}
139+
140+
# Last item of data revieved, so we can start where we left off
141+
rset('Station')->find(1)->update({ lastdata => $lastdata });
142+

config.yml.example

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# This is the main configuration file of your Dancer2 app
2+
# env-related settings should go to environments/$env.yml
3+
# all the settings in this file will be loaded at Dancer's startup.
4+
5+
# Your application's name
6+
appname: "Wihlo"
7+
8+
# The default layout to use for your application (located in
9+
# views/layouts/main.tt)
10+
layout: "main"
11+
12+
# when the charset is set to UTF-8 Dancer2 will handle for you
13+
# all the magic of encoding and decoding. You should not care
14+
# about unicode within your app when this setting is set (recommended).
15+
charset: "UTF-8"
16+
17+
# template engine
18+
# simple: default and very basic template engine
19+
# template_toolkit: TT
20+
21+
template: "simple"
22+
23+
# template: "template_toolkit"
24+
# engines:
25+
# template:
26+
# template_toolkit:
27+
# start_tag: '<%'
28+
# end_tag: '%>'
29+
30+
plugins:
31+
DBIC:
32+
default:
33+
## Use this for a connection to a local database
34+
# dsn: dbi:mysql:database=wihlo;host=localhost
35+
## Use this for an SSL connection to a remote database
36+
# dsn: dbi:mysql:database=wihlo;host=dev.simplelists.com;mysql_ssl=1;mysql_ssl_client_key=/etc/mysql/newcerts/client-key.pem;mysql_ssl_client_cert=/etc/mysql/newcerts/client-cert.pem;mysql_ssl_ca_file=/etc/mysql/newcerts/ca-cert.pem;
37+
schema_class: Wihlo::Schema
38+
user: wihlo
39+
pass: password
40+
options:
41+
RaiseError: 1
42+
PrintError: 1
43+
44+
stations:
45+
vp:
46+
baudrate: 19200
47+
device: /dev/ttyUSB0

0 commit comments

Comments
 (0)