weatherstation: BeagleBone Weather Cape demo app.

The BeagleBone Weather cape enhances the BeagleBone's capabilities
by providing environment sensors (temperature, humidity, pressure,
and ambient light level).

The weatherstation demo is a port of the bonescript weatherstation
to Minix. It provides a nice visual display of the sensor data in
a web browser.

The code is installed to /usr/share/beaglebone/weather on 'earm'
and an embedded web server is started at boot time on port 80
when the cape is attached. Further details are provided in the
README.txt file.

Change-Id: I1596a2b66b213762ace26c0c750c8154c76b5c6e
This commit is contained in:
Thomas Cort 2013-08-25 22:07:42 -04:00 committed by Gerrit Code Review
parent 1fbbfa06c2
commit 60a61dffae
16 changed files with 1088 additions and 1 deletions

View file

@ -119,5 +119,16 @@
./usr/sbin/tps65217 minix-sys
./usr/sbin/tps65950 minix-sys
./usr/sbin/tsl2550 minix-sys
./usr/share/beaglebone minix-sys
./usr/share/beaglebone/weather minix-sys
./usr/share/beaglebone/weather/LICENSE minix-sys
./usr/share/beaglebone/weather/README.txt minix-sys
./usr/share/beaglebone/weather/index.html minix-sys
./usr/share/beaglebone/weather/jquery.js minix-sys
./usr/share/beaglebone/weather/processing.js minix-sys
./usr/share/beaglebone/weather/spin.js minix-sys
./usr/share/beaglebone/weather/style.css minix-sys
./usr/share/beaglebone/weather/weatherstation.js minix-sys
./usr/share/beaglebone/weather/weatherstation.lua minix-sys
./usr/tests/minix-posix/mod minix-sys
./usr/tests/minix-posix/test63 minix-sys

View file

@ -18,7 +18,8 @@ EXTRA_DIST_FILES+= ${.CURDIR}/Minix.gcccmds
# XXX these are only used by compat currently, but they could be used
# by something else; this may need to be fixed properly in the future.
.if ${MKCOMPAT} != "no"
# TAC these are used by Minix for arch-specific content.
.if ${MKCOMPAT} != "no" || defined(__MINIX)
.if exists(NetBSD.dist.${MACHINE_ARCH})
EXTRA_DIST_FILES+= ${.CURDIR}/NetBSD.dist.${MACHINE_ARCH}
.endif

View file

@ -0,0 +1,4 @@
#
./usr/share/beaglebone
./usr/share/beaglebone/weather

View file

@ -17,3 +17,4 @@ test -e /dev/bmp085b3s77 || (cd /dev && MAKEDEV bmp085b3s77)
/bin/service up /usr/sbin/bmp085 -dev /dev/bmp085b3s77 \
-label bmp085.3.77 -args 'bus=3 address=0x77' && echo -n " bmp085"
daemonize tcpd http /usr/share/beaglebone/weather/weatherstation.lua

View file

@ -9,6 +9,13 @@
make(clean) || make(cleandir) || make(distclean) || make(obj)
SUBDIR= misc mk \
terminfo zoneinfo
.if defined(__MINIX)
.if ${MACHINE_ARCH} == "earm"
SUBDIR+= beaglebone
.endif # ${MACHINE_ARCH} == "earm"
.endif # defined(__MINIX)
.if ${MKNLS} != "no"
SUBDIR+=i18n locale nls
.endif

View file

@ -0,0 +1,4 @@
SUBDIR+= weather
.include <bsd.subdir.mk>

View file

@ -0,0 +1,27 @@
Original code is based on bonescript by Jason Kridner <jdk@ti.com>.
To see the original, checkout the bonescript repository on github
The license for the original code is below, between the dashes. Minix changes
Copyright (c) 2013 Thomas Cort <tcort@minix3.org> (same license applies).
spin.js, jQuery and Processing.js are under MIT license.
-------------------------------------------------------------------------------
Copyright (c) 2011 Jason Kridner <jdk@ti.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-------------------------------------------------------------------------------

View file

@ -0,0 +1,15 @@
# BeagleBone Weather Cape Demo
.include <bsd.own.mk>
NOOBJ= # defined
FILES= LICENSE README.txt jquery.js style.css weatherstation.js \
index.html processing.js spin.js weatherstation.lua
# weatherstation.lua is an executable script run under tcpd and needs BINMODE
FILESMODE_weatherstation.lua=${BINMODE}
FILESDIR=/usr/share/beaglebone/weather
.include <bsd.prog.mk>

View file

@ -0,0 +1,53 @@
_ _ _ _ _
__ _____ __ _| |_| |__ ___ _ __ ___| |_ __ _| |_(_) ___ _ __
\ \ /\ / / _ \/ _` | __| '_ \ / _ \ '__/ __| __/ _` | __| |/ _ \| '_ \
\ V V / __/ (_| | |_| | | | __/ | \__ \ || (_| | |_| | (_) | | | |
\_/\_/ \___|\__,_|\__|_| |_|\___|_| |___/\__\__,_|\__|_|\___/|_| |_|
Minix Demo for the BeagleBone Weather Cape
Based on https://github.com/jadonk/bonescript
Overview
--------
This is a demo of the BeagleBone Weather Cape. It's a fork of the original
bonescript weatherstation demo, modified to work on the Minix operating
system. The main changes are conversion from nodejs to JSON and mbar to hPa.
Requirements
------------
Hardware:
BeagleBone
BeagleBone Weather Cape
Network (doesn't have to be connected to the Internet)
Software:
Web Browser with HTML5 support and JavaScript enabled.
Setup
-----
This demo is meant to work 'out of the box'. It requires the BeagleBone be
connected to the network and have the BeagleBone Weather cape attached at
boot.
1) Attach the BeagleBone Weather Cape.
2) Connect a network cable to the BeagleBone.
3) Power on the BeagleBone.
4) Configure the network by running these commands.
# netconf
# reboot
5) Enter the BeagleBone's IP address into your web browser.
Usage
-----
This demo is a web application. You just need to point your web browser at
the BeagleBone's IP address to view a nice display of the sensor values. You
can get your BeagleBone's IP address by running `ifconfig` on the BeagleBone.
If the address is 192.168.12.138, you'd enter http://192.168.12.138/ into your
web browser.

View file

@ -0,0 +1,97 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Weather Station - Powered by Minix and BeagleBone</title>
<link rel="stylesheet" type="text/css" media="all" href="style.css"/>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="processing.js"></script>
<script type="text/javascript" src="spin.js"></script>
<script type="text/javascript">
// Refresh weather data every 3 seconds.
var weather_fetch_interval = 3 * 1000;
// Location of the weather data.
var weather_json_url = 'weather.json';
// Loading animation while the initial fetch is being performed.
var spinner = new Spinner().spin();
// Callback for fetching weather reports.
function weather_cb_fetch() {
var now;
var url;
// Make the URL of every request unique to help ensure
// that the browser doesn't cache the JSON data.
now = new Date();
url = weather_json_url + '?timestamp=' + now.getTime();
$.getJSON(url, weather_cb_process);
}
// Callback for processing weather reports.
function weather_cb_process(data) {
set_lux(data.illuminance);
set_temp(data.temperature);
set_humidity(data.humidity);
set_pressure(data.pressure/100); // Pa to hPa
// hide the loading message once everything is loaded
$("#loading").fadeOut("slow", function(){
spinner.stop();
});
// weather station is initially hidden
// fade in after first paint.
$("#weatherstation").fadeIn("slow");
// Queue the next server request.
setTimeout(weather_cb_fetch, weather_fetch_interval);
}
function weather_init() {
// Start the loading animation spinning.
$("#loading").append(spinner.el);
// Fetch the initial weather report.
weather_cb_fetch();
}
// Start getting weather reports.
$(weather_init);
</script>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
</head>
<body>
<!--
Page loading animation (spin.js).
Hidden after weather canvases are loaded.
-->
<div id="loading"> </div>
<!--
Thermometer, gauges, and light level dot.
Initially hidden while loading/painting.
-->
<div id="weatherstation">
<canvas id="barometerCanvas"></canvas>
<canvas id="thermometerCanvas"></canvas>
<canvas id="lightCanvas"></canvas>
</div>
<script type="text/javascript" src="weatherstation.js"></script>
</body>
</html>

6
share/beaglebone/weather/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,355 @@
//fgnass.github.com/spin.js#v1.3.2
/**
* Copyright (c) 2011-2013 Felix Gnass
* Licensed under the MIT license
*/
(function(root, factory) {
/* CommonJS */
if (typeof exports == 'object') module.exports = factory()
/* AMD module */
else if (typeof define == 'function' && define.amd) define(factory)
/* Browser global */
else root.Spinner = factory()
}
(this, function() {
"use strict";
var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
, animations = {} /* Animation rules keyed by their name */
, useCssAnimations /* Whether to use CSS animations or setTimeout */
/**
* Utility function to create elements. If no tag name is given,
* a DIV is created. Optionally properties can be passed.
*/
function createEl(tag, prop) {
var el = document.createElement(tag || 'div')
, n
for(n in prop) el[n] = prop[n]
return el
}
/**
* Appends children and returns the parent.
*/
function ins(parent /* child1, child2, ...*/) {
for (var i=1, n=arguments.length; i<n; i++)
parent.appendChild(arguments[i])
return parent
}
/**
* Insert a new stylesheet to hold the @keyframe or VML rules.
*/
var sheet = (function() {
var el = createEl('style', {type : 'text/css'})
ins(document.getElementsByTagName('head')[0], el)
return el.sheet || el.styleSheet
}())
/**
* Creates an opacity keyframe animation rule and returns its name.
* Since most mobile Webkits have timing issues with animation-delay,
* we create separate rules for each line/segment.
*/
function addAnimation(alpha, trail, i, lines) {
var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-')
, start = 0.01 + i/lines * 100
, z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
, prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
, pre = prefix && '-' + prefix + '-' || ''
if (!animations[name]) {
sheet.insertRule(
'@' + pre + 'keyframes ' + name + '{' +
'0%{opacity:' + z + '}' +
start + '%{opacity:' + alpha + '}' +
(start+0.01) + '%{opacity:1}' +
(start+trail) % 100 + '%{opacity:' + alpha + '}' +
'100%{opacity:' + z + '}' +
'}', sheet.cssRules.length)
animations[name] = 1
}
return name
}
/**
* Tries various vendor prefixes and returns the first supported property.
*/
function vendor(el, prop) {
var s = el.style
, pp
, i
prop = prop.charAt(0).toUpperCase() + prop.slice(1)
for(i=0; i<prefixes.length; i++) {
pp = prefixes[i]+prop
if(s[pp] !== undefined) return pp
}
if(s[prop] !== undefined) return prop
}
/**
* Sets multiple style properties at once.
*/
function css(el, prop) {
for (var n in prop)
el.style[vendor(el, n)||n] = prop[n]
return el
}
/**
* Fills in default values.
*/
function merge(obj) {
for (var i=1; i < arguments.length; i++) {
var def = arguments[i]
for (var n in def)
if (obj[n] === undefined) obj[n] = def[n]
}
return obj
}
/**
* Returns the absolute page-offset of the given element.
*/
function pos(el) {
var o = { x:el.offsetLeft, y:el.offsetTop }
while((el = el.offsetParent))
o.x+=el.offsetLeft, o.y+=el.offsetTop
return o
}
/**
* Returns the line color from the given string or array.
*/
function getColor(color, idx) {
return typeof color == 'string' ? color : color[idx % color.length]
}
// Built-in defaults
var defaults = {
lines: 12, // The number of lines to draw
length: 7, // The length of each line
width: 5, // The line thickness
radius: 10, // The radius of the inner circle
rotate: 0, // Rotation offset
corners: 1, // Roundness (0..1)
color: '#000', // #rgb or #rrggbb
direction: 1, // 1: clockwise, -1: counterclockwise
speed: 1, // Rounds per second
trail: 100, // Afterglow percentage
opacity: 1/4, // Opacity of the lines
fps: 20, // Frames per second when using setTimeout()
zIndex: 2e9, // Use a high z-index by default
className: 'spinner', // CSS class to assign to the element
top: 'auto', // center vertically
left: 'auto', // center horizontally
position: 'relative' // element position
}
/** The constructor */
function Spinner(o) {
if (typeof this == 'undefined') return new Spinner(o)
this.opts = merge(o || {}, Spinner.defaults, defaults)
}
// Global defaults that override the built-ins:
Spinner.defaults = {}
merge(Spinner.prototype, {
/**
* Adds the spinner to the given target element. If this instance is already
* spinning, it is automatically removed from its previous target b calling
* stop() internally.
*/
spin: function(target) {
this.stop()
var self = this
, o = self.opts
, el = self.el = css(createEl(0, {className: o.className}), {position: o.position, width: 0, zIndex: o.zIndex})
, mid = o.radius+o.length+o.width
, ep // element position
, tp // target position
if (target) {
target.insertBefore(el, target.firstChild||null)
tp = pos(target)
ep = pos(el)
css(el, {
left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + 'px',
top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + 'px'
})
}
el.setAttribute('role', 'progressbar')
self.lines(el, self.opts)
if (!useCssAnimations) {
// No CSS animation support, use setTimeout() instead
var i = 0
, start = (o.lines - 1) * (1 - o.direction) / 2
, alpha
, fps = o.fps
, f = fps/o.speed
, ostep = (1-o.opacity) / (f*o.trail / 100)
, astep = f/o.lines
;(function anim() {
i++;
for (var j = 0; j < o.lines; j++) {
alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)
self.opacity(el, j * o.direction + start, alpha, o)
}
self.timeout = self.el && setTimeout(anim, ~~(1000/fps))
})()
}
return self
},
/**
* Stops and removes the Spinner.
*/
stop: function() {
var el = this.el
if (el) {
clearTimeout(this.timeout)
if (el.parentNode) el.parentNode.removeChild(el)
this.el = undefined
}
return this
},
/**
* Internal method that draws the individual lines. Will be overwritten
* in VML fallback mode below.
*/
lines: function(el, o) {
var i = 0
, start = (o.lines - 1) * (1 - o.direction) / 2
, seg
function fill(color, shadow) {
return css(createEl(), {
position: 'absolute',
width: (o.length+o.width) + 'px',
height: o.width + 'px',
background: color,
boxShadow: shadow,
transformOrigin: 'left',
transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)',
borderRadius: (o.corners * o.width>>1) + 'px'
})
}
for (; i < o.lines; i++) {
seg = css(createEl(), {
position: 'absolute',
top: 1+~(o.width/2) + 'px',
transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
opacity: o.opacity,
animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1/o.speed + 's linear infinite'
})
if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'}))
ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')))
}
return el
},
/**
* Internal method that adjusts the opacity of a single line.
* Will be overwritten in VML fallback mode below.
*/
opacity: function(el, i, val) {
if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
}
})
function initVML() {
/* Utility function to create a VML tag */
function vml(tag, attr) {
return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
}
// No CSS transforms but VML support, add a CSS rule for VML elements:
sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')
Spinner.prototype.lines = function(el, o) {
var r = o.length+o.width
, s = 2*r
function grp() {
return css(
vml('group', {
coordsize: s + ' ' + s,
coordorigin: -r + ' ' + -r
}),
{ width: s, height: s }
)
}
var margin = -(o.width+o.length)*2 + 'px'
, g = css(grp(), {position: 'absolute', top: margin, left: margin})
, i
function seg(i, dx, filter) {
ins(g,
ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}),
ins(css(vml('roundrect', {arcsize: o.corners}), {
width: r,
height: o.width,
left: o.radius,
top: -o.width>>1,
filter: filter
}),
vml('fill', {color: getColor(o.color, i), opacity: o.opacity}),
vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
)
)
)
}
if (o.shadow)
for (i = 1; i <= o.lines; i++)
seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')
for (i = 1; i <= o.lines; i++) seg(i)
return ins(el, g)
}
Spinner.prototype.opacity = function(el, i, val, o) {
var c = el.firstChild
o = o.shadow && o.lines || 0
if (c && i+o < c.childNodes.length) {
c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild
if (c) c.opacity = val
}
}
}
var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})
if (!vendor(probe, 'transform') && probe.adj) initVML()
else useCssAnimations = vendor(probe, 'animation')
return Spinner
}));

View file

@ -0,0 +1,17 @@
body {
background-color: white;
font-family: verdana, helvetica, sans-serif;
color: #333333;
font-size: 12px;
}
#weatherstation {
display: none;
}
#loading {
/* Display in the center of the screen */
top: 50%;
left: 50%;
position: absolute;
}

View file

@ -0,0 +1,302 @@
// Main javascript for the BeagleBone Weather Cape demo.
//
// This code handles drawing the thermometer, gauges, and light level dot.
// Most of this code is from the original weatherstation demo. There is some
// room for improvement (i.e. changing the scale on the gauges requires a lot
// of tweaking, not just changing pmax and pmin), but it gets the job done.
// global vars
var PI = 3.14;
var HALF_PI = 1.57;
var TWO_PI = 6.28;
// set defaults
var pressure = 0;
var pmax = 1200;
var pmin = 200;
var pdata = 0;
var punit = "hPa";
var temp = 0.0;
var tmax = 40.0;
var tmin = -20.0;
var tunit = "C";
var humidity = 0.0;
var hmax = 100.0;
var hmin = 0.0;
var hdata = 0.0;
var hunit = "% Humidity";
var lux = 0;
var lmax = 2000;
var lmin = 0;
var lunit = "lux";
var setWidth = function() {
var canvasWidth = window.innerWidth * 0.9;
if ( canvasWidth > (0.8 * window.innerHeight)) {
canvasWidth = 1.8 * window.innerHeight;
$('#heading').hide();
} else
$('#heading').show();
barometerWidth = canvasWidth*0.5;
barometerHeight = barometerWidth;
thermometerWidth = canvasWidth*0.25;
lightWidth = canvasWidth*0.25;
};
setWidth();
var barometerSketchProc = function(p) {
p.size(barometerWidth, barometerWidth);
p.draw = function() {
p.size(barometerWidth, barometerWidth);
barometerWidth=p.width;
p.background(0,0);
// barometer
p.fill(220);
p.noStroke();
// Angles for sin() and cos() start at 3 o'clock;
// subtract HALF_PI to make them start at the top
p.ellipse(barometerWidth/2, barometerWidth/2, barometerWidth*0.8, barometerWidth*0.8);
var angle = -HALF_PI + HALF_PI / 3;
var inc = TWO_PI / 12;
p.stroke(0);
p.strokeWeight(barometerWidth*0.015);
p.arc(barometerWidth/2, barometerWidth/2, barometerWidth*0.8, barometerWidth*0.8, -(4/3)*PI, PI/3);
// we want a range from 200 hPa to 1200 hPa
// we want a range from ±950 - ±1050 milibar
// 1-5=1010-1050, 7-12=950-1000
p.textSize(Math.round(barometerWidth*0.04));
for ( i = 1; i <= 12; i++, angle += inc ) {
if(i!=6) {
p.line(barometerWidth/2 + Math.cos(angle) * barometerWidth*0.35,barometerWidth/2 + Math.sin(angle) * barometerWidth*.35,barometerWidth/2 + Math.cos(angle) * barometerWidth*0.4,barometerWidth/2 + Math.sin(angle) * barometerWidth*.4);
if (i < 6) {
myText = 700 + 100*i;
} else {
myText = 100*i - 500;
}
myWidth = p.textWidth(myText);
p.fill(0, 102, 153);
p.text(myText, Math.round(barometerWidth/2 + Math.cos(angle) * barometerWidth*0.3 - myWidth/2),Math.round(barometerWidth/2 + Math.sin(angle) * barometerWidth*0.3));
} else {
myText = pdata + ' ' + punit;
myWidth = p.textWidth(myText);
p.fill(0, 102, 153);
p.text(myText, Math.round(barometerWidth/2 + Math.cos(angle) * barometerWidth*0.3 - myWidth/2),Math.round(barometerWidth/2 + Math.sin(angle) * barometerWidth*0.3));
}
}
// RH scale
p.fill(220);
p.stroke(0);
p.strokeWeight(barometerWidth*0.005);
p.arc(barometerWidth/2, barometerWidth/2, barometerWidth*0.4, barometerWidth*0.4, -(4/3)*PI, PI/3);
// we want a range from 0 - 100%
// 1-5=60-100, 7-12=0-50
p.textSize(Math.round(barometerWidth*0.02));
for ( i = 1; i <= 12; i++, angle += inc ) {
if(i!=6) {
p.line(barometerWidth/2 + Math.cos(angle) * barometerWidth*0.18,barometerWidth/2 + Math.sin(angle) * barometerWidth*.18,barometerWidth/2 + Math.cos(angle) * barometerWidth*0.2,barometerWidth/2 + Math.sin(angle) * barometerWidth*.2);
if (i < 6) {
myText = 50 +10*i;
} else {
myText = 10*i - 70;
}
myWidth = p.textWidth(myText);
p.fill(0, 102, 153);
p.text(myText, Math.round(barometerWidth/2 + Math.cos(angle) * barometerWidth*0.16 - myWidth/2),Math.round(barometerWidth/2 + Math.sin(angle) * barometerWidth*0.16));
} else {
myText = hdata + ' ' + hunit;
myWidth = p.textWidth(myText);
p.fill(0, 102, 153);
p.text(myText, Math.round(barometerWidth/2 + Math.cos(angle) * barometerWidth*0.12 - myWidth/2),Math.round(barometerWidth/2 + Math.sin(angle) * barometerWidth*0.12));
}
}
//humidity needle
p.stroke(60);
p.strokeWeight(barometerWidth*0.005);
p.line(-Math.cos(humidity) * barometerWidth*0.05 + barometerWidth/2, -Math.sin(humidity) * barometerWidth*0.05 + barometerWidth/2, Math.cos(humidity) * barometerWidth*0.15 + barometerWidth/2, Math.sin(humidity) * barometerWidth*0.15 + barometerWidth/2);
//p.ellipse(barometerWidth/2, barometerWidth/2, barometerWidth/20, barometerWidth/20);
// pressure needle
p.stroke(60);
p.strokeWeight(barometerWidth*0.015);
p.line(-Math.cos(pressure) * barometerWidth*0.05 + barometerWidth/2, -Math.sin(pressure) * barometerWidth*0.05 + barometerWidth/2, Math.cos(pressure) * barometerWidth*0.35 + barometerWidth/2, Math.sin(pressure) * barometerWidth*0.35 + barometerWidth/2);
p.ellipse(barometerWidth/2, barometerWidth/2, barometerWidth/20, barometerWidth/20);
};
p.noLoop();
}
var thermometerSketchProc = function(p) {
p.size(thermometerWidth, barometerHeight);
p.draw = function() {
p.size(thermometerWidth, barometerHeight);
thermometerWidth=p.width;
p.background(0,0);
// thermometer
// contour
p.stroke(0);
p.strokeWeight(thermometerWidth*0.27);
p.line(thermometerWidth/2,thermometerWidth*1.75,thermometerWidth/2,thermometerWidth/4);
p.ellipse(thermometerWidth/2, thermometerWidth*1.75, thermometerWidth/5, thermometerWidth/5);
p.strokeWeight(thermometerWidth*0.22);
p.stroke(255);
p.line(thermometerWidth/2,thermometerWidth*1.75,thermometerWidth/2,thermometerWidth/4);
// mercury bubble
if(temp > 0) {
p.stroke(255,0,0);
} else {
p.stroke(0,0,255)
}
p.ellipse(thermometerWidth/2, thermometerWidth*1.75, thermometerWidth/5, thermometerWidth/5);
// temperature line
if (temp < 44) {
p.strokeCap("butt");
} else {
// don't exceed thermometer bounds.
temp = 44;
p.strokeCap("round");
}
p.line(thermometerWidth/2,thermometerWidth*1.75,thermometerWidth/2,thermometerWidth*1.1 - (thermometerWidth/50) * temp);
// scale
p.strokeCap("round");
p.stroke(0);
p.strokeWeight(thermometerWidth*0.03);
var theight = thermometerWidth*1.5;
var inc = thermometerWidth/5;
p.textSize(Math.round(thermometerWidth*0.06));
for ( i = 1; i <= 7; i++, theight -= inc ) {
// longer bar at zero degrees C
if(i==3) {
extra = thermometerWidth/10;
} else {
extra = thermometerWidth/20;
}
p.line((thermometerWidth/2) - thermometerWidth/8,theight,(thermometerWidth/2) - thermometerWidth/8 + extra, theight );
myText = -30 + i*10;
p.fill(0, 0, 0);
p.text(myText, (thermometerWidth/2) - thermometerWidth*0.09 + extra, theight + 4);
// min/max marks
p.strokeWeight(thermometerWidth*0.01);
p.line((thermometerWidth/2) + thermometerWidth/8,thermometerWidth*1.1 - (thermometerWidth/50) * tmin,(thermometerWidth/2) + thermometerWidth/8 - thermometerWidth/20, thermometerWidth*1.1 - (thermometerWidth/50) * tmin );
p.line((thermometerWidth/2) + thermometerWidth/8,thermometerWidth*1.1 - (thermometerWidth/50) * tmax,(thermometerWidth/2) + thermometerWidth/8 - thermometerWidth/20, thermometerWidth*1.1 - (thermometerWidth/50) * tmax );
p.strokeWeight(thermometerWidth*0.03);
}
myText = temp + ' ' + tunit;
p.fill(0, 0, 0);
p.textSize(Math.round(thermometerWidth*0.09));
myWidth = p.textWidth(myText);
p.text(myText, thermometerWidth/2 - myWidth/2, thermometerWidth*1.75 + thermometerWidth*0.045);
};
p.noLoop();
}
var lightSketchProc = function(p) {
var fill_lux;
p.size(lightWidth, barometerHeight);
p.draw = function() {
p.size(lightWidth, barometerHeight);
lightWidth=p.width;
p.background(0,0);
// contour
p.stroke(0);
p.strokeWeight(lightWidth*0.01);
fill_lux = lux;
if(fill_lux > (3*255 - 10))
fill_lux = (3*255 - 10);
p.fill(fill_lux/3 + 10);
p.ellipse(lightWidth/2,lightWidth,lightWidth/2,lightWidth/2);
myText = lux + ' ' + lunit;
p.fill(0, 0, 0);
p.textSize(Math.round(lightWidth*0.09));
myWidth = p.textWidth(myText);
p.text(myText, lightWidth/2 - myWidth/2, lightWidth*1.4 + lightWidth*0.045);
};
p.noLoop();
}
var canvas = document.getElementById("barometerCanvas");
var thermometerCanvas = document.getElementById("thermometerCanvas");
var lightCanvas = document.getElementById("lightCanvas");
var barometer = new Processing(canvas, barometerSketchProc);
var thermometer = new Processing(thermometerCanvas, thermometerSketchProc);
var light = new Processing(lightCanvas, lightSketchProc);
function set_pressure(data) {
var myData = parseFloat(data);
// Angles for sin() and cos() start at 3 o'clock;
// subtract HALF_PI to make them start at the top
pressure = ((myData - 700) / 10) * (TWO_PI / 120) - HALF_PI;
pdata = myData;
if (myData > pmax) pmax = myData;
if (myData < pmin) pmin = myData;
barometer.redraw();
}
function set_humidity(data) {
var myData = parseFloat(data);
// Angles for sin() and cos() start at 3 o'clock;
// subtract HALF_PI to make them start at the top
// 30% = HALF_PI
humidity = (myData - 50) * (TWO_PI / 120) - HALF_PI;
hdata = myData;
if (myData > hmax) hmax = myData;
if (myData < hmin) hmin = myData;
barometer.redraw();
}
function set_temp(data) {
var myData = parseFloat(data);
temp = myData;
if (myData > tmax) tmax = myData;
if (myData < tmin) tmin = myData;
thermometer.redraw();
}
function set_lux(data) {
var myData = parseFloat(data);
lux = myData;
light.redraw();
}
function resizeHandler() {
setWidth();
barometer.redraw();
thermometer.redraw();
light.redraw();
}
window.onresize=resizeHandler;

View file

@ -0,0 +1,174 @@
#!/usr/bin/lua
--
-- weatherstation.lua - a simple web server intended to be run from tcpd to
-- expose sensors from the BeagleBone Weather cape and serve the web app.
--
-- List of files shared by this service.
distfiles={}
distfiles["/index.html"] = true
distfiles["/style.css"] = true
distfiles["/jquery.js"] = true
distfiles["/processing.js"] = true
distfiles["/spin.js"] = true
distfiles["/weatherstation.js"] = true
-- Base path for distfiles
prefix="/usr/share/beaglebone/weather"
-- Check that the filename is part of this demo.
function filename_is_valid(filename)
return distfiles[filename] ~= nil
end
-- Check if the string 's' starts with 'starting'
function starts_with(s, starting)
return string.sub(s, 1, string.len(starting)) == starting
end
-- Check if the string 's' ends with 'ending'
function ends_with(s, ending)
return string.sub(s, -string.len(ending)) == ending
end
-- Return the content type of the file based only on the file extension.
function get_content_type(filename)
if ends_with(filename, ".js") then
return "application/javascript"
elseif ends_with(filename, ".css") then
return "text/css"
elseif ends_with(filename, ".html") then
return "text/html"
elseif ends_with(filename, ".json") then
return "application/json"
else
return "text/plain"
end
end
-- Reads from STDIN until an empty or nil line is received.
-- Returns the first line of the request.
function read_request()
local i = 0
local lines = {}
repeat
local line = io.read("*line")
if line == nil or line == "\r" then
break
end
lines[i] = line
i = i + 1
until false
return lines[0]
end
-- Return the entire contents of a file
function read_file(filename)
local f = io.open(filename, "rb")
if f == nil then
return nil
end
local content = f:read("*all")
f:close()
return content
end
-- Extract the value of the LABEL:VALUE pairs read from the device files.
function extract_value(label, data, pat)
local x, y, z = string.find(data, label .. "%s+: (" .. pat .. ")")
if x == nil or y == nil or z == nil then
return "0"
end
return z
end
-- Read the sensor values and generate json output
function generate_json()
local json = ""
local tsl2550 = read_file("/dev/tsl2550b3s39")
if tsl2550 == nil then
return nil
end
local illuminance = extract_value("ILLUMINANCE", tsl2550, "%d+")
local sht21 = read_file("/dev/sht21b3s40")
if sht21 == nil then
return nil
end
local temperature = extract_value("TEMPERATURE", sht21, "%d+.%d+")
local humidity = extract_value("HUMIDITY", sht21, "%d+.%d+")
local bmp085 = read_file("/dev/bmp085b3s77")
if bmp085 == nil then
return nil
end
local pressure = extract_value("PRESSURE", bmp085, "%d+")
json = json .. "{\n"
json = json .. "\"temperature\": " .. temperature .. ",\n"
json = json .. "\"humidity\": " .. humidity .. ",\n"
json = json .. "\"illuminance\": " .. illuminance .. ",\n"
json = json .. "\"pressure\": " .. pressure .. "\n"
json = json .. "}\n"
return json
end
function handle_request(req)
if req == nil then
response("404 Not Found", "text/plain", "Unknown Request")
return
end
-- Parse filename out of HTTP request
local filename = (string.match(req, " %S+ "):gsub("^%s+", ""):gsub("%s+$", ""))
if filename == "" or filename == "/" then
filename = "/index.html"
end
-- Check if the filename is known (i.e. it's a file in the web app)
if filename_is_valid(filename) then
local contents = read_file(prefix .. filename)
if contents ~= nil then
response("200 OK", get_content_type(filename), contents)
else
response("404 Not Found", "text/plain", "Failed to load known file")
end
-- Else maybe the user is requesting the dynamic json data
elseif starts_with(filename, "/weather.json") then
local json = generate_json()
if json == nil then
response("500 Internal Server Error", "text/plain", "Could not get sensor values")
else
response("200 OK", get_content_type("/weather.json"), json)
end
-- Else, the user is requesting something not part of the app
else
response("403 Forbidden", "text/plain", "File not allowed.")
end
return
end
-- Send the response to the HTTP request.
function response(err_code, content_type, content)
io.write("HTTP/1.1 " .. err_code .. "\r\n")
io.write("Content-Type: " .. content_type .. "\r\n")
io.write("\r\n")
io.write(content)
io.write("\r\n")
end
-- Read the request and then handle it.
handle_request(read_request())