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:
parent
1fbbfa06c2
commit
60a61dffae
16 changed files with 1088 additions and 1 deletions
|
@ -119,5 +119,16 @@
|
||||||
./usr/sbin/tps65217 minix-sys
|
./usr/sbin/tps65217 minix-sys
|
||||||
./usr/sbin/tps65950 minix-sys
|
./usr/sbin/tps65950 minix-sys
|
||||||
./usr/sbin/tsl2550 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/mod minix-sys
|
||||||
./usr/tests/minix-posix/test63 minix-sys
|
./usr/tests/minix-posix/test63 minix-sys
|
||||||
|
|
|
@ -18,7 +18,8 @@ EXTRA_DIST_FILES+= ${.CURDIR}/Minix.gcccmds
|
||||||
|
|
||||||
# XXX these are only used by compat currently, but they could be used
|
# 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.
|
# 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})
|
.if exists(NetBSD.dist.${MACHINE_ARCH})
|
||||||
EXTRA_DIST_FILES+= ${.CURDIR}/NetBSD.dist.${MACHINE_ARCH}
|
EXTRA_DIST_FILES+= ${.CURDIR}/NetBSD.dist.${MACHINE_ARCH}
|
||||||
.endif
|
.endif
|
||||||
|
|
4
etc/mtree/NetBSD.dist.earm
Normal file
4
etc/mtree/NetBSD.dist.earm
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#
|
||||||
|
|
||||||
|
./usr/share/beaglebone
|
||||||
|
./usr/share/beaglebone/weather
|
|
@ -17,3 +17,4 @@ test -e /dev/bmp085b3s77 || (cd /dev && MAKEDEV bmp085b3s77)
|
||||||
/bin/service up /usr/sbin/bmp085 -dev /dev/bmp085b3s77 \
|
/bin/service up /usr/sbin/bmp085 -dev /dev/bmp085b3s77 \
|
||||||
-label bmp085.3.77 -args 'bus=3 address=0x77' && echo -n " bmp085"
|
-label bmp085.3.77 -args 'bus=3 address=0x77' && echo -n " bmp085"
|
||||||
|
|
||||||
|
daemonize tcpd http /usr/share/beaglebone/weather/weatherstation.lua
|
||||||
|
|
|
@ -9,6 +9,13 @@
|
||||||
make(clean) || make(cleandir) || make(distclean) || make(obj)
|
make(clean) || make(cleandir) || make(distclean) || make(obj)
|
||||||
SUBDIR= misc mk \
|
SUBDIR= misc mk \
|
||||||
terminfo zoneinfo
|
terminfo zoneinfo
|
||||||
|
|
||||||
|
.if defined(__MINIX)
|
||||||
|
.if ${MACHINE_ARCH} == "earm"
|
||||||
|
SUBDIR+= beaglebone
|
||||||
|
.endif # ${MACHINE_ARCH} == "earm"
|
||||||
|
.endif # defined(__MINIX)
|
||||||
|
|
||||||
.if ${MKNLS} != "no"
|
.if ${MKNLS} != "no"
|
||||||
SUBDIR+=i18n locale nls
|
SUBDIR+=i18n locale nls
|
||||||
.endif
|
.endif
|
||||||
|
|
4
share/beaglebone/Makefile
Normal file
4
share/beaglebone/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
SUBDIR+= weather
|
||||||
|
|
||||||
|
.include <bsd.subdir.mk>
|
27
share/beaglebone/weather/LICENSE
Normal file
27
share/beaglebone/weather/LICENSE
Normal 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.
|
||||||
|
-------------------------------------------------------------------------------
|
15
share/beaglebone/weather/Makefile
Normal file
15
share/beaglebone/weather/Makefile
Normal 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>
|
53
share/beaglebone/weather/README.txt
Normal file
53
share/beaglebone/weather/README.txt
Normal 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.
|
||||||
|
|
97
share/beaglebone/weather/index.html
Normal file
97
share/beaglebone/weather/index.html
Normal 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
6
share/beaglebone/weather/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
share/beaglebone/weather/processing.js
Normal file
13
share/beaglebone/weather/processing.js
Normal file
File diff suppressed because one or more lines are too long
355
share/beaglebone/weather/spin.js
Normal file
355
share/beaglebone/weather/spin.js
Normal 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
|
||||||
|
|
||||||
|
}));
|
17
share/beaglebone/weather/style.css
Normal file
17
share/beaglebone/weather/style.css
Normal 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;
|
||||||
|
}
|
302
share/beaglebone/weather/weatherstation.js
Normal file
302
share/beaglebone/weather/weatherstation.js
Normal 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;
|
174
share/beaglebone/weather/weatherstation.lua
Normal file
174
share/beaglebone/weather/weatherstation.lua
Normal 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())
|
||||||
|
|
Loading…
Reference in a new issue