mirror of
https://github.com/wezm/wezm.net.git
synced 2024-12-19 02:39:54 +00:00
Merge branch 'weather'
This commit is contained in:
commit
cd35c78029
19 changed files with 325 additions and 5 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@ output/*/*/*/*/*.html
|
||||||
output/fonts/*
|
output/fonts/*
|
||||||
tmp/*
|
tmp/*
|
||||||
.*.swp
|
.*.swp
|
||||||
|
output/weather.json
|
||||||
|
|
5
Rules
5
Rules
|
@ -5,6 +5,11 @@ compile '/' do
|
||||||
filter :rubypants
|
filter :rubypants
|
||||||
end
|
end
|
||||||
|
|
||||||
|
compile '/weather/' do
|
||||||
|
layout 'weather'
|
||||||
|
filter :erb
|
||||||
|
end
|
||||||
|
|
||||||
compile %r{/(?:technical|personal|)articles/(page/|)} do
|
compile %r{/(?:technical|personal|)articles/(page/|)} do
|
||||||
filter :erb
|
filter :erb
|
||||||
layout 'articles'
|
layout 'articles'
|
||||||
|
|
|
@ -14,3 +14,25 @@ written in Lua. The Lua one is the one currently in use.
|
||||||
|
|
||||||
[flickr]: http://www.flickr.com/photos/wezm/
|
[flickr]: http://www.flickr.com/photos/wezm/
|
||||||
[github]: http://github.com/wezm
|
[github]: http://github.com/wezm
|
||||||
|
|
||||||
|
Weather Station
|
||||||
|
---------------
|
||||||
|
|
||||||
|
I have a weather station at my home for keeping track of the local conditions.
|
||||||
|
I added support for [SQLite][sqlite] logging to [my fork][open2300fork] of the
|
||||||
|
[Open2300][open2300] project. The weather station is connected to our TV
|
||||||
|
computer, which is a Mac mini. Periodically it logs the current conditions,
|
||||||
|
uploads them to my server and invokes a [lua program][weather-tools] to generate
|
||||||
|
a [JSON][json] file, which is used on the [Weather page][weather].
|
||||||
|
|
||||||
|
[sqlite]: http://www.sqlite.org/
|
||||||
|
[open2300]: http://www.lavrsen.dk/foswiki/bin/view/Open2300/WebHome
|
||||||
|
[open2300fork]: http://github.com/wezm/open2300
|
||||||
|
[weather]: http://weather.wezm.net/
|
||||||
|
[json]: http://www.json.org/
|
||||||
|
[weather-tools]: http://github.com/wezm/weather-tools
|
||||||
|
|
||||||
|
See the following posts for more information on the weather station:
|
||||||
|
|
||||||
|
* [Weather Station Install](/personal/2010/09/weather-station/)
|
||||||
|
* [Weather Station Software Update](/technical/2010/09/weather-station-software/)
|
||||||
|
|
|
@ -163,7 +163,7 @@ body > header
|
||||||
margin-right: 40%
|
margin-right: 40%
|
||||||
|
|
||||||
|
|
||||||
body.articles #content
|
body.articles #content, body.weather #content
|
||||||
min-width: 0
|
min-width: 0
|
||||||
margin-right: 0
|
margin-right: 0
|
||||||
|
|
||||||
|
@ -417,3 +417,34 @@ table
|
||||||
.vcard .photo
|
.vcard .photo
|
||||||
margin: 0 1em 1em 0
|
margin: 0 1em 1em 0
|
||||||
|
|
||||||
|
|
||||||
|
// Weather
|
||||||
|
body.weather
|
||||||
|
|
||||||
|
#current > .temperature
|
||||||
|
//margin-left: 1em
|
||||||
|
|
||||||
|
.temperature
|
||||||
|
font-family: "Helvetica Neue", Helvetica, "Liberation Sans", "Bitstream Vera Sans", Tahoma, Geneva, Arial, sans-serif
|
||||||
|
font-weight: 200
|
||||||
|
|
||||||
|
.current
|
||||||
|
font-size: 36px
|
||||||
|
|
||||||
|
figure.forecast
|
||||||
|
margin: 0 1em 1em 0
|
||||||
|
width: 48px
|
||||||
|
float: left
|
||||||
|
|
||||||
|
.chart
|
||||||
|
clear: both
|
||||||
|
|
||||||
|
.canvas
|
||||||
|
width: 100%
|
||||||
|
height: 320px
|
||||||
|
|
||||||
|
small
|
||||||
|
display: block
|
||||||
|
font-size: smaller
|
||||||
|
margin-top: 1em
|
||||||
|
color: #999
|
||||||
|
|
37
content/technical/2010/09/weather-station-software.html
Normal file
37
content/technical/2010/09/weather-station-software.html
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
Two weeks ago when I [installed my weather station][install] I setup the wview
|
||||||
|
software to log the current conditions and generate and upload
|
||||||
|
HTML and graphs to [http://weather.wezm.net/][weather]. wview seemed like the
|
||||||
|
perfect tool for the job but unfortunately it proved unreliable, even with the
|
||||||
|
built in process monitoring. After a few hours of running fine it appeared the
|
||||||
|
HTML generation process would hang, preventing any further updates to the
|
||||||
|
website.
|
||||||
|
|
||||||
|
[install]: /personal/2010/09/weather-station/
|
||||||
|
[weather]: http://weather.wezm.net/
|
||||||
|
|
||||||
|
wview has a lot of functionality and thus a lot of code, which turned me
|
||||||
|
off trying to track down the bug. I looked around for
|
||||||
|
alternatives and found [Open2300][open2300], which is a core collection of
|
||||||
|
functions for communicating with a LaCrosse WS-23xx weather station and a set
|
||||||
|
of tools. wview logged its observations to an SQLite database, which seemed
|
||||||
|
appropriate for this application. There were tools in Open2300 to log current
|
||||||
|
conditions to MySQL and PostgreSQL databases but not SQLite. So last weekend
|
||||||
|
I spend some of the afternoon learning enough of the SQLite C API to add
|
||||||
|
such a tool. The result is in my
|
||||||
|
[git mirror of the Open2300 SVN repo][open2300git]. Once the SQLite tool is
|
||||||
|
tidied up a bit more I'll submit it upstream.
|
||||||
|
|
||||||
|
[open2300]: http://www.lavrsen.dk/foswiki/bin/view/Open2300/WebHome
|
||||||
|
[open2300git]: http://github.com/wezm/open2300
|
||||||
|
|
||||||
|
Now I had a database of observations I needed to replace the HTML pages
|
||||||
|
that wview was previously generated. To do this I wrote a small
|
||||||
|
[Lua tool][weather-tools]
|
||||||
|
to query the database and output the results to a [JSON][json] file. The
|
||||||
|
JSON is used on the new [weather page][weather], which is largely populated
|
||||||
|
by Javascript and uses the [flot charting library][flot] to graph the
|
||||||
|
temperature history.
|
||||||
|
|
||||||
|
[json]: http://www.json.org/
|
||||||
|
[flot]: http://code.google.com/p/flot/
|
||||||
|
[weather-tools]: http://github.com/wezm/weather-tools
|
13
content/technical/2010/09/weather-station-software.yaml
Normal file
13
content/technical/2010/09/weather-station-software.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
title: Weather Station Software
|
||||||
|
extra: After trouble with the wview weather station software I built my own solution to log and publish weather conditions.
|
||||||
|
kind: article
|
||||||
|
section: technical
|
||||||
|
created_at: 2010-09-26 17:01:00
|
||||||
|
keywords:
|
||||||
|
- weather
|
||||||
|
- station
|
||||||
|
- wview
|
||||||
|
- open2300
|
||||||
|
- software
|
||||||
|
short_url: http://bit.ly/9Sf8Lu
|
15
content/weather.html
Normal file
15
content/weather.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<h2>Temperature</h2>
|
||||||
|
|
||||||
|
<div class="loading"><img src="/images/spinner.gif" width="64" height="64" alt="Loading" /> Loading</div>
|
||||||
|
|
||||||
|
<div class="temperature chart">
|
||||||
|
<!--><button id="year">Year</button>
|
||||||
|
<button id="month">Month</button>
|
||||||
|
<button id="day">Day</button>-->
|
||||||
|
<div class="canvas"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <h2>Rainfall</h2>
|
||||||
|
<div class="rainfall chart"></div> -->
|
||||||
|
|
||||||
|
<small>Forecast icons by <a href="http://lavana.deviantart.com/art/Flat-Weather-Icons-32021664">LavAna</a></small>
|
2
content/weather.yaml
Normal file
2
content/weather.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
---
|
||||||
|
title: Macedon Weather
|
18
layouts/weather.html
Normal file
18
layouts/weather.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<%= render '_head' %>
|
||||||
|
<script src="/js/jquery.flot.min.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
<!--[if IE lt 9]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]-->
|
||||||
|
<script src="/js/mojo.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
<script src="/js/weather.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
</head>
|
||||||
|
<body class="weather">
|
||||||
|
<%= render '_header' %>
|
||||||
|
<div id="content">
|
||||||
|
<h1><a href="<%= @item.reps.first.path %>"><%=h @item[:title] %></a></h1>
|
||||||
|
<%= yield %>
|
||||||
|
</div>
|
||||||
|
<%= render '_footer' %>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
output/images/spinner.gif
Normal file
BIN
output/images/spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
output/images/weather/Cloudy.png
Normal file
BIN
output/images/weather/Cloudy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3 KiB |
BIN
output/images/weather/Rainy.png
Normal file
BIN
output/images/weather/Rainy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
output/images/weather/Sunny.png
Normal file
BIN
output/images/weather/Sunny.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
1
output/js/dygraph-combined.js
Normal file
1
output/js/dygraph-combined.js
Normal file
File diff suppressed because one or more lines are too long
1
output/js/excanvas.min.js
vendored
Normal file
1
output/js/excanvas.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
output/js/jquery.flot.min.js
vendored
Normal file
1
output/js/jquery.flot.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
// --- Version
|
// --- Version
|
||||||
|
|
||||||
version: '0.2.0',
|
version: '0.3.0',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape HTML.
|
* Escape HTML.
|
||||||
|
@ -33,8 +33,11 @@
|
||||||
* @api public
|
* @api public
|
||||||
*/
|
*/
|
||||||
|
|
||||||
normalize: function(object) {
|
normalize: function(object, property) {
|
||||||
return typeof object == 'function' ? object() : object
|
if(property === undefined)
|
||||||
|
return typeof object == 'function' ? object() : object
|
||||||
|
else
|
||||||
|
return typeof object[property] == 'function' ? object[property]() : object[property]
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
154
output/js/weather.js
Normal file
154
output/js/weather.js
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
function render_current(o) {
|
||||||
|
return '<div id="current">\n\
|
||||||
|
<figure class="forecast">\n\
|
||||||
|
<img src="/images/weather/' + (Mojo.escape(Mojo.normalize(o, "forecast"))) + '.png" width="48" height="48" alt="' + (Mojo.escape(Mojo.normalize(o, "forecast"))) + '" />\n\
|
||||||
|
<figcaption>Forecast</figcaption>\n\
|
||||||
|
</figure>\n\
|
||||||
|
\n\
|
||||||
|
<div class="temperature">\n\
|
||||||
|
<div class="current temperature">' + (Mojo.escape(Mojo.normalize(o, "temperature"))) + '°C</div>\n\
|
||||||
|
<div class="minmax">\n\
|
||||||
|
Minimum <span class="min temperature">' + (Mojo.escape(Mojo.normalize(o, "min_temp"))) + '°C</span> at\n\
|
||||||
|
<time datetime="' + (Mojo.escape(Mojo.normalize(o, "min_datetime"))) + '">' + (Mojo.escape(Mojo.normalize(o, "minDateString"))) + '</time>\n\
|
||||||
|
Maximum <span class="max temperature">' + (Mojo.escape(Mojo.normalize(o, "max_temp"))) + '°C</span> at\n\
|
||||||
|
<time datetime="' + (Mojo.escape(Mojo.normalize(o, "max_datetime"))) + '">' + (Mojo.escape(Mojo.normalize(o, "maxDateString"))) + '</time>\n\
|
||||||
|
</div>\n\
|
||||||
|
</div>\n\
|
||||||
|
</div>';
|
||||||
|
};
|
||||||
|
|
||||||
|
// {"wind_angle":270,"rel_humidity_in":51,"rain_1h":0,"temperature_out":9.9,"forecast":"Sunny","rain_24h":0,"dewpoint":7.11,"wind_chill":9.9,"temperature_in":20.8,"rel_humidity_out":83,"tendency":"Rising","wind_speed":0,"rel_pressure":970.7,"rain_total":1.55,"datetime":"2010-09-20 11:30:13","wind_direction":"W"}
|
||||||
|
|
||||||
|
// forecaset is Rainy, Cloudy or Sunny
|
||||||
|
|
||||||
|
// Return a string of a number padded with leading zeros
|
||||||
|
function padNumber(n, count) {
|
||||||
|
if(count === undefined) count = 2;
|
||||||
|
|
||||||
|
var string = n.toString();
|
||||||
|
var padding = [];
|
||||||
|
for(var i = count - string.length; i > 0; i--) {
|
||||||
|
padding.push('0');
|
||||||
|
}
|
||||||
|
return padding.join('') + string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isoString(date) {
|
||||||
|
// YYYY-MM-DDTHH:MM:SS
|
||||||
|
if(!date) return '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
date.getUTCFullYear(),
|
||||||
|
padNumber(date.getUTCMonth() + 1),
|
||||||
|
padNumber(date.getUTCDate())
|
||||||
|
].join('-'),
|
||||||
|
'T',
|
||||||
|
[
|
||||||
|
padNumber(date.getUTCHours()),
|
||||||
|
padNumber(date.getUTCMinutes()),
|
||||||
|
padNumber(date.getUTCSeconds())
|
||||||
|
].join(':')
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function datetimeString(date) {
|
||||||
|
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||||
|
return [
|
||||||
|
date.getUTCDate(),
|
||||||
|
months[date.getUTCMonth()],
|
||||||
|
timeString(date)
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeString(date) {
|
||||||
|
return [
|
||||||
|
padNumber(date.getUTCHours()),
|
||||||
|
padNumber(date.getUTCMinutes())
|
||||||
|
].join(':')
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery(function() {
|
||||||
|
jQuery.getJSON("/weather.json", function(data, status) {
|
||||||
|
var count = data.history.length;
|
||||||
|
for(var i = 0; i < count; i++) {
|
||||||
|
data.history[i][0] = new Date(data.history[i][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the current conditions
|
||||||
|
var current = {
|
||||||
|
temperature: data.current.temperature_out,
|
||||||
|
timestamp: function() {
|
||||||
|
var d = new Date(data.current.timestamp);
|
||||||
|
return datetimeString(d);
|
||||||
|
},
|
||||||
|
min_temp: data.current.min.temperature,
|
||||||
|
min_date: new Date(data.current.min.timestamp),
|
||||||
|
min_datetime: function() {
|
||||||
|
return isoString(this.min_date)
|
||||||
|
},
|
||||||
|
minDateString: function() {
|
||||||
|
return timeString(current.min_date)
|
||||||
|
},
|
||||||
|
max_temp: data.current.max.temperature,
|
||||||
|
max_date: new Date(data.current.max.timestamp),
|
||||||
|
max_datetime: function() {
|
||||||
|
return isoString(this.max_date)
|
||||||
|
},
|
||||||
|
maxDateString: function() {
|
||||||
|
return timeString(current.max_date)
|
||||||
|
},
|
||||||
|
forecast: data.current.forecast
|
||||||
|
};
|
||||||
|
|
||||||
|
var current_div = render_current(current);
|
||||||
|
$('.loading').replaceWith(current_div)
|
||||||
|
|
||||||
|
// Populate the charts
|
||||||
|
$('.temperature.chart .canvas').each(function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
xaxis: {
|
||||||
|
mode: "time"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
jQuery.plot(self, data.history, options);
|
||||||
|
|
||||||
|
/*
|
||||||
|
$("#year").click(function () {
|
||||||
|
$.plot(self, data.history, {
|
||||||
|
xaxis: {
|
||||||
|
mode: "time",
|
||||||
|
minTickSize: [1, "month"] //,
|
||||||
|
// min: (new Date("1990/01/01")).getTime(),
|
||||||
|
// max: (new Date()).getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#month").click(function () {
|
||||||
|
$.plot(self, data.history, {
|
||||||
|
xaxis: {
|
||||||
|
mode: "time",
|
||||||
|
min: (new Date("2010/08/21")).getTime(),
|
||||||
|
max: (new Date()).getTime()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#day").click(function () {
|
||||||
|
$.plot(self, data.history, {
|
||||||
|
xaxis: {
|
||||||
|
mode: "time",
|
||||||
|
min: (new Date("2010/09/21 00:00")).getTime(),
|
||||||
|
max: (new Date()).getTime()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
16
templates/current_weather.html
Normal file
16
templates/current_weather.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<div id="current">
|
||||||
|
<figure class="forecast">
|
||||||
|
<img src="/images/weather/{{forecast}}.png" width="48" height="48" alt="{{forecast}}" />
|
||||||
|
<figcaption>Forecast</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<div class="temperature">
|
||||||
|
<div class="current temperature">{{temperature}}°C</div>
|
||||||
|
<div class="minmax">
|
||||||
|
Minimum <span class="min temperature">{{min_temp}}°C</span> at
|
||||||
|
<time datetime="{{min_datetime}}">{{minDateString}}</time>
|
||||||
|
Maximum <span class="max temperature">{{max_temp}}°C</span> at
|
||||||
|
<time datetime="{{max_datetime}}">{{maxDateString}}</time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in a new issue