├── .github ├── FUNDING.yml └── workflows │ └── stale.yml ├── .gitignore ├── README.md ├── Vagrantfile ├── config ├── example.config.yml ├── host_vars │ └── .gitignore └── inventory-example ├── dashboard ├── app.js ├── bin │ └── www ├── database │ ├── sample-data.sql │ └── schema.sql ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── images │ │ └── loading.gif │ ├── javascripts │ │ ├── flot │ │ │ ├── API.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── FAQ.md │ │ │ ├── LICENSE.txt │ │ │ ├── Makefile │ │ │ ├── NEWS.md │ │ │ ├── PLUGINS.md │ │ │ ├── README.md │ │ │ ├── examples │ │ │ │ ├── ajax │ │ │ │ │ ├── data-eu-gdp-growth-1.json │ │ │ │ │ ├── data-eu-gdp-growth-2.json │ │ │ │ │ ├── data-eu-gdp-growth-3.json │ │ │ │ │ ├── data-eu-gdp-growth-4.json │ │ │ │ │ ├── data-eu-gdp-growth-5.json │ │ │ │ │ ├── data-eu-gdp-growth.json │ │ │ │ │ ├── data-japan-gdp-growth.json │ │ │ │ │ ├── data-usa-gdp-growth.json │ │ │ │ │ └── index.html │ │ │ │ ├── annotating │ │ │ │ │ └── index.html │ │ │ │ ├── axes-interacting │ │ │ │ │ └── index.html │ │ │ │ ├── axes-multiple │ │ │ │ │ └── index.html │ │ │ │ ├── axes-time-zones │ │ │ │ │ ├── date.js │ │ │ │ │ ├── index.html │ │ │ │ │ └── tz │ │ │ │ │ │ ├── africa │ │ │ │ │ │ ├── antarctica │ │ │ │ │ │ ├── asia │ │ │ │ │ │ ├── australasia │ │ │ │ │ │ ├── backward │ │ │ │ │ │ ├── etcetera │ │ │ │ │ │ ├── europe │ │ │ │ │ │ ├── factory │ │ │ │ │ │ ├── iso3166.tab │ │ │ │ │ │ ├── leapseconds │ │ │ │ │ │ ├── northamerica │ │ │ │ │ │ ├── pacificnew │ │ │ │ │ │ ├── solar87 │ │ │ │ │ │ ├── solar88 │ │ │ │ │ │ ├── solar89 │ │ │ │ │ │ ├── southamerica │ │ │ │ │ │ ├── systemv │ │ │ │ │ │ ├── yearistype.sh │ │ │ │ │ │ └── zone.tab │ │ │ │ ├── axes-time │ │ │ │ │ └── index.html │ │ │ │ ├── background.png │ │ │ │ ├── basic-options │ │ │ │ │ └── index.html │ │ │ │ ├── basic-usage │ │ │ │ │ └── index.html │ │ │ │ ├── canvas │ │ │ │ │ └── index.html │ │ │ │ ├── categories │ │ │ │ │ └── index.html │ │ │ │ ├── examples.css │ │ │ │ ├── image │ │ │ │ │ ├── hs-2004-27-a-large-web.jpg │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── interacting │ │ │ │ │ └── index.html │ │ │ │ ├── navigate │ │ │ │ │ ├── arrow-down.gif │ │ │ │ │ ├── arrow-left.gif │ │ │ │ │ ├── arrow-right.gif │ │ │ │ │ ├── arrow-up.gif │ │ │ │ │ └── index.html │ │ │ │ ├── percentiles │ │ │ │ │ └── index.html │ │ │ │ ├── realtime │ │ │ │ │ └── index.html │ │ │ │ ├── resize │ │ │ │ │ └── index.html │ │ │ │ ├── selection │ │ │ │ │ └── index.html │ │ │ │ ├── series-errorbars │ │ │ │ │ └── index.html │ │ │ │ ├── series-pie │ │ │ │ │ └── index.html │ │ │ │ ├── series-toggle │ │ │ │ │ └── index.html │ │ │ │ ├── series-types │ │ │ │ │ └── index.html │ │ │ │ ├── shared │ │ │ │ │ └── jquery-ui │ │ │ │ │ │ ├── jquery-ui.min.css │ │ │ │ │ │ └── jquery-ui.min.js │ │ │ │ ├── stacking │ │ │ │ │ └── index.html │ │ │ │ ├── symbols │ │ │ │ │ └── index.html │ │ │ │ ├── threshold │ │ │ │ │ └── index.html │ │ │ │ ├── tracking │ │ │ │ │ └── index.html │ │ │ │ ├── visitors │ │ │ │ │ └── index.html │ │ │ │ └── zooming │ │ │ │ │ └── index.html │ │ │ ├── excanvas.js │ │ │ ├── excanvas.min.js │ │ │ ├── jquery.colorhelpers.js │ │ │ ├── jquery.colorhelpers.min.js │ │ │ ├── jquery.flot.canvas.js │ │ │ ├── jquery.flot.canvas.min.js │ │ │ ├── jquery.flot.categories.js │ │ │ ├── jquery.flot.categories.min.js │ │ │ ├── jquery.flot.crosshair.js │ │ │ ├── jquery.flot.crosshair.min.js │ │ │ ├── jquery.flot.errorbars.js │ │ │ ├── jquery.flot.errorbars.min.js │ │ │ ├── jquery.flot.fillbetween.js │ │ │ ├── jquery.flot.fillbetween.min.js │ │ │ ├── jquery.flot.image.js │ │ │ ├── jquery.flot.image.min.js │ │ │ ├── jquery.flot.js │ │ │ ├── jquery.flot.min.js │ │ │ ├── jquery.flot.navigate.js │ │ │ ├── jquery.flot.navigate.min.js │ │ │ ├── jquery.flot.pie.js │ │ │ ├── jquery.flot.pie.min.js │ │ │ ├── jquery.flot.resize.js │ │ │ ├── jquery.flot.resize.min.js │ │ │ ├── jquery.flot.selection.js │ │ │ ├── jquery.flot.selection.min.js │ │ │ ├── jquery.flot.stack.js │ │ │ ├── jquery.flot.stack.min.js │ │ │ ├── jquery.flot.symbol.js │ │ │ ├── jquery.flot.symbol.min.js │ │ │ ├── jquery.flot.threshold.js │ │ │ ├── jquery.flot.threshold.min.js │ │ │ ├── jquery.flot.time.js │ │ │ ├── jquery.flot.time.min.js │ │ │ ├── jquery.js │ │ │ └── jquery.min.js │ │ ├── index.js │ │ └── jquery │ │ │ ├── jquery.min.js │ │ │ └── jquery.min.map │ └── stylesheets │ │ └── style.css ├── router │ ├── index.js │ └── routes │ │ ├── home.js │ │ ├── sensors.js │ │ └── temps.js ├── screenshot.png └── views │ ├── error.pug │ ├── index.pug │ └── layout.pug ├── images ├── arudino-temperature-wiring.jpg ├── pi-temperature-wiring-1.jpg └── pi-temperature-wiring-2.jpg ├── playbooks ├── main.yml ├── master │ ├── main.yml │ ├── tasks │ │ ├── app.yml │ │ ├── database.yml │ │ └── scripts.yml │ └── templates │ │ ├── nest_api.j2 │ │ ├── temps.conf.j2 │ │ └── wu_api.j2 ├── remote-monitor │ ├── main.yml │ └── templates │ │ └── pi-temps-init.j2 └── requirements.yml └── scripts ├── example.temps.conf ├── nest-temps.py ├── outdoor-temps-owm.py ├── outdoor-temps-wu.py ├── pi-temps.py ├── requirements.txt └── temp_api.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | --- 3 | github: geerlingguy 4 | patreon: geerlingguy 5 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Close inactive issues 3 | 'on': 4 | schedule: 5 | - cron: "55 7 * * 4" # semi-random time 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v8 15 | with: 16 | days-before-stale: 120 17 | days-before-close: 60 18 | exempt-issue-labels: bug,pinned,security,planned 19 | exempt-pr-labels: bug,pinned,security,planned 20 | stale-issue-label: "stale" 21 | stale-pr-label: "stale" 22 | stale-issue-message: | 23 | This issue has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 24 | 25 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 26 | close-issue-message: | 27 | This issue has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 28 | stale-pr-message: | 29 | This pr has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 30 | 31 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 32 | close-pr-message: | 33 | This pr has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 34 | repo-token: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac. 2 | .DS_Store 3 | 4 | # Vagrant. 5 | .vagrant 6 | 7 | # Configuration. 8 | config/config.yml 9 | config/inventory 10 | /config/host-vars 11 | 12 | # App-specific files. 13 | *.log 14 | scripts/temps.conf 15 | 16 | # Python. 17 | *.pyc 18 | 19 | # Node.js. 20 | */node_modules/* 21 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure(2) do |config| 5 | config.vm.box = "geerlingguy/debian10" 6 | 7 | inventory_groups = { 8 | 'master' => ['master'], 9 | 'remotes' => ['remote'] 10 | } 11 | 12 | # VMware Fusion. 13 | config.vm.provider "vmware_fusion" do |v| 14 | v.vmx["memsize"] = 1024 15 | end 16 | 17 | # VirtualBox. 18 | config.vm.provider "virtualbox" do |vb| 19 | vb.memory = "1024" 20 | end 21 | 22 | config.vm.define "master" do |machine| 23 | config.vm.network "forwarded_port", guest: 3000, host: 3000, auto_correct: true 24 | 25 | config.vm.provision :ansible do |ansible| 26 | ansible.playbook = "playbooks/master/main.yml" 27 | ansible.become = true 28 | ansible.extra_vars = { 29 | deploy_target_is_pi: false, 30 | temperature_monitor_dir: '/vagrant', 31 | temperature_monitor_user: 'vagrant', 32 | dashboard_uri: 'http://localhost:3000' 33 | } 34 | ansible.groups = inventory_groups 35 | end 36 | end 37 | 38 | # TODO - Add second VM for remote-monitor server. 39 | end 40 | -------------------------------------------------------------------------------- /config/example.config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Whether this playbook is targeting an actual Pi or a non-Pi environment. 3 | deploy_target_is_pi: true 4 | 5 | # Temperature monitor app variables. 6 | temperature_monitor_dir: /home/pi/temperature-monitor 7 | temperature_monitor_user: pi 8 | 9 | # Dashboard and sensor configuration. Make sure you *remove* any variables here 10 | # if you want to be able to override the variables per-host in host_vars. 11 | dashboard_uri: http://10.0.1.33:3000 12 | local_temp_read_delay: 60 13 | local_temp_offset: 0 14 | local_sensor_id: 1 15 | nest_sensor_id: 3 16 | wu_sensor_id: 2 17 | owm_sensor_id: 2 18 | owm_city_id: "4407084" 19 | 20 | # Raspberry Pi configuration. 21 | raspberry_pi_boot_config_options: 22 | # Set the GPU memory split value to conserve system RAM. 23 | - regexp: "^#?gpu_mem" 24 | line: "gpu_mem=16" 25 | # Enable 1-Wire via GPIO. 26 | - regexp: "^dtoverlay=w1-gpio" 27 | line: "dtoverlay=w1-gpio" 28 | # Turn off the PWR LED. 29 | - regexp: "^dtparam=pwr_led_trigger=" 30 | line: "dtparam=pwr_led_trigger=none" 31 | - regexp: "^dtparam=pwr_led_activelow=" 32 | line: "dtparam=pwr_led_activelow=off" 33 | raspberry_pi_rc_local_options: 34 | # Disable HDMI on startup (for power savings). 35 | - regexp: "^/usr/bin/tvservice" 36 | line: "/usr/bin/tvservice -o" 37 | 38 | # Node.js vars. Used only for the dashboard/master server. 39 | nodejs_version: "12.x" 40 | nodejs_npm_global_packages: 41 | - name: forever 42 | 43 | # MySQL vars. Used only for the dashboard/master server. 44 | mysql_root_password: root 45 | mysql_key_buffer_size: "16M" 46 | mysql_max_allowed_packet: "64M" 47 | mysql_table_open_cache: "128" 48 | mysql_sort_buffer_size: "512K" 49 | mysql_read_buffer_size: "512K" 50 | mysql_read_rnd_buffer_size: "1M" 51 | mysql_myisam_sort_buffer_size: "4M" 52 | mysql_thread_cache_size: "4" 53 | mysql_query_cache_size: "2M" 54 | mysql_thread_concurrency: 1 55 | mysql_innodb_file_per_table: "1" 56 | mysql_innodb_buffer_pool_size: "16M" 57 | mysql_innodb_additional_mem_pool_size: "4M" 58 | mysql_innodb_log_file_size: "16M" 59 | mysql_innodb_log_buffer_size: "2M" 60 | mysql_innodb_flush_log_at_trx_commit: "1" 61 | 62 | # Nest credentials (if empty, will not be used). 63 | nest_access_token: "" 64 | nest_thermostat_id: "" 65 | 66 | # Weather Underground credentials (if empty, will not be used). 67 | wu_api_key: "" 68 | wu_location: "MO/Saint_Louis" 69 | -------------------------------------------------------------------------------- /config/host_vars/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* 2 | -------------------------------------------------------------------------------- /config/inventory-example: -------------------------------------------------------------------------------- 1 | [master] 2 | 10.0.1.33 3 | 4 | [remotes] 5 | 10.0.1.34 6 | 10.0.1.35 7 | -------------------------------------------------------------------------------- /dashboard/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var app = express(); 9 | 10 | // Connect to MySQL database. 11 | var mysql = require('mysql'); 12 | var pool = mysql.createPool({ 13 | connectionLimit: 5, 14 | host: '127.0.0.1', 15 | user: 'root', 16 | password: 'root', 17 | database: 'temperature_log', 18 | supportBigNumbers: true, 19 | bigNumberStrings: true 20 | }); 21 | 22 | // view engine setup 23 | app.set('views', path.join(__dirname, 'views')); 24 | app.set('view engine', 'pug'); 25 | 26 | // uncomment after placing your favicon in /public 27 | app.use(favicon(__dirname + '/public/favicon.ico')); 28 | app.use(logger('dev')); 29 | app.use(bodyParser.json()); 30 | app.use(bodyParser.urlencoded({ extended: false })); 31 | app.use(cookieParser()); 32 | app.use(express.static(path.join(__dirname, 'public'))); 33 | 34 | // Make database connection available to routes. 35 | app.use(function(req, res, next) { 36 | req.pool = pool; 37 | next(); 38 | }); 39 | 40 | var router = require('./router/index')(app); 41 | 42 | // catch 404 and forward to error handler 43 | app.use(function(req, res, next) { 44 | var err = new Error('Not Found'); 45 | err.status = 404; 46 | next(err); 47 | }); 48 | 49 | // error handlers 50 | 51 | // development error handler 52 | // will print stacktrace 53 | if (app.get('env') === 'development') { 54 | app.use(function(err, req, res, next) { 55 | res.status(err.status || 500); 56 | res.render('error', { 57 | message: err.message, 58 | error: err 59 | }); 60 | }); 61 | } 62 | 63 | // production error handler 64 | // no stacktraces leaked to user 65 | app.use(function(err, req, res, next) { 66 | res.status(err.status || 500); 67 | res.render('error', { 68 | message: err.message, 69 | error: {} 70 | }); 71 | }); 72 | 73 | module.exports = app; 74 | -------------------------------------------------------------------------------- /dashboard/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('node:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /dashboard/database/sample-data.sql: -------------------------------------------------------------------------------- 1 | USE `temperature_log`; 2 | 3 | -- Add sensors. 4 | INSERT INTO sensors VALUES(NULL,'Front Bedroom','indoor'); 5 | INSERT INTO sensors VALUES(NULL,'Master Bedroom','indoor'); 6 | INSERT INTO sensors VALUES(NULL,'Outside','outdoor'); 7 | 8 | -- Add temps for first sensor. 9 | INSERT INTO temps VALUES(NULL,1,67.85,UNIX_TIMESTAMP(NOW() - INTERVAL 10 HOUR)); 10 | INSERT INTO temps VALUES(NULL,1,68.25,UNIX_TIMESTAMP(NOW() - INTERVAL 9 HOUR)); 11 | INSERT INTO temps VALUES(NULL,1,68.80,UNIX_TIMESTAMP(NOW() - INTERVAL 8 HOUR)); 12 | INSERT INTO temps VALUES(NULL,1,69.50,UNIX_TIMESTAMP(NOW() - INTERVAL 7 HOUR)); 13 | INSERT INTO temps VALUES(NULL,1,69.90,UNIX_TIMESTAMP(NOW() - INTERVAL 6 HOUR)); 14 | INSERT INTO temps VALUES(NULL,1,70.01,UNIX_TIMESTAMP(NOW() - INTERVAL 5 HOUR)); 15 | INSERT INTO temps VALUES(NULL,1,70.90,UNIX_TIMESTAMP(NOW() - INTERVAL 4 HOUR)); 16 | INSERT INTO temps VALUES(NULL,1,71.02,UNIX_TIMESTAMP(NOW() - INTERVAL 3 HOUR)); 17 | INSERT INTO temps VALUES(NULL,1,71.02,UNIX_TIMESTAMP(NOW() - INTERVAL 2 HOUR)); 18 | INSERT INTO temps VALUES(NULL,1,70.95,UNIX_TIMESTAMP(NOW() - INTERVAL 1 HOUR)); 19 | INSERT INTO temps VALUES(NULL,1,71.20,UNIX_TIMESTAMP()); 20 | 21 | -- Add temps for second sensor. 22 | INSERT INTO temps VALUES(NULL,2,70.90,UNIX_TIMESTAMP(NOW() - INTERVAL 10 HOUR)); 23 | INSERT INTO temps VALUES(NULL,2,70.01,UNIX_TIMESTAMP(NOW() - INTERVAL 9 HOUR)); 24 | INSERT INTO temps VALUES(NULL,2,69.67,UNIX_TIMESTAMP(NOW() - INTERVAL 8 HOUR)); 25 | INSERT INTO temps VALUES(NULL,2,69.50,UNIX_TIMESTAMP(NOW() - INTERVAL 7 HOUR)); 26 | INSERT INTO temps VALUES(NULL,2,68.90,UNIX_TIMESTAMP(NOW() - INTERVAL 6 HOUR)); 27 | INSERT INTO temps VALUES(NULL,2,67.80,UNIX_TIMESTAMP(NOW() - INTERVAL 5 HOUR)); 28 | INSERT INTO temps VALUES(NULL,2,67.70,UNIX_TIMESTAMP(NOW() - INTERVAL 4 HOUR)); 29 | INSERT INTO temps VALUES(NULL,2,67.65,UNIX_TIMESTAMP(NOW() - INTERVAL 3 HOUR)); 30 | INSERT INTO temps VALUES(NULL,2,67.40,UNIX_TIMESTAMP(NOW() - INTERVAL 2 HOUR)); 31 | INSERT INTO temps VALUES(NULL,2,67.32,UNIX_TIMESTAMP(NOW() - INTERVAL 1 HOUR)); 32 | INSERT INTO temps VALUES(NULL,2,68.20,UNIX_TIMESTAMP()); 33 | 34 | -- Add temps for third sensor. 35 | INSERT INTO temps VALUES(NULL,3,55.80,UNIX_TIMESTAMP(NOW() - INTERVAL 10 HOUR)); 36 | INSERT INTO temps VALUES(NULL,3,55.95,UNIX_TIMESTAMP(NOW() - INTERVAL 9 HOUR)); 37 | INSERT INTO temps VALUES(NULL,3,57.42,UNIX_TIMESTAMP(NOW() - INTERVAL 8 HOUR)); 38 | INSERT INTO temps VALUES(NULL,3,59.10,UNIX_TIMESTAMP(NOW() - INTERVAL 7 HOUR)); 39 | INSERT INTO temps VALUES(NULL,3,62.80,UNIX_TIMESTAMP(NOW() - INTERVAL 6 HOUR)); 40 | INSERT INTO temps VALUES(NULL,3,62.85,UNIX_TIMESTAMP(NOW() - INTERVAL 5 HOUR)); 41 | INSERT INTO temps VALUES(NULL,3,63.00,UNIX_TIMESTAMP(NOW() - INTERVAL 4 HOUR)); 42 | INSERT INTO temps VALUES(NULL,3,64.65,UNIX_TIMESTAMP(NOW() - INTERVAL 3 HOUR)); 43 | INSERT INTO temps VALUES(NULL,3,63.40,UNIX_TIMESTAMP(NOW() - INTERVAL 2 HOUR)); 44 | INSERT INTO temps VALUES(NULL,3,63.32,UNIX_TIMESTAMP(NOW() - INTERVAL 1 HOUR)); 45 | INSERT INTO temps VALUES(NULL,3,62.20,UNIX_TIMESTAMP()); 46 | -------------------------------------------------------------------------------- /dashboard/database/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS `temperature_log`; 2 | USE `temperature_log`; 3 | CREATE TABLE IF NOT EXISTS `sensors` ( 4 | `id` int UNSIGNED NOT NULL AUTO_INCREMENT, 5 | `location` VARCHAR(128), 6 | `group` VARCHAR(32), 7 | PRIMARY KEY (`id`) 8 | ) ENGINE=InnoDB; 9 | CREATE TABLE IF NOT EXISTS `temps` ( 10 | `id` int UNSIGNED NOT NULL AUTO_INCREMENT, 11 | `sensor` int UNSIGNED NOT NULL, 12 | `temp` DECIMAL(5,2) NOT NULL, 13 | time int UNSIGNED NOT NULL, 14 | PRIMARY KEY (`id`), 15 | INDEX (`sensor`) 16 | ) ENGINE=InnoDB; 17 | -------------------------------------------------------------------------------- /dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "async": "*", 10 | "body-parser": "^1.20.3", 11 | "cookie-parser": "~1.3.3", 12 | "debug": "^2.6.9", 13 | "express": "^5.0.0", 14 | "morgan": "^1.9.1", 15 | "mysql": "*", 16 | "pug": "^3.0.3", 17 | "serve-favicon": "^2.5.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dashboard/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/public/favicon.ico -------------------------------------------------------------------------------- /dashboard/public/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/public/images/loading.gif -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Flot ## 2 | 3 | We welcome all contributions, but following these guidelines results in less 4 | work for us, and a faster and better response. 5 | 6 | ### Issues ### 7 | 8 | Issues are not a way to ask general questions about Flot. If you see unexpected 9 | behavior but are not 100% certain that it is a bug, please try posting to the 10 | [forum](http://groups.google.com/group/flot-graphs) first, and confirm that 11 | what you see is really a Flot problem before creating a new issue for it. When 12 | reporting a bug, please include a working demonstration of the problem, if 13 | possible, or at least a clear description of the options you're using and the 14 | environment (browser and version, jQuery version, other libraries) that you're 15 | running under. 16 | 17 | If you have suggestions for new features, or changes to existing ones, we'd 18 | love to hear them! Please submit each suggestion as a separate new issue. 19 | 20 | If you would like to work on an existing issue, please make sure it is not 21 | already assigned to someone else. If an issue is assigned to someone, that 22 | person has already started working on it. So, pick unassigned issues to prevent 23 | duplicated effort. 24 | 25 | ### Pull Requests ### 26 | 27 | To make merging as easy as possible, please keep these rules in mind: 28 | 29 | 1. Submit new features or architectural changes to the *<version>-work* 30 | branch for the next major release. Submit bug fixes to the master branch. 31 | 32 | 2. Divide larger changes into a series of small, logical commits with 33 | descriptive messages. 34 | 35 | 3. Rebase, if necessary, before submitting your pull request, to reduce the 36 | work we need to do to merge it. 37 | 38 | 4. Format your code according to the style guidelines below. 39 | 40 | ### Flot Style Guidelines ### 41 | 42 | Flot follows the [jQuery Core Style Guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines), 43 | with the following updates and exceptions: 44 | 45 | #### Spacing #### 46 | 47 | Use four-space indents, no tabs. Do not add horizontal space around parameter 48 | lists, loop definitions, or array/object indices. For example: 49 | 50 | ```js 51 | for ( var i = 0; i < data.length; i++ ) { // This block is wrong! 52 | if ( data[ i ] > 1 ) { 53 | data[ i ] = 2; 54 | } 55 | } 56 | 57 | for (var i = 0; i < data.length; i++) { // This block is correct! 58 | if (data[i] > 1) { 59 | data[i] = 2; 60 | } 61 | } 62 | ``` 63 | 64 | #### Comments #### 65 | 66 | Use [jsDoc](http://usejsdoc.org) comments for all file and function headers. 67 | Use // for all inline and block comments, regardless of length. 68 | 69 | All // comment blocks should have an empty line above *and* below them. For 70 | example: 71 | 72 | ```js 73 | var a = 5; 74 | 75 | // We're going to loop here 76 | // TODO: Make this loop faster, better, stronger! 77 | 78 | for (var x = 0; x < 10; x++) {} 79 | ``` 80 | 81 | #### Wrapping #### 82 | 83 | Block comments should be wrapped at 80 characters. 84 | 85 | Code should attempt to wrap at 80 characters, but may run longer if wrapping 86 | would hurt readability more than having to scroll horizontally. This is a 87 | judgement call made on a situational basis. 88 | 89 | Statements containing complex logic should not be wrapped arbitrarily if they 90 | do not exceed 80 characters. For example: 91 | 92 | ```js 93 | if (a == 1 && // This block is wrong! 94 | b == 2 && 95 | c == 3) {} 96 | 97 | if (a == 1 && b == 2 && c == 3) {} // This block is correct! 98 | ``` 99 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/FAQ.md: -------------------------------------------------------------------------------- 1 | ## Frequently asked questions ## 2 | 3 | #### How much data can Flot cope with? #### 4 | 5 | Flot will happily draw everything you send to it so the answer 6 | depends on the browser. The excanvas emulation used for IE (built with 7 | VML) makes IE by far the slowest browser so be sure to test with that 8 | if IE users are in your target group (for large plots in IE, you can 9 | also check out Flashcanvas which may be faster). 10 | 11 | 1000 points is not a problem, but as soon as you start having more 12 | points than the pixel width, you should probably start thinking about 13 | downsampling/aggregation as this is near the resolution limit of the 14 | chart anyway. If you downsample server-side, you also save bandwidth. 15 | 16 | 17 | #### Flot isn't working when I'm using JSON data as source! #### 18 | 19 | Actually, Flot loves JSON data, you just got the format wrong. 20 | Double check that you're not inputting strings instead of numbers, 21 | like [["0", "-2.13"], ["5", "4.3"]]. This is most common mistake, and 22 | the error might not show up immediately because Javascript can do some 23 | conversion automatically. 24 | 25 | 26 | #### Can I export the graph? #### 27 | 28 | You can grab the image rendered by the canvas element used by Flot 29 | as a PNG or JPEG (remember to set a background). Note that it won't 30 | include anything not drawn in the canvas (such as the legend). And it 31 | doesn't work with excanvas which uses VML, but you could try 32 | Flashcanvas. 33 | 34 | 35 | #### The bars are all tiny in time mode? #### 36 | 37 | It's not really possible to determine the bar width automatically. 38 | So you have to set the width with the barWidth option which is NOT in 39 | pixels, but in the units of the x axis (or the y axis for horizontal 40 | bars). For time mode that's milliseconds so the default value of 1 41 | makes the bars 1 millisecond wide. 42 | 43 | 44 | #### Can I use Flot with libraries like Mootools or Prototype? #### 45 | 46 | Yes, Flot supports it out of the box and it's easy! Just use jQuery 47 | instead of $, e.g. call jQuery.plot instead of $.plot and use 48 | jQuery(something) instead of $(something). As a convenience, you can 49 | put in a DOM element for the graph placeholder where the examples and 50 | the API documentation are using jQuery objects. 51 | 52 | Depending on how you include jQuery, you may have to add one line of 53 | code to prevent jQuery from overwriting functions from the other 54 | libraries, see the documentation in jQuery ("Using jQuery with other 55 | libraries") for details. 56 | 57 | 58 | #### Flot doesn't work with [insert name of Javascript UI framework]! #### 59 | 60 | Flot is using standard HTML to make charts. If this is not working, 61 | it's probably because the framework you're using is doing something 62 | weird with the DOM or with the CSS that is interfering with Flot. 63 | 64 | A common problem is that there's display:none on a container until the 65 | user does something. Many tab widgets work this way, and there's 66 | nothing wrong with it - you just can't call Flot inside a display:none 67 | container as explained in the README so you need to hold off the Flot 68 | call until the container is actually displayed (or use 69 | visibility:hidden instead of display:none or move the container 70 | off-screen). 71 | 72 | If you find there's a specific thing we can do to Flot to help, feel 73 | free to submit a bug report. Otherwise, you're welcome to ask for help 74 | on the forum/mailing list, but please don't submit a bug report to 75 | Flot. 76 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2014 IOLA and Ole Laursen 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for generating minified files 2 | 3 | .PHONY: all 4 | 5 | # we cheat and process all .js files instead of an exhaustive list 6 | all: $(patsubst %.js,%.min.js,$(filter-out %.min.js,$(wildcard *.js))) 7 | 8 | %.min.js: %.js 9 | yui-compressor $< -o $@ 10 | 11 | test: 12 | ./node_modules/.bin/jshint *jquery.flot.js 13 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/PLUGINS.md: -------------------------------------------------------------------------------- 1 | ## Writing plugins ## 2 | 3 | All you need to do to make a new plugin is creating an init function 4 | and a set of options (if needed), stuffing it into an object and 5 | putting it in the $.plot.plugins array. For example: 6 | 7 | ```js 8 | function myCoolPluginInit(plot) { 9 | plot.coolstring = "Hello!"; 10 | }; 11 | 12 | $.plot.plugins.push({ init: myCoolPluginInit, options: { ... } }); 13 | 14 | // if $.plot is called, it will return a plot object with the 15 | // attribute "coolstring" 16 | ``` 17 | 18 | Now, given that the plugin might run in many different places, it's 19 | a good idea to avoid leaking names. The usual trick here is wrap the 20 | above lines in an anonymous function which is called immediately, like 21 | this: (function () { inner code ... })(). To make it even more robust 22 | in case $ is not bound to jQuery but some other Javascript library, we 23 | can write it as 24 | 25 | ```js 26 | (function ($) { 27 | // plugin definition 28 | // ... 29 | })(jQuery); 30 | ``` 31 | 32 | There's a complete example below, but you should also check out the 33 | plugins bundled with Flot. 34 | 35 | 36 | ## Complete example ## 37 | 38 | Here is a simple debug plugin which alerts each of the series in the 39 | plot. It has a single option that control whether it is enabled and 40 | how much info to output: 41 | 42 | ```js 43 | (function ($) { 44 | function init(plot) { 45 | var debugLevel = 1; 46 | 47 | function checkDebugEnabled(plot, options) { 48 | if (options.debug) { 49 | debugLevel = options.debug; 50 | plot.hooks.processDatapoints.push(alertSeries); 51 | } 52 | } 53 | 54 | function alertSeries(plot, series, datapoints) { 55 | var msg = "series " + series.label; 56 | if (debugLevel > 1) { 57 | msg += " with " + series.data.length + " points"; 58 | alert(msg); 59 | } 60 | } 61 | 62 | plot.hooks.processOptions.push(checkDebugEnabled); 63 | } 64 | 65 | var options = { debug: 0 }; 66 | 67 | $.plot.plugins.push({ 68 | init: init, 69 | options: options, 70 | name: "simpledebug", 71 | version: "0.1" 72 | }); 73 | })(jQuery); 74 | ``` 75 | 76 | We also define "name" and "version". It's not used by Flot, but might 77 | be helpful for other plugins in resolving dependencies. 78 | 79 | Put the above in a file named "jquery.flot.debug.js", include it in an 80 | HTML page and then it can be used with: 81 | 82 | ```js 83 | $.plot($("#placeholder"), [...], { debug: 2 }); 84 | ``` 85 | 86 | This simple plugin illustrates a couple of points: 87 | 88 | - It uses the anonymous function trick to avoid name pollution. 89 | - It can be enabled/disabled through an option. 90 | - Variables in the init function can be used to store plot-specific 91 | state between the hooks. 92 | 93 | The two last points are important because there may be multiple plots 94 | on the same page, and you'd want to make sure they are not mixed up. 95 | 96 | 97 | ## Shutting down a plugin ## 98 | 99 | Each plot object has a shutdown hook which is run when plot.shutdown() 100 | is called. This usually mostly happens in case another plot is made on 101 | top of an existing one. 102 | 103 | The purpose of the hook is to give you a chance to unbind any event 104 | handlers you've registered and remove any extra DOM things you've 105 | inserted. 106 | 107 | The problem with event handlers is that you can have registered a 108 | handler which is run in some point in the future, e.g. with 109 | setTimeout(). Meanwhile, the plot may have been shutdown and removed, 110 | but because your event handler is still referencing it, it can't be 111 | garbage collected yet, and worse, if your handler eventually runs, it 112 | may overwrite stuff on a completely different plot. 113 | 114 | 115 | ## Some hints on the options ## 116 | 117 | Plugins should always support appropriate options to enable/disable 118 | them because the plugin user may have several plots on the same page 119 | where only one should use the plugin. In most cases it's probably a 120 | good idea if the plugin is turned off rather than on per default, just 121 | like most of the powerful features in Flot. 122 | 123 | If the plugin needs options that are specific to each series, like the 124 | points or lines options in core Flot, you can put them in "series" in 125 | the options object, e.g. 126 | 127 | ```js 128 | var options = { 129 | series: { 130 | downsample: { 131 | algorithm: null, 132 | maxpoints: 1000 133 | } 134 | } 135 | } 136 | ``` 137 | 138 | Then they will be copied by Flot into each series, providing default 139 | values in case none are specified. 140 | 141 | Think hard and long about naming the options. These names are going to 142 | be public API, and code is going to depend on them if the plugin is 143 | successful. 144 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/README.md: -------------------------------------------------------------------------------- 1 | # Flot [![Build status](https://travis-ci.org/flot/flot.png)](https://travis-ci.org/flot/flot) 2 | 3 | ## About ## 4 | 5 | Flot is a Javascript plotting library for jQuery. 6 | Read more at the website: 7 | 8 | Take a look at the the examples in examples/index.html; they should give a good 9 | impression of what Flot can do, and the source code of the examples is probably 10 | the fastest way to learn how to use Flot. 11 | 12 | 13 | ## Installation ## 14 | 15 | Just include the Javascript file after you've included jQuery. 16 | 17 | Generally, all browsers that support the HTML5 canvas tag are 18 | supported. 19 | 20 | For support for Internet Explorer < 9, you can use [Excanvas] 21 | [excanvas], a canvas emulator; this is used in the examples bundled 22 | with Flot. You just include the excanvas script like this: 23 | 24 | ```html 25 | 26 | ``` 27 | 28 | If it's not working on your development IE 6.0, check that it has 29 | support for VML which Excanvas is relying on. It appears that some 30 | stripped down versions used for test environments on virtual machines 31 | lack the VML support. 32 | 33 | You can also try using [Flashcanvas][flashcanvas], which uses Flash to 34 | do the emulation. Although Flash can be a bit slower to load than VML, 35 | if you've got a lot of points, the Flash version can be much faster 36 | overall. Flot contains some wrapper code for activating Excanvas which 37 | Flashcanvas is compatible with. 38 | 39 | You need at least jQuery 1.2.6, but try at least 1.3.2 for interactive 40 | charts because of performance improvements in event handling. 41 | 42 | 43 | ## Basic usage ## 44 | 45 | Create a placeholder div to put the graph in: 46 | 47 | ```html 48 |
49 | ``` 50 | 51 | You need to set the width and height of this div, otherwise the plot 52 | library doesn't know how to scale the graph. You can do it inline like 53 | this: 54 | 55 | ```html 56 |
57 | ``` 58 | 59 | You can also do it with an external stylesheet. Make sure that the 60 | placeholder isn't within something with a display:none CSS property - 61 | in that case, Flot has trouble measuring label dimensions which 62 | results in garbled looks and might have trouble measuring the 63 | placeholder dimensions which is fatal (it'll throw an exception). 64 | 65 | Then when the div is ready in the DOM, which is usually on document 66 | ready, run the plot function: 67 | 68 | ```js 69 | $.plot($("#placeholder"), data, options); 70 | ``` 71 | 72 | Here, data is an array of data series and options is an object with 73 | settings if you want to customize the plot. Take a look at the 74 | examples for some ideas of what to put in or look at the 75 | [API reference](API.md). Here's a quick example that'll draw a line 76 | from (0, 0) to (1, 1): 77 | 78 | ```js 79 | $.plot($("#placeholder"), [ [[0, 0], [1, 1]] ], { yaxis: { max: 1 } }); 80 | ``` 81 | 82 | The plot function immediately draws the chart and then returns a plot 83 | object with a couple of methods. 84 | 85 | 86 | ## What's with the name? ## 87 | 88 | First: it's pronounced with a short o, like "plot". Not like "flawed". 89 | 90 | So "Flot" rhymes with "plot". 91 | 92 | And if you look up "flot" in a Danish-to-English dictionary, some of 93 | the words that come up are "good-looking", "attractive", "stylish", 94 | "smart", "impressive", "extravagant". One of the main goals with Flot 95 | is pretty looks. 96 | 97 | 98 | ## Notes about the examples ## 99 | 100 | In order to have a useful, functional example of time-series plots using time 101 | zones, date.js from [timezone-js][timezone-js] (released under the Apache 2.0 102 | license) and the [Olson][olson] time zone database (released to the public 103 | domain) have been included in the examples directory. They are used in 104 | examples/axes-time-zones/index.html. 105 | 106 | 107 | [excanvas]: http://code.google.com/p/explorercanvas/ 108 | [flashcanvas]: http://code.google.com/p/flashcanvas/ 109 | [timezone-js]: https://github.com/mde/timezone-js 110 | [olson]: http://ftp.iana.org/time-zones 111 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/ajax/data-eu-gdp-growth-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Europe (EU27)", 3 | "data": [[1999, 3.0], [2000, 3.9]] 4 | } 5 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/ajax/data-eu-gdp-growth-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Europe (EU27)", 3 | "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2]] 4 | } 5 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/ajax/data-eu-gdp-growth-3.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Europe (EU27)", 3 | "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5]] 4 | } 5 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/ajax/data-eu-gdp-growth-4.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Europe (EU27)", 3 | "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5], [2005, 2.0], [2006, 3.1]] 4 | } 5 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/ajax/data-eu-gdp-growth-5.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Europe (EU27)", 3 | "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5], [2005, 2.0], [2006, 3.1], [2007, 2.9], [2008, 0.9]] 4 | } 5 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/ajax/data-eu-gdp-growth.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Europe (EU27)", 3 | "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5], [2005, 2.0], [2006, 3.1], [2007, 2.9], [2008, 0.9]] 4 | } 5 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/ajax/data-japan-gdp-growth.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Japan", 3 | "data": [[1999, -0.1], [2000, 2.9], [2001, 0.2], [2002, 0.3], [2003, 1.4], [2004, 2.7], [2005, 1.9], [2006, 2.0], [2007, 2.3], [2008, -0.7]] 4 | } 5 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/ajax/data-usa-gdp-growth.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "USA", 3 | "data": [[1999, 4.4], [2000, 3.7], [2001, 0.8], [2002, 1.6], [2003, 2.5], [2004, 3.6], [2005, 2.9], [2006, 2.8], [2007, 2.0], [2008, 1.1]] 4 | } 5 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/ajax/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: AJAX 6 | 7 | 8 | 9 | 10 | 125 | 126 | 127 | 128 | 131 | 132 |
133 | 134 |
135 |
136 |
137 | 138 |

Example of loading data dynamically with AJAX. Percentage change in GDP (source: Eurostat). Click the buttons below:

139 | 140 |

The data is fetched over HTTP, in this case directly from text files. Usually the URL would point to some web server handler (e.g. a PHP page or Java/.NET/Python/Ruby on Rails handler) that extracts it from a database and serializes it to JSON.

141 | 142 |

143 | 144 | [ see data ] 145 | 146 |

147 | 148 |

149 | 150 | [ see data ] 151 | 152 |

153 | 154 |

155 | 156 | [ see data ] 157 | 158 |

159 | 160 |

If you combine AJAX with setTimeout, you can poll the server for new data.

161 | 162 |

163 | 164 |

165 | 166 |
167 | 168 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/annotating/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Adding Annotations 6 | 7 | 8 | 9 | 10 | 65 | 66 | 67 | 68 | 71 | 72 |
73 | 74 |
75 |
76 |
77 | 78 |

Flot has support for simple background decorations such as lines and rectangles. They can be useful for marking up certain areas. You can easily add any HTML you need with standard DOM manipulation, e.g. for labels. For drawing custom shapes there is also direct access to the canvas.

79 | 80 |
81 | 82 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/axes-interacting/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Interacting with axes 6 | 7 | 8 | 9 | 10 | 71 | 72 | 73 | 74 | 77 | 78 |
79 | 80 |
81 |
82 |
83 | 84 |

With multiple axes, you sometimes need to interact with them. A simple way to do this is to draw the plot, deduce the axis placements and insert a couple of divs on top to catch events.

85 | 86 |

Try clicking an axis.

87 | 88 |

89 | 90 |
91 | 92 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/axes-time-zones/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Time zones 6 | 7 | 8 | 9 | 10 | 11 | 12 | 83 | 84 | 85 | 86 | 89 | 90 |
91 | 92 |

UTC

93 |
94 |
95 |
96 | 97 |

Browser

98 |
99 |
100 |
101 | 102 |

Chicago

103 |
104 |
105 |
106 | 107 |
108 | 109 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/axes-time-zones/tz/backward: -------------------------------------------------------------------------------- 1 | #
  2 | # This file is in the public domain, so clarified as of
  3 | # 2009-05-17 by Arthur David Olson.
  4 | 
  5 | # This file provides links between current names for time zones
  6 | # and their old names.  Many names changed in late 1993.
  7 | 
  8 | Link	Africa/Asmara		Africa/Asmera
  9 | Link	Africa/Bamako		Africa/Timbuktu
 10 | Link	America/Argentina/Catamarca	America/Argentina/ComodRivadavia
 11 | Link	America/Adak		America/Atka
 12 | Link	America/Argentina/Buenos_Aires	America/Buenos_Aires
 13 | Link	America/Argentina/Catamarca	America/Catamarca
 14 | Link	America/Atikokan	America/Coral_Harbour
 15 | Link	America/Argentina/Cordoba	America/Cordoba
 16 | Link	America/Tijuana		America/Ensenada
 17 | Link	America/Indiana/Indianapolis	America/Fort_Wayne
 18 | Link	America/Indiana/Indianapolis	America/Indianapolis
 19 | Link	America/Argentina/Jujuy	America/Jujuy
 20 | Link	America/Indiana/Knox	America/Knox_IN
 21 | Link	America/Kentucky/Louisville	America/Louisville
 22 | Link	America/Argentina/Mendoza	America/Mendoza
 23 | Link	America/Rio_Branco	America/Porto_Acre
 24 | Link	America/Argentina/Cordoba	America/Rosario
 25 | Link	America/St_Thomas	America/Virgin
 26 | Link	Asia/Ashgabat		Asia/Ashkhabad
 27 | Link	Asia/Chongqing		Asia/Chungking
 28 | Link	Asia/Dhaka		Asia/Dacca
 29 | Link	Asia/Kathmandu		Asia/Katmandu
 30 | Link	Asia/Kolkata		Asia/Calcutta
 31 | Link	Asia/Macau		Asia/Macao
 32 | Link	Asia/Jerusalem		Asia/Tel_Aviv
 33 | Link	Asia/Ho_Chi_Minh	Asia/Saigon
 34 | Link	Asia/Thimphu		Asia/Thimbu
 35 | Link	Asia/Makassar		Asia/Ujung_Pandang
 36 | Link	Asia/Ulaanbaatar	Asia/Ulan_Bator
 37 | Link	Atlantic/Faroe		Atlantic/Faeroe
 38 | Link	Europe/Oslo		Atlantic/Jan_Mayen
 39 | Link	Australia/Sydney	Australia/ACT
 40 | Link	Australia/Sydney	Australia/Canberra
 41 | Link	Australia/Lord_Howe	Australia/LHI
 42 | Link	Australia/Sydney	Australia/NSW
 43 | Link	Australia/Darwin	Australia/North
 44 | Link	Australia/Brisbane	Australia/Queensland
 45 | Link	Australia/Adelaide	Australia/South
 46 | Link	Australia/Hobart	Australia/Tasmania
 47 | Link	Australia/Melbourne	Australia/Victoria
 48 | Link	Australia/Perth		Australia/West
 49 | Link	Australia/Broken_Hill	Australia/Yancowinna
 50 | Link	America/Rio_Branco	Brazil/Acre
 51 | Link	America/Noronha		Brazil/DeNoronha
 52 | Link	America/Sao_Paulo	Brazil/East
 53 | Link	America/Manaus		Brazil/West
 54 | Link	America/Halifax		Canada/Atlantic
 55 | Link	America/Winnipeg	Canada/Central
 56 | Link	America/Regina		Canada/East-Saskatchewan
 57 | Link	America/Toronto		Canada/Eastern
 58 | Link	America/Edmonton	Canada/Mountain
 59 | Link	America/St_Johns	Canada/Newfoundland
 60 | Link	America/Vancouver	Canada/Pacific
 61 | Link	America/Regina		Canada/Saskatchewan
 62 | Link	America/Whitehorse	Canada/Yukon
 63 | Link	America/Santiago	Chile/Continental
 64 | Link	Pacific/Easter		Chile/EasterIsland
 65 | Link	America/Havana		Cuba
 66 | Link	Africa/Cairo		Egypt
 67 | Link	Europe/Dublin		Eire
 68 | Link	Europe/London		Europe/Belfast
 69 | Link	Europe/Chisinau		Europe/Tiraspol
 70 | Link	Europe/London		GB
 71 | Link	Europe/London		GB-Eire
 72 | Link	Etc/GMT			GMT+0
 73 | Link	Etc/GMT			GMT-0
 74 | Link	Etc/GMT			GMT0
 75 | Link	Etc/GMT			Greenwich
 76 | Link	Asia/Hong_Kong		Hongkong
 77 | Link	Atlantic/Reykjavik	Iceland
 78 | Link	Asia/Tehran		Iran
 79 | Link	Asia/Jerusalem		Israel
 80 | Link	America/Jamaica		Jamaica
 81 | Link	Asia/Tokyo		Japan
 82 | Link	Pacific/Kwajalein	Kwajalein
 83 | Link	Africa/Tripoli		Libya
 84 | Link	America/Tijuana		Mexico/BajaNorte
 85 | Link	America/Mazatlan	Mexico/BajaSur
 86 | Link	America/Mexico_City	Mexico/General
 87 | Link	Pacific/Auckland	NZ
 88 | Link	Pacific/Chatham		NZ-CHAT
 89 | Link	America/Denver		Navajo
 90 | Link	Asia/Shanghai		PRC
 91 | Link	Pacific/Pago_Pago	Pacific/Samoa
 92 | Link	Pacific/Chuuk		Pacific/Yap
 93 | Link	Pacific/Chuuk		Pacific/Truk
 94 | Link	Pacific/Pohnpei		Pacific/Ponape
 95 | Link	Europe/Warsaw		Poland
 96 | Link	Europe/Lisbon		Portugal
 97 | Link	Asia/Taipei		ROC
 98 | Link	Asia/Seoul		ROK
 99 | Link	Asia/Singapore		Singapore
100 | Link	Europe/Istanbul		Turkey
101 | Link	Etc/UCT			UCT
102 | Link	America/Anchorage	US/Alaska
103 | Link	America/Adak		US/Aleutian
104 | Link	America/Phoenix		US/Arizona
105 | Link	America/Chicago		US/Central
106 | Link	America/Indiana/Indianapolis	US/East-Indiana
107 | Link	America/New_York	US/Eastern
108 | Link	Pacific/Honolulu	US/Hawaii
109 | Link	America/Indiana/Knox	US/Indiana-Starke
110 | Link	America/Detroit		US/Michigan
111 | Link	America/Denver		US/Mountain
112 | Link	America/Los_Angeles	US/Pacific
113 | Link	Pacific/Pago_Pago	US/Samoa
114 | Link	Etc/UTC			UTC
115 | Link	Etc/UTC			Universal
116 | Link	Europe/Moscow		W-SU
117 | Link	Etc/UTC			Zulu
118 | 


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/axes-time-zones/tz/etcetera:
--------------------------------------------------------------------------------
 1 | # 
 2 | # This file is in the public domain, so clarified as of
 3 | # 2009-05-17 by Arthur David Olson.
 4 | 
 5 | # These entries are mostly present for historical reasons, so that
 6 | # people in areas not otherwise covered by the tz files could "zic -l"
 7 | # to a time zone that was right for their area.  These days, the
 8 | # tz files cover almost all the inhabited world, and the only practical
 9 | # need now for the entries that are not on UTC are for ships at sea
10 | # that cannot use POSIX TZ settings.
11 | 
12 | Zone	Etc/GMT		0	-	GMT
13 | Zone	Etc/UTC		0	-	UTC
14 | Zone	Etc/UCT		0	-	UCT
15 | 
16 | # The following link uses older naming conventions,
17 | # but it belongs here, not in the file `backward',
18 | # as functions like gmtime load the "GMT" file to handle leap seconds properly.
19 | # We want this to work even on installations that omit the other older names.
20 | Link	Etc/GMT				GMT
21 | 
22 | Link	Etc/UTC				Etc/Universal
23 | Link	Etc/UTC				Etc/Zulu
24 | 
25 | Link	Etc/GMT				Etc/Greenwich
26 | Link	Etc/GMT				Etc/GMT-0
27 | Link	Etc/GMT				Etc/GMT+0
28 | Link	Etc/GMT				Etc/GMT0
29 | 
30 | # We use POSIX-style signs in the Zone names and the output abbreviations,
31 | # even though this is the opposite of what many people expect.
32 | # POSIX has positive signs west of Greenwich, but many people expect
33 | # positive signs east of Greenwich.  For example, TZ='Etc/GMT+4' uses
34 | # the abbreviation "GMT+4" and corresponds to 4 hours behind UTC
35 | # (i.e. west of Greenwich) even though many people would expect it to
36 | # mean 4 hours ahead of UTC (i.e. east of Greenwich).
37 | #
38 | # In the draft 5 of POSIX 1003.1-200x, the angle bracket notation allows for
39 | # TZ='+4'; if you want time zone abbreviations conforming to
40 | # ISO 8601 you can use TZ='<-0400>+4'.  Thus the commonly-expected
41 | # offset is kept within the angle bracket (and is used for display)
42 | # while the POSIX sign is kept outside the angle bracket (and is used
43 | # for calculation).
44 | #
45 | # Do not use a TZ setting like TZ='GMT+4', which is four hours behind
46 | # GMT but uses the completely misleading abbreviation "GMT".
47 | 
48 | # Earlier incarnations of this package were not POSIX-compliant,
49 | # and had lines such as
50 | #		Zone	GMT-12		-12	-	GMT-1200
51 | # We did not want things to change quietly if someone accustomed to the old
52 | # way does a
53 | #		zic -l GMT-12
54 | # so we moved the names into the Etc subdirectory.
55 | 
56 | Zone	Etc/GMT-14	14	-	GMT-14	# 14 hours ahead of GMT
57 | Zone	Etc/GMT-13	13	-	GMT-13
58 | Zone	Etc/GMT-12	12	-	GMT-12
59 | Zone	Etc/GMT-11	11	-	GMT-11
60 | Zone	Etc/GMT-10	10	-	GMT-10
61 | Zone	Etc/GMT-9	9	-	GMT-9
62 | Zone	Etc/GMT-8	8	-	GMT-8
63 | Zone	Etc/GMT-7	7	-	GMT-7
64 | Zone	Etc/GMT-6	6	-	GMT-6
65 | Zone	Etc/GMT-5	5	-	GMT-5
66 | Zone	Etc/GMT-4	4	-	GMT-4
67 | Zone	Etc/GMT-3	3	-	GMT-3
68 | Zone	Etc/GMT-2	2	-	GMT-2
69 | Zone	Etc/GMT-1	1	-	GMT-1
70 | Zone	Etc/GMT+1	-1	-	GMT+1
71 | Zone	Etc/GMT+2	-2	-	GMT+2
72 | Zone	Etc/GMT+3	-3	-	GMT+3
73 | Zone	Etc/GMT+4	-4	-	GMT+4
74 | Zone	Etc/GMT+5	-5	-	GMT+5
75 | Zone	Etc/GMT+6	-6	-	GMT+6
76 | Zone	Etc/GMT+7	-7	-	GMT+7
77 | Zone	Etc/GMT+8	-8	-	GMT+8
78 | Zone	Etc/GMT+9	-9	-	GMT+9
79 | Zone	Etc/GMT+10	-10	-	GMT+10
80 | Zone	Etc/GMT+11	-11	-	GMT+11
81 | Zone	Etc/GMT+12	-12	-	GMT+12
82 | 


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/axes-time-zones/tz/factory:
--------------------------------------------------------------------------------
 1 | # 
 2 | # This file is in the public domain, so clarified as of
 3 | # 2009-05-17 by Arthur David Olson.
 4 | 
 5 | # For companies who don't want to put time zone specification in
 6 | # their installation procedures.  When users run date, they'll get the message.
 7 | # Also useful for the "comp.sources" version.
 8 | 
 9 | # Zone	NAME	GMTOFF	RULES	FORMAT
10 | Zone	Factory	0	- "Local time zone must be set--see zic manual page"
11 | 


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/axes-time-zones/tz/iso3166.tab:
--------------------------------------------------------------------------------
  1 | # 
  2 | # This file is in the public domain, so clarified as of
  3 | # 2009-05-17 by Arthur David Olson.
  4 | # ISO 3166 alpha-2 country codes
  5 | #
  6 | # From Paul Eggert (2006-09-27):
  7 | #
  8 | # This file contains a table with the following columns:
  9 | # 1.  ISO 3166-1 alpha-2 country code, current as of
 10 | #     ISO 3166-1 Newsletter VI-1 (2007-09-21).  See:
 11 | #     
 12 | #     ISO 3166 Maintenance agency (ISO 3166/MA)
 13 | #     .
 14 | # 2.  The usual English name for the country,
 15 | #     chosen so that alphabetic sorting of subsets produces helpful lists.
 16 | #     This is not the same as the English name in the ISO 3166 tables.
 17 | #
 18 | # Columns are separated by a single tab.
 19 | # The table is sorted by country code.
 20 | #
 21 | # Lines beginning with `#' are comments.
 22 | #
 23 | # From Arthur David Olson (2011-08-17):
 24 | # Resynchronized today with the ISO 3166 site (adding SS for South Sudan).
 25 | #
 26 | #country-
 27 | #code	country name
 28 | AD	Andorra
 29 | AE	United Arab Emirates
 30 | AF	Afghanistan
 31 | AG	Antigua & Barbuda
 32 | AI	Anguilla
 33 | AL	Albania
 34 | AM	Armenia
 35 | AO	Angola
 36 | AQ	Antarctica
 37 | AR	Argentina
 38 | AS	Samoa (American)
 39 | AT	Austria
 40 | AU	Australia
 41 | AW	Aruba
 42 | AX	Aaland Islands
 43 | AZ	Azerbaijan
 44 | BA	Bosnia & Herzegovina
 45 | BB	Barbados
 46 | BD	Bangladesh
 47 | BE	Belgium
 48 | BF	Burkina Faso
 49 | BG	Bulgaria
 50 | BH	Bahrain
 51 | BI	Burundi
 52 | BJ	Benin
 53 | BL	St Barthelemy
 54 | BM	Bermuda
 55 | BN	Brunei
 56 | BO	Bolivia
 57 | BQ	Bonaire Sint Eustatius & Saba
 58 | BR	Brazil
 59 | BS	Bahamas
 60 | BT	Bhutan
 61 | BV	Bouvet Island
 62 | BW	Botswana
 63 | BY	Belarus
 64 | BZ	Belize
 65 | CA	Canada
 66 | CC	Cocos (Keeling) Islands
 67 | CD	Congo (Dem. Rep.)
 68 | CF	Central African Rep.
 69 | CG	Congo (Rep.)
 70 | CH	Switzerland
 71 | CI	Cote d'Ivoire
 72 | CK	Cook Islands
 73 | CL	Chile
 74 | CM	Cameroon
 75 | CN	China
 76 | CO	Colombia
 77 | CR	Costa Rica
 78 | CU	Cuba
 79 | CV	Cape Verde
 80 | CW	Curacao
 81 | CX	Christmas Island
 82 | CY	Cyprus
 83 | CZ	Czech Republic
 84 | DE	Germany
 85 | DJ	Djibouti
 86 | DK	Denmark
 87 | DM	Dominica
 88 | DO	Dominican Republic
 89 | DZ	Algeria
 90 | EC	Ecuador
 91 | EE	Estonia
 92 | EG	Egypt
 93 | EH	Western Sahara
 94 | ER	Eritrea
 95 | ES	Spain
 96 | ET	Ethiopia
 97 | FI	Finland
 98 | FJ	Fiji
 99 | FK	Falkland Islands
100 | FM	Micronesia
101 | FO	Faroe Islands
102 | FR	France
103 | GA	Gabon
104 | GB	Britain (UK)
105 | GD	Grenada
106 | GE	Georgia
107 | GF	French Guiana
108 | GG	Guernsey
109 | GH	Ghana
110 | GI	Gibraltar
111 | GL	Greenland
112 | GM	Gambia
113 | GN	Guinea
114 | GP	Guadeloupe
115 | GQ	Equatorial Guinea
116 | GR	Greece
117 | GS	South Georgia & the South Sandwich Islands
118 | GT	Guatemala
119 | GU	Guam
120 | GW	Guinea-Bissau
121 | GY	Guyana
122 | HK	Hong Kong
123 | HM	Heard Island & McDonald Islands
124 | HN	Honduras
125 | HR	Croatia
126 | HT	Haiti
127 | HU	Hungary
128 | ID	Indonesia
129 | IE	Ireland
130 | IL	Israel
131 | IM	Isle of Man
132 | IN	India
133 | IO	British Indian Ocean Territory
134 | IQ	Iraq
135 | IR	Iran
136 | IS	Iceland
137 | IT	Italy
138 | JE	Jersey
139 | JM	Jamaica
140 | JO	Jordan
141 | JP	Japan
142 | KE	Kenya
143 | KG	Kyrgyzstan
144 | KH	Cambodia
145 | KI	Kiribati
146 | KM	Comoros
147 | KN	St Kitts & Nevis
148 | KP	Korea (North)
149 | KR	Korea (South)
150 | KW	Kuwait
151 | KY	Cayman Islands
152 | KZ	Kazakhstan
153 | LA	Laos
154 | LB	Lebanon
155 | LC	St Lucia
156 | LI	Liechtenstein
157 | LK	Sri Lanka
158 | LR	Liberia
159 | LS	Lesotho
160 | LT	Lithuania
161 | LU	Luxembourg
162 | LV	Latvia
163 | LY	Libya
164 | MA	Morocco
165 | MC	Monaco
166 | MD	Moldova
167 | ME	Montenegro
168 | MF	St Martin (French part)
169 | MG	Madagascar
170 | MH	Marshall Islands
171 | MK	Macedonia
172 | ML	Mali
173 | MM	Myanmar (Burma)
174 | MN	Mongolia
175 | MO	Macau
176 | MP	Northern Mariana Islands
177 | MQ	Martinique
178 | MR	Mauritania
179 | MS	Montserrat
180 | MT	Malta
181 | MU	Mauritius
182 | MV	Maldives
183 | MW	Malawi
184 | MX	Mexico
185 | MY	Malaysia
186 | MZ	Mozambique
187 | NA	Namibia
188 | NC	New Caledonia
189 | NE	Niger
190 | NF	Norfolk Island
191 | NG	Nigeria
192 | NI	Nicaragua
193 | NL	Netherlands
194 | NO	Norway
195 | NP	Nepal
196 | NR	Nauru
197 | NU	Niue
198 | NZ	New Zealand
199 | OM	Oman
200 | PA	Panama
201 | PE	Peru
202 | PF	French Polynesia
203 | PG	Papua New Guinea
204 | PH	Philippines
205 | PK	Pakistan
206 | PL	Poland
207 | PM	St Pierre & Miquelon
208 | PN	Pitcairn
209 | PR	Puerto Rico
210 | PS	Palestine
211 | PT	Portugal
212 | PW	Palau
213 | PY	Paraguay
214 | QA	Qatar
215 | RE	Reunion
216 | RO	Romania
217 | RS	Serbia
218 | RU	Russia
219 | RW	Rwanda
220 | SA	Saudi Arabia
221 | SB	Solomon Islands
222 | SC	Seychelles
223 | SD	Sudan
224 | SE	Sweden
225 | SG	Singapore
226 | SH	St Helena
227 | SI	Slovenia
228 | SJ	Svalbard & Jan Mayen
229 | SK	Slovakia
230 | SL	Sierra Leone
231 | SM	San Marino
232 | SN	Senegal
233 | SO	Somalia
234 | SR	Suriname
235 | SS	South Sudan
236 | ST	Sao Tome & Principe
237 | SV	El Salvador
238 | SX	Sint Maarten
239 | SY	Syria
240 | SZ	Swaziland
241 | TC	Turks & Caicos Is
242 | TD	Chad
243 | TF	French Southern & Antarctic Lands
244 | TG	Togo
245 | TH	Thailand
246 | TJ	Tajikistan
247 | TK	Tokelau
248 | TL	East Timor
249 | TM	Turkmenistan
250 | TN	Tunisia
251 | TO	Tonga
252 | TR	Turkey
253 | TT	Trinidad & Tobago
254 | TV	Tuvalu
255 | TW	Taiwan
256 | TZ	Tanzania
257 | UA	Ukraine
258 | UG	Uganda
259 | UM	US minor outlying islands
260 | US	United States
261 | UY	Uruguay
262 | UZ	Uzbekistan
263 | VA	Vatican City
264 | VC	St Vincent
265 | VE	Venezuela
266 | VG	Virgin Islands (UK)
267 | VI	Virgin Islands (US)
268 | VN	Vietnam
269 | VU	Vanuatu
270 | WF	Wallis & Futuna
271 | WS	Samoa (western)
272 | YE	Yemen
273 | YT	Mayotte
274 | ZA	South Africa
275 | ZM	Zambia
276 | ZW	Zimbabwe
277 | 


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/axes-time-zones/tz/leapseconds:
--------------------------------------------------------------------------------
  1 | # 
  2 | # This file is in the public domain, so clarified as of
  3 | # 2009-05-17 by Arthur David Olson.
  4 | 
  5 | # Allowance for leapseconds added to each timezone file.
  6 | 
  7 | # The International Earth Rotation Service periodically uses leap seconds
  8 | # to keep UTC to within 0.9 s of UT1
  9 | # (which measures the true angular orientation of the earth in space); see
 10 | # Terry J Quinn, The BIPM and the accurate measure of time,
 11 | # Proc IEEE 79, 7 (July 1991), 894-905.
 12 | # There were no leap seconds before 1972, because the official mechanism
 13 | # accounting for the discrepancy between atomic time and the earth's rotation
 14 | # did not exist until the early 1970s.
 15 | 
 16 | # The correction (+ or -) is made at the given time, so lines
 17 | # will typically look like:
 18 | #	Leap	YEAR	MON	DAY	23:59:60	+	R/S
 19 | # or
 20 | #	Leap	YEAR	MON	DAY	23:59:59	-	R/S
 21 | 
 22 | # If the leapsecond is Rolling (R) the given time is local time
 23 | # If the leapsecond is Stationary (S) the given time is UTC
 24 | 
 25 | # Leap	YEAR	MONTH	DAY	HH:MM:SS	CORR	R/S
 26 | Leap	1972	Jun	30	23:59:60	+	S
 27 | Leap	1972	Dec	31	23:59:60	+	S
 28 | Leap	1973	Dec	31	23:59:60	+	S
 29 | Leap	1974	Dec	31	23:59:60	+	S
 30 | Leap	1975	Dec	31	23:59:60	+	S
 31 | Leap	1976	Dec	31	23:59:60	+	S
 32 | Leap	1977	Dec	31	23:59:60	+	S
 33 | Leap	1978	Dec	31	23:59:60	+	S
 34 | Leap	1979	Dec	31	23:59:60	+	S
 35 | Leap	1981	Jun	30	23:59:60	+	S
 36 | Leap	1982	Jun	30	23:59:60	+	S
 37 | Leap	1983	Jun	30	23:59:60	+	S
 38 | Leap	1985	Jun	30	23:59:60	+	S
 39 | Leap	1987	Dec	31	23:59:60	+	S
 40 | Leap	1989	Dec	31	23:59:60	+	S
 41 | Leap	1990	Dec	31	23:59:60	+	S
 42 | Leap	1992	Jun	30	23:59:60	+	S
 43 | Leap	1993	Jun	30	23:59:60	+	S
 44 | Leap	1994	Jun	30	23:59:60	+	S
 45 | Leap	1995	Dec	31	23:59:60	+	S
 46 | Leap	1997	Jun	30	23:59:60	+	S
 47 | Leap	1998	Dec	31	23:59:60	+	S
 48 | Leap	2005	Dec	31	23:59:60	+	S
 49 | Leap	2008	Dec	31	23:59:60	+	S
 50 | Leap	2012	Jun	30	23:59:60	+	S
 51 | 
 52 | # INTERNATIONAL EARTH ROTATION AND REFERENCE SYSTEMS SERVICE (IERS)
 53 | #
 54 | # SERVICE INTERNATIONAL DE LA ROTATION TERRESTRE ET DES SYSTEMES DE REFERENCE
 55 | #
 56 | #
 57 | # SERVICE DE LA ROTATION TERRESTRE
 58 | # OBSERVATOIRE DE PARIS
 59 | # 61, Av. de l'Observatoire 75014 PARIS (France)
 60 | # Tel.      : 33 (0) 1 40 51 22 26
 61 | # FAX       : 33 (0) 1 40 51 22 91
 62 | # e-mail    : (E-Mail Removed)
 63 | # http://hpiers.obspm.fr/eop-pc
 64 | #
 65 | # Paris, 5 January 2012
 66 | #
 67 | #
 68 | # Bulletin C 43
 69 | #
 70 | # To authorities responsible
 71 | # for the measurement and
 72 | # distribution of time
 73 | #
 74 | #
 75 | # UTC TIME STEP
 76 | # on the 1st of July 2012
 77 | #
 78 | #
 79 | # A positive leap second will be introduced at the end of June 2012.
 80 | # The sequence of dates of the UTC second markers will be:
 81 | #
 82 | #                          2012 June 30,     23h 59m 59s
 83 | #                          2012 June 30,     23h 59m 60s
 84 | #                          2012 July  1,      0h  0m  0s
 85 | #
 86 | # The difference between UTC and the International Atomic Time TAI is:
 87 | #
 88 | # from 2009 January 1, 0h UTC, to 2012 July 1  0h UTC  : UTC-TAI = - 34s
 89 | # from 2012 July 1,    0h UTC, until further notice    : UTC-TAI = - 35s
 90 | #
 91 | # Leap seconds can be introduced in UTC at the end of the months of December
 92 | # or June, depending on the evolution of UT1-TAI. Bulletin C is mailed every
 93 | # six months, either to announce a time step in UTC or to confirm that there
 94 | # will be no time step at the next possible date.
 95 | #
 96 | #
 97 | # Daniel GAMBIS
 98 | # Head
 99 | # Earth Orientation Center of IERS
100 | # Observatoire de Paris, France
101 | 


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/axes-time-zones/tz/pacificnew:
--------------------------------------------------------------------------------
 1 | # 
 2 | # This file is in the public domain, so clarified as of
 3 | # 2009-05-17 by Arthur David Olson.
 4 | 
 5 | # From Arthur David Olson (1989-04-05):
 6 | # On 1989-04-05, the U. S. House of Representatives passed (238-154) a bill
 7 | # establishing "Pacific Presidential Election Time"; it was not acted on
 8 | # by the Senate or signed into law by the President.
 9 | # You might want to change the "PE" (Presidential Election) below to
10 | # "Q" (Quadrennial) to maintain three-character zone abbreviations.
11 | # If you're really conservative, you might want to change it to "D".
12 | # Avoid "L" (Leap Year), which won't be true in 2100.
13 | 
14 | # If Presidential Election Time is ever established, replace "XXXX" below
15 | # with the year the law takes effect and uncomment the "##" lines.
16 | 
17 | # Rule	NAME	FROM	TO	TYPE	IN	ON	AT	SAVE	LETTER/S
18 | ## Rule	Twilite	XXXX	max	-	Apr	Sun>=1	2:00	1:00	D
19 | ## Rule	Twilite	XXXX	max	uspres	Oct	lastSun	2:00	1:00	PE
20 | ## Rule	Twilite	XXXX	max	uspres	Nov	Sun>=7	2:00	0	S
21 | ## Rule	Twilite	XXXX	max	nonpres	Oct	lastSun	2:00	0	S
22 | 
23 | # Zone	NAME			GMTOFF	RULES/SAVE	FORMAT	[UNTIL]
24 | ## Zone	America/Los_Angeles-PET	-8:00	US		P%sT	XXXX
25 | ##				-8:00	Twilite		P%sT
26 | 
27 | # For now...
28 | Link	America/Los_Angeles	US/Pacific-New	##
29 | 


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/axes-time-zones/tz/southamerica:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/public/javascripts/flot/examples/axes-time-zones/tz/southamerica


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/axes-time-zones/tz/systemv:
--------------------------------------------------------------------------------
 1 | # 
 2 | # This file is in the public domain, so clarified as of
 3 | # 2009-05-17 by Arthur David Olson.
 4 | 
 5 | # Old rules, should the need arise.
 6 | # No attempt is made to handle Newfoundland, since it cannot be expressed
 7 | # using the System V "TZ" scheme (half-hour offset), or anything outside
 8 | # North America (no support for non-standard DST start/end dates), nor
 9 | # the changes in the DST rules in the US after 1976 (which occurred after
10 | # the old rules were written).
11 | #
12 | # If you need the old rules, uncomment ## lines.
13 | # Compile this *without* leap second correction for true conformance.
14 | 
15 | # Rule	NAME	FROM	TO	TYPE	IN	ON	AT	SAVE	LETTER/S
16 | Rule	SystemV	min	1973	-	Apr	lastSun	2:00	1:00	D
17 | Rule	SystemV	min	1973	-	Oct	lastSun	2:00	0	S
18 | Rule	SystemV	1974	only	-	Jan	6	2:00	1:00	D
19 | Rule	SystemV	1974	only	-	Nov	lastSun	2:00	0	S
20 | Rule	SystemV	1975	only	-	Feb	23	2:00	1:00	D
21 | Rule	SystemV	1975	only	-	Oct	lastSun	2:00	0	S
22 | Rule	SystemV	1976	max	-	Apr	lastSun	2:00	1:00	D
23 | Rule	SystemV	1976	max	-	Oct	lastSun	2:00	0	S
24 | 
25 | # Zone	NAME		GMTOFF	RULES/SAVE	FORMAT	[UNTIL]
26 | ## Zone	SystemV/AST4ADT	-4:00	SystemV		A%sT
27 | ## Zone	SystemV/EST5EDT	-5:00	SystemV		E%sT
28 | ## Zone	SystemV/CST6CDT	-6:00	SystemV		C%sT
29 | ## Zone	SystemV/MST7MDT	-7:00	SystemV		M%sT
30 | ## Zone	SystemV/PST8PDT	-8:00	SystemV		P%sT
31 | ## Zone	SystemV/YST9YDT	-9:00	SystemV		Y%sT
32 | ## Zone	SystemV/AST4	-4:00	-		AST
33 | ## Zone	SystemV/EST5	-5:00	-		EST
34 | ## Zone	SystemV/CST6	-6:00	-		CST
35 | ## Zone	SystemV/MST7	-7:00	-		MST
36 | ## Zone	SystemV/PST8	-8:00	-		PST
37 | ## Zone	SystemV/YST9	-9:00	-		YST
38 | ## Zone	SystemV/HST10	-10:00	-		HST
39 | 


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/axes-time-zones/tz/yearistype.sh:
--------------------------------------------------------------------------------
 1 | #! /bin/sh
 2 | 
 3 | : 'This file is in the public domain, so clarified as of'
 4 | : '2006-07-17 by Arthur David Olson.'
 5 | 
 6 | case $#-$1 in
 7 | 	2-|2-0*|2-*[!0-9]*)
 8 | 		echo "$0: wild year - $1" >&2
 9 | 		exit 1 ;;
10 | esac
11 | 
12 | case $#-$2 in
13 | 	2-even)
14 | 		case $1 in
15 | 			*[24680])			exit 0 ;;
16 | 			*)				exit 1 ;;
17 | 		esac ;;
18 | 	2-nonpres|2-nonuspres)
19 | 		case $1 in
20 | 			*[02468][048]|*[13579][26])	exit 1 ;;
21 | 			*)				exit 0 ;;
22 | 		esac ;;
23 | 	2-odd)
24 | 		case $1 in
25 | 			*[13579])			exit 0 ;;
26 | 			*)				exit 1 ;;
27 | 		esac ;;
28 | 	2-uspres)
29 | 		case $1 in
30 | 			*[02468][048]|*[13579][26])	exit 0 ;;
31 | 			*)				exit 1 ;;
32 | 		esac ;;
33 | 	2-*)
34 | 		echo "$0: wild type - $2" >&2 ;;
35 | esac
36 | 
37 | echo "$0: usage is $0 year even|odd|uspres|nonpres|nonuspres" >&2
38 | exit 1
39 | 


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/public/javascripts/flot/examples/background.png


--------------------------------------------------------------------------------
/dashboard/public/javascripts/flot/examples/basic-options/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 	
 5 | 	Flot Examples: Basic Options
 6 | 	
 7 | 	
 8 | 	
 9 | 	
10 | 	
67 | 
68 | 
69 | 
70 | 	
73 | 
74 | 	
75 | 76 |
77 |
78 |
79 | 80 |

There are plenty of options you can set to control the precise looks of your plot. You can control the ticks on the axes, the legend, the graph type, etc.

81 | 82 |

Flot goes to great lengths to provide sensible defaults so that you don't have to customize much for a good-looking result.

83 | 84 |
85 | 86 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/basic-usage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Basic Usage 6 | 7 | 8 | 9 | 10 | 33 | 34 | 35 | 36 | 39 | 40 |
41 | 42 |
43 |
44 |
45 | 46 |

You don't have to do much to get an attractive plot. Create a placeholder, make sure it has dimensions (so Flot knows at what size to draw the plot), then call the plot function with your data.

47 | 48 |

The axes are automatically scaled.

49 | 50 |
51 | 52 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/categories/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Categories 6 | 7 | 8 | 9 | 10 | 11 | 37 | 38 | 39 | 40 | 43 | 44 |
45 | 46 |
47 |
48 |
49 | 50 |

With the categories plugin you can plot categories/textual data easily.

51 | 52 |
53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/examples.css: -------------------------------------------------------------------------------- 1 | * { padding: 0; margin: 0; vertical-align: top; } 2 | 3 | body { 4 | background: url(background.png) repeat-x; 5 | font: 18px/1.5em "proxima-nova", Helvetica, Arial, sans-serif; 6 | } 7 | 8 | a { color: #069; } 9 | a:hover { color: #28b; } 10 | 11 | h2 { 12 | margin-top: 15px; 13 | font: normal 32px "omnes-pro", Helvetica, Arial, sans-serif; 14 | } 15 | 16 | h3 { 17 | margin-left: 30px; 18 | font: normal 26px "omnes-pro", Helvetica, Arial, sans-serif; 19 | color: #666; 20 | } 21 | 22 | p { 23 | margin-top: 10px; 24 | } 25 | 26 | button { 27 | font-size: 18px; 28 | padding: 1px 7px; 29 | } 30 | 31 | input { 32 | font-size: 18px; 33 | } 34 | 35 | input[type=checkbox] { 36 | margin: 7px; 37 | } 38 | 39 | #header { 40 | position: relative; 41 | width: 900px; 42 | margin: auto; 43 | } 44 | 45 | #header h2 { 46 | margin-left: 10px; 47 | vertical-align: middle; 48 | font-size: 42px; 49 | font-weight: bold; 50 | text-decoration: none; 51 | color: #000; 52 | } 53 | 54 | #content { 55 | width: 880px; 56 | margin: 0 auto; 57 | padding: 10px; 58 | } 59 | 60 | #footer { 61 | margin-top: 25px; 62 | margin-bottom: 10px; 63 | text-align: center; 64 | font-size: 12px; 65 | color: #999; 66 | } 67 | 68 | .demo-container { 69 | box-sizing: border-box; 70 | width: 850px; 71 | height: 450px; 72 | padding: 20px 15px 15px 15px; 73 | margin: 15px auto 30px auto; 74 | border: 1px solid #ddd; 75 | background: #fff; 76 | background: linear-gradient(#f6f6f6 0, #fff 50px); 77 | background: -o-linear-gradient(#f6f6f6 0, #fff 50px); 78 | background: -ms-linear-gradient(#f6f6f6 0, #fff 50px); 79 | background: -moz-linear-gradient(#f6f6f6 0, #fff 50px); 80 | background: -webkit-linear-gradient(#f6f6f6 0, #fff 50px); 81 | box-shadow: 0 3px 10px rgba(0,0,0,0.15); 82 | -o-box-shadow: 0 3px 10px rgba(0,0,0,0.1); 83 | -ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1); 84 | -moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1); 85 | -webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1); 86 | } 87 | 88 | .demo-placeholder { 89 | width: 100%; 90 | height: 100%; 91 | font-size: 14px; 92 | line-height: 1.2em; 93 | } 94 | 95 | .legend table { 96 | border-spacing: 5px; 97 | } -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/image/hs-2004-27-a-large-web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/public/javascripts/flot/examples/image/hs-2004-27-a-large-web.jpg -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/image/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Image Plots 6 | 7 | 8 | 9 | 10 | 11 | 43 | 44 | 45 | 46 | 49 | 50 |
51 | 52 |
53 |
54 |
55 | 56 |

The Cat's Eye Nebula (picture from Hubble).

57 | 58 |

With the image plugin, you can plot static images against a set of axes. This is for useful for adding ticks to complex prerendered visualizations. Instead of inputting data points, you specify the images and where their two opposite corners are supposed to be in plot space.

59 | 60 |

Images represent a little further complication because you need to make sure they are loaded before you can use them (Flot skips incomplete images). The plugin comes with a couple of helpers for doing that.

61 | 62 |
63 | 64 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples 6 | 7 | 15 | 16 | 17 | 27 | 28 | 29 | 30 | 33 | 34 |
35 | 36 |

Here are some examples for Flot, the Javascript charting library for jQuery:

37 | 38 |

Basic Usage

39 | 40 | 46 | 47 |

Interactivity

48 | 49 | 56 | 57 |

Additional Features

58 | 59 | 72 | 73 |
74 | 75 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/interacting/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Interactivity 6 | 7 | 8 | 9 | 10 | 87 | 88 | 89 | 92 | 93 |
94 | 95 |
96 |
97 |
98 | 99 |

One of the goals of Flot is to support user interactions. Try pointing and clicking on the points.

100 | 101 |

102 | 103 | 104 | 105 |

106 | 107 |

A tooltip is easy to build with a bit of jQuery code and the data returned from the plot.

108 | 109 |

110 | 111 |
112 | 113 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/navigate/arrow-down.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/public/javascripts/flot/examples/navigate/arrow-down.gif -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/navigate/arrow-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/public/javascripts/flot/examples/navigate/arrow-left.gif -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/navigate/arrow-right.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/public/javascripts/flot/examples/navigate/arrow-right.gif -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/navigate/arrow-up.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/public/javascripts/flot/examples/navigate/arrow-up.gif -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/navigate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Navigation 6 | 7 | 26 | 27 | 28 | 29 | 30 | 127 | 128 | 129 | 130 | 133 | 134 |
135 | 136 |
137 |
138 |
139 | 140 |

141 | 142 |

With the navigate plugin it is easy to add panning and zooming. Drag to pan, double click to zoom (or use the mouse scrollwheel).

143 | 144 |

The plugin fires events (useful for synchronizing several plots) and adds a couple of public methods so you can easily build a little user interface around it, like the little buttons at the top right in the plot.

145 | 146 |
147 | 148 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/realtime/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Real-time updates 6 | 7 | 8 | 9 | 10 | 98 | 99 | 100 | 101 | 104 | 105 |
106 | 107 |
108 |
109 |
110 | 111 |

You can update a chart periodically to get a real-time effect by using a timer to insert the new data in the plot and redraw it.

112 | 113 |

Time between updates: milliseconds

114 | 115 |
116 | 117 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/resize/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Resizing 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 50 | 51 | 52 | 53 | 56 | 57 |
58 | 59 |
60 |
61 |
62 | 63 |

64 | 65 |

Sometimes it makes more sense to just let the plot take up the available space. In that case, we need to redraw the plot each time the placeholder changes its size. If you include the resize plugin, this is handled automatically.

66 | 67 |

Drag the bottom and right sides of the plot to resize it.

68 | 69 |
70 | 71 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/selection/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Selection 6 | 7 | 8 | 9 | 10 | 11 | 115 | 116 | 117 | 118 | 121 | 122 |
123 | 124 |
125 |
126 |
127 | 128 |

1000 kg. CO2 emissions per year per capita for various countries (source: Wikipedia).

129 | 130 |

Flot supports selections through the selection plugin. You can enable rectangular selection or one-dimensional selection if the user should only be able to select on one axis. Try left-click and drag on the plot above where selection on the x axis is enabled.

131 | 132 |

You selected:

133 | 134 |

The plot command returns a plot object you can use to control the selection. Click the buttons below.

135 | 136 |

137 | 138 | 139 |

140 | 141 |

Selections are really useful for zooming. Just replot the chart with min and max values for the axes set to the values in the "plotselected" event triggered. Enable the checkbox below and select a region again.

142 | 143 |

144 | 145 |
146 | 147 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/series-errorbars/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Error Bars 6 | 7 | 8 | 9 | 10 | 11 | 12 | 128 | 129 | 130 | 131 | 134 | 135 |
136 | 137 |
138 |
139 |
140 | 141 |

With the errorbars plugin you can plot error bars to show standard deviation and other useful statistical properties.

142 | 143 |
144 | 145 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/series-toggle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Toggling Series 6 | 7 | 8 | 9 | 10 | 96 | 97 | 98 | 99 | 102 | 103 |
104 | 105 |
106 |
107 |

108 |
109 | 110 |

This example shows military budgets for various countries in constant (2005) million US dollars (source: SIPRI).

111 | 112 |

Since all data is available client-side, it's pretty easy to make the plot interactive. Try turning countries on and off with the checkboxes next to the plot.

113 | 114 |
115 | 116 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/series-types/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Series Types 6 | 7 | 8 | 9 | 10 | 68 | 69 | 70 | 71 | 74 | 75 |
76 | 77 |
78 |
79 |
80 | 81 |

Flot supports lines, points, filled areas, bars and any combinations of these, in the same plot and even on the same data series.

82 | 83 |
84 | 85 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/shared/jquery-ui/jquery-ui.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.10.0 - 2013-01-26 2 | * http://jqueryui.com 3 | * Includes: jquery.ui.core.css, jquery.ui.resizable.css 4 | * Copyright (c) 2013 jQuery Foundation and other contributors Licensed MIT */ 5 | 6 | .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px} -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/stacking/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Stacking 6 | 7 | 8 | 9 | 10 | 11 | 74 | 75 | 76 | 77 | 80 | 81 |
82 | 83 |
84 |
85 |
86 | 87 |

With the stack plugin, you can have Flot stack the series. This is useful if you wish to display both a total and the constituents it is made of. The only requirement is that you provide the input sorted on x.

88 | 89 |

90 | 91 | 92 |

93 | 94 |

95 | 96 | 97 | 98 |

99 | 100 |
101 | 102 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/symbols/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Symbols 6 | 7 | 8 | 9 | 10 | 11 | 54 | 55 | 56 | 57 | 60 | 61 |
62 | 63 |
64 |
65 |
66 | 67 |

Points can be marked in several ways, with circles being the built-in default. For other point types, you can define a callback function to draw the symbol. Some common symbols are available in the symbol plugin.

68 | 69 |
70 | 71 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/threshold/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Thresholds 6 | 7 | 8 | 9 | 10 | 11 | 48 | 49 | 50 | 51 | 54 | 55 |
56 | 57 |
58 |
59 |
60 | 61 |

With the threshold plugin, you can apply a specific color to the part of a data series below a threshold. This is can be useful for highlighting negative values, e.g. when displaying net results or what's in stock.

62 | 63 |

64 | 65 | 66 | 67 |

68 | 69 |
70 | 71 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/tracking/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Tracking 6 | 7 | 8 | 9 | 10 | 11 | 109 | 110 | 111 | 112 | 115 | 116 |
117 | 118 |
119 |
120 |
121 | 122 |

You can add crosshairs that'll track the mouse position, either on both axes or as here on only one.

123 | 124 |

If you combine it with listening on hover events, you can use it to track the intersection on the curves by interpolating the data points (look at the legend).

125 | 126 |

127 | 128 |
129 | 130 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/visitors/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Visitors 6 | 7 | 8 | 9 | 10 | 11 | 12 | 119 | 120 | 121 | 122 | 125 | 126 |
127 | 128 |
129 |
130 |
131 | 132 |
133 |
134 |
135 | 136 |

This plot shows visitors per day to the Flot homepage, with weekends colored.

137 | 138 |

The smaller plot is linked to the main plot, so it acts as an overview. Try dragging a selection on either plot, and watch the behavior of the other.

139 | 140 |
141 | 142 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/examples/zooming/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flot Examples: Selection and zooming 6 | 7 | 8 | 9 | 10 | 11 | 121 | 122 | 123 | 124 | 127 | 128 |
129 | 130 |
131 |
132 |
133 |
134 | 135 |

Selection support makes it easy to construct flexible zooming schemes. With a few lines of code, the small overview plot to the right has been connected to the large plot. Try selecting a rectangle on either of them.

136 | 137 |
138 | 139 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/jquery.colorhelpers.min.js: -------------------------------------------------------------------------------- 1 | (function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/jquery.flot.canvas.min.js: -------------------------------------------------------------------------------- 1 | /* Javascript plotting library for jQuery, version 0.8.3. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | */ 7 | (function($){var options={canvas:true};var render,getTextInfo,addText;var hasOwnProperty=Object.prototype.hasOwnProperty;function init(plot,classes){var Canvas=classes.Canvas;if(render==null){getTextInfo=Canvas.prototype.getTextInfo,addText=Canvas.prototype.addText,render=Canvas.prototype.render}Canvas.prototype.render=function(){if(!plot.getOptions().canvas){return render.call(this)}var context=this.context,cache=this._textCache;context.save();context.textBaseline="middle";for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layerCache=cache[layerKey];for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey],updateStyles=true;for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var info=styleCache[key],positions=info.positions,lines=info.lines;if(updateStyles){context.fillStyle=info.font.color;context.font=info.font.definition;updateStyles=false}for(var i=0,position;position=positions[i];i++){if(position.active){for(var j=0,line;line=position.lines[j];j++){context.fillText(lines[j].text,line[0],line[1])}}else{positions.splice(i--,1)}}if(positions.length==0){delete styleCache[key]}}}}}}}context.restore()};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){if(!plot.getOptions().canvas){return getTextInfo.call(this,layer,text,font,angle,width)}var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var context=this.context;if(typeof font!=="object"){var element=$("
 
").css("position","absolute").addClass(typeof font==="string"?font:null).appendTo(this.getTextLayer(layer));font={lineHeight:element.height(),style:element.css("font-style"),variant:element.css("font-variant"),weight:element.css("font-weight"),family:element.css("font-family"),color:element.css("color")};font.size=element.css("line-height",1).height();element.remove()}textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family;info=styleCache[text]={width:0,height:0,positions:[],lines:[],font:{definition:textStyle,color:font.color}};context.save();context.font=textStyle;var lines=(text+"").replace(/
|\r\n|\r/g,"\n").split("\n");for(var i=0;iindex)index=categories[v];return index+1}function categoriesTickGenerator(axis){var res=[];for(var label in axis.categories){var v=axis.categories[label];if(v>=axis.min&&v<=axis.max)res.push([v,label])}res.sort(function(a,b){return a[0]-b[0]});return res}function setupCategoriesForAxis(series,axis,datapoints){if(series[axis].options.mode!="categories")return;if(!series[axis].categories){var c={},o=series[axis].options.categories||{};if($.isArray(o)){for(var i=0;iax[1].max||yax[0].max)continue;if(err[e].err=="y")if(x>ax[0].max||xax[1].max)continue;var drawUpper=true,drawLower=true;if(upper>minmax[1]){drawUpper=false;upper=minmax[1]}if(lower0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w+w/2,minmax);ctx.strokeStyle="rgba(0,0,0,0.2)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w/2,minmax)}ctx.strokeStyle=err[e].color?err[e].color:s.color;ctx.lineWidth=lw;drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,0,minmax)}}}}function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){y+=offset;upper+=offset;lower+=offset;if(err.err=="x"){if(upper>x+radius)drawPath(ctx,[[upper,y],[Math.max(x+radius,minmax[0]),y]]);else drawUpper=false;if(lowery+radius)drawPath(ctx,[[x,Math.max(y+radius,minmax[1])],[x,lower]]);else drawLower=false}radius=err.radius!=null?err.radius:radius;if(drawUpper){if(err.upperCap=="-"){if(err.err=="x")drawPath(ctx,[[upper,y-radius],[upper,y+radius]]);else drawPath(ctx,[[x-radius,upper],[x+radius,upper]])}else if($.isFunction(err.upperCap)){if(err.err=="x")err.upperCap(ctx,upper,y,radius);else err.upperCap(ctx,x,upper,radius)}}if(drawLower){if(err.lowerCap=="-"){if(err.err=="x")drawPath(ctx,[[lower,y-radius],[lower,y+radius]]);else drawPath(ctx,[[x-radius,lower],[x+radius,lower]])}else if($.isFunction(err.lowerCap)){if(err.err=="x")err.lowerCap(ctx,lower,y,radius);else err.lowerCap(ctx,x,lower,radius)}}}function drawPath(ctx,pts){ctx.beginPath();ctx.moveTo(pts[0][0],pts[0][1]);for(var p=1;p=allseries.length){return null}return allseries[s.fillBetween]}return null}function computeFillBottoms(plot,s,datapoints){if(s.fillBetween==null){return}var other=findBottomSeries(s,plot.getData());if(!other){return}var ps=datapoints.pointsize,points=datapoints.points,otherps=other.datapoints.pointsize,otherpoints=other.datapoints.points,newpoints=[],px,py,intery,qx,qy,bottom,withlines=s.lines.show,withbottom=ps>2&&datapoints.format[2].y,withsteps=withlines&&s.lines.steps,fromgap=true,i=0,j=0,l,m;while(true){if(i>=points.length){break}l=newpoints.length;if(points[i]==null){for(m=0;m=otherpoints.length){if(!withlines){for(m=0;mqx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+1]-py)*(qx-px)/(points[i-ps]-px);newpoints.push(qx);newpoints.push(intery);for(m=2;m0&&otherpoints[j-otherps]!=null){bottom=qy+(otherpoints[j-otherps+1]-qy)*(px-qx)/(otherpoints[j-otherps]-qx)}i+=ps}fromgap=false;if(l!==newpoints.length&&withbottom){newpoints[l+2]=bottom}}if(withsteps&&l!==newpoints.length&&l>0&&newpoints[l]!==null&&newpoints[l]!==newpoints[l-ps]&&newpoints[l+1]!==newpoints[l-ps+1]){for(m=0;m").load(handler).error(handler).attr("src",url)})};function drawSeries(plot,ctx,series){var plotOffset=plot.getPlotOffset();if(!series.images||!series.images.show)return;var points=series.datapoints.points,ps=series.datapoints.pointsize;for(var i=0;ix2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}if(series.images.anchor=="center"){tmp=.5*(x2-x1)/(img.width-1);x1-=tmp;x2+=tmp;tmp=.5*(y2-y1)/(img.height-1);y1-=tmp;y2+=tmp}if(x1==x2||y1==y2||x1>=xaxis.max||x2<=xaxis.min||y1>=yaxis.max||y2<=yaxis.min)continue;var sx1=0,sy1=0,sx2=img.width,sy2=img.height;if(x1xaxis.max){sx2+=(sx2-sx1)*(xaxis.max-x2)/(x2-x1);x2=xaxis.max}if(y1yaxis.max){sy1+=(sy1-sy2)*(yaxis.max-y2)/(y2-y1);y2=yaxis.max}x1=xaxis.p2c(x1);x2=xaxis.p2c(x2);y1=yaxis.p2c(y1);y2=yaxis.p2c(y2);if(x1>x2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}tmp=ctx.globalAlpha;ctx.globalAlpha*=series.images.alpha;ctx.drawImage(img,sx1,sy1,sx2-sx1,sy2-sy1,x1+plotOffset.left,y1+plotOffset.top,x2-x1,y2-y1);ctx.globalAlpha=tmp}}function processRawData(plot,series,data,datapoints){if(!series.images.show)return;datapoints.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.drawSeries.push(drawSeries)}$.plot.plugins.push({init:init,options:options,name:"image",version:"1.1"})})(jQuery); -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/jquery.flot.resize.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for automatically redrawing plots as the placeholder resizes. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | It works by listening for changes on the placeholder div (through the jQuery 7 | resize event plugin) - if the size changes, it will redraw the plot. 8 | 9 | There are no options. If you need to disable the plugin for some plots, you 10 | can just fix the size of their placeholders. 11 | 12 | */ 13 | 14 | /* Inline dependency: 15 | * jQuery resize event - v1.1 - 3/14/2010 16 | * http://benalman.com/projects/jquery-resize-plugin/ 17 | * 18 | * Copyright (c) 2010 "Cowboy" Ben Alman 19 | * Dual licensed under the MIT and GPL licenses. 20 | * http://benalman.com/about/license/ 21 | */ 22 | (function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this); 23 | 24 | (function ($) { 25 | var options = { }; // no options 26 | 27 | function init(plot) { 28 | function onResize() { 29 | var placeholder = plot.getPlaceholder(); 30 | 31 | // somebody might have hidden us and we can't plot 32 | // when we don't have the dimensions 33 | if (placeholder.width() == 0 || placeholder.height() == 0) 34 | return; 35 | 36 | plot.resize(); 37 | plot.setupGrid(); 38 | plot.draw(); 39 | } 40 | 41 | function bindEvents(plot, eventHolder) { 42 | plot.getPlaceholder().resize(onResize); 43 | } 44 | 45 | function shutdown(plot, eventHolder) { 46 | plot.getPlaceholder().unbind("resize", onResize); 47 | } 48 | 49 | plot.hooks.bindEvents.push(bindEvents); 50 | plot.hooks.shutdown.push(shutdown); 51 | } 52 | 53 | $.plot.plugins.push({ 54 | init: init, 55 | options: options, 56 | name: 'resize', 57 | version: '1.0' 58 | }); 59 | })(jQuery); 60 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/jquery.flot.resize.min.js: -------------------------------------------------------------------------------- 1 | /* Javascript plotting library for jQuery, version 0.8.3. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | */ 7 | (function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);(function($){var options={};function init(plot){function onResize(){var placeholder=plot.getPlaceholder();if(placeholder.width()==0||placeholder.height()==0)return;plot.resize();plot.setupGrid();plot.draw()}function bindEvents(plot,eventHolder){plot.getPlaceholder().resize(onResize)}function shutdown(plot,eventHolder){plot.getPlaceholder().unbind("resize",onResize)}plot.hooks.bindEvents.push(bindEvents);plot.hooks.shutdown.push(shutdown)}$.plot.plugins.push({init:init,options:options,name:"resize",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/jquery.flot.selection.min.js: -------------------------------------------------------------------------------- 1 | /* Javascript plotting library for jQuery, version 0.8.3. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | */ 7 | (function($){function init(plot){var selection={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false};var savedhandlers={};var mouseUpHandler=null;function onMouseMove(e){if(selection.active){updateSelection(e);plot.getPlaceholder().trigger("plotselecting",[getSelection()])}}function onMouseDown(e){if(e.which!=1)return;document.body.focus();if(document.onselectstart!==undefined&&savedhandlers.onselectstart==null){savedhandlers.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&savedhandlers.ondrag==null){savedhandlers.ondrag=document.ondrag;document.ondrag=function(){return false}}setSelectionPos(selection.first,e);selection.active=true;mouseUpHandler=function(e){onMouseUp(e)};$(document).one("mouseup",mouseUpHandler)}function onMouseUp(e){mouseUpHandler=null;if(document.onselectstart!==undefined)document.onselectstart=savedhandlers.onselectstart;if(document.ondrag!==undefined)document.ondrag=savedhandlers.ondrag;selection.active=false;updateSelection(e);if(selectionIsSane())triggerSelectedEvent();else{plot.getPlaceholder().trigger("plotunselected",[]);plot.getPlaceholder().trigger("plotselecting",[null])}return false}function getSelection(){if(!selectionIsSane())return null;if(!selection.show)return null;var r={},c1=selection.first,c2=selection.second;$.each(plot.getAxes(),function(name,axis){if(axis.used){var p1=axis.c2p(c1[axis.direction]),p2=axis.c2p(c2[axis.direction]);r[name]={from:Math.min(p1,p2),to:Math.max(p1,p2)}}});return r}function triggerSelectedEvent(){var r=getSelection();plot.getPlaceholder().trigger("plotselected",[r]);if(r.xaxis&&r.yaxis)plot.getPlaceholder().trigger("selected",[{x1:r.xaxis.from,y1:r.yaxis.from,x2:r.xaxis.to,y2:r.yaxis.to}])}function clamp(min,value,max){return valuemax?max:value}function setSelectionPos(pos,e){var o=plot.getOptions();var offset=plot.getPlaceholder().offset();var plotOffset=plot.getPlotOffset();pos.x=clamp(0,e.pageX-offset.left-plotOffset.left,plot.width());pos.y=clamp(0,e.pageY-offset.top-plotOffset.top,plot.height());if(o.selection.mode=="y")pos.x=pos==selection.first?0:plot.width();if(o.selection.mode=="x")pos.y=pos==selection.first?0:plot.height()}function updateSelection(pos){if(pos.pageX==null)return;setSelectionPos(selection.second,pos);if(selectionIsSane()){selection.show=true;plot.triggerRedrawOverlay()}else clearSelection(true)}function clearSelection(preventEvent){if(selection.show){selection.show=false;plot.triggerRedrawOverlay();if(!preventEvent)plot.getPlaceholder().trigger("plotunselected",[])}}function extractRange(ranges,coord){var axis,from,to,key,axes=plot.getAxes();for(var k in axes){axis=axes[k];if(axis.direction==coord){key=coord+axis.n+"axis";if(!ranges[key]&&axis.n==1)key=coord+"axis";if(ranges[key]){from=ranges[key].from;to=ranges[key].to;break}}}if(!ranges[key]){axis=coord=="x"?plot.getXAxes()[0]:plot.getYAxes()[0];from=ranges[coord+"1"];to=ranges[coord+"2"]}if(from!=null&&to!=null&&from>to){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function setSelection(ranges,preventEvent){var axis,range,o=plot.getOptions();if(o.selection.mode=="y"){selection.first.x=0;selection.second.x=plot.width()}else{range=extractRange(ranges,"x");selection.first.x=range.axis.p2c(range.from);selection.second.x=range.axis.p2c(range.to)}if(o.selection.mode=="x"){selection.first.y=0;selection.second.y=plot.height()}else{range=extractRange(ranges,"y");selection.first.y=range.axis.p2c(range.from);selection.second.y=range.axis.p2c(range.to)}selection.show=true;plot.triggerRedrawOverlay();if(!preventEvent&&selectionIsSane())triggerSelectedEvent()}function selectionIsSane(){var minSize=plot.getOptions().selection.minSize;return Math.abs(selection.second.x-selection.first.x)>=minSize&&Math.abs(selection.second.y-selection.first.y)>=minSize}plot.clearSelection=clearSelection;plot.setSelection=setSelection;plot.getSelection=getSelection;plot.hooks.bindEvents.push(function(plot,eventHolder){var o=plot.getOptions();if(o.selection.mode!=null){eventHolder.mousemove(onMouseMove);eventHolder.mousedown(onMouseDown)}});plot.hooks.drawOverlay.push(function(plot,ctx){if(selection.show&&selectionIsSane()){var plotOffset=plot.getPlotOffset();var o=plot.getOptions();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var c=$.color.parse(o.selection.color);ctx.strokeStyle=c.scale("a",.8).toString();ctx.lineWidth=1;ctx.lineJoin=o.selection.shape;ctx.fillStyle=c.scale("a",.4).toString();var x=Math.min(selection.first.x,selection.second.x)+.5,y=Math.min(selection.first.y,selection.second.y)+.5,w=Math.abs(selection.second.x-selection.first.x)-1,h=Math.abs(selection.second.y-selection.first.y)-1;ctx.fillRect(x,y,w,h);ctx.strokeRect(x,y,w,h);ctx.restore()}});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mousedown",onMouseDown);if(mouseUpHandler)$(document).unbind("mouseup",mouseUpHandler)})}$.plot.plugins.push({init:init,options:{selection:{mode:null,color:"#e8cfac",shape:"round",minSize:5}},name:"selection",version:"1.1"})})(jQuery); -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/jquery.flot.stack.min.js: -------------------------------------------------------------------------------- 1 | /* Javascript plotting library for jQuery, version 0.8.3. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | */ 7 | (function($){var options={series:{stack:null}};function init(plot){function findMatchingSeries(s,allseries){var res=null;for(var i=0;i2&&(horizontal?datapoints.format[2].x:datapoints.format[2].y),withsteps=withlines&&s.lines.steps,fromgap=true,keyOffset=horizontal?1:0,accumulateOffset=horizontal?0:1,i=0,j=0,l,m;while(true){if(i>=points.length)break;l=newpoints.length;if(points[i]==null){for(m=0;m=otherpoints.length){if(!withlines){for(m=0;mqx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+accumulateOffset]-py)*(qx-px)/(points[i-ps+keyOffset]-px);newpoints.push(qx);newpoints.push(intery+qy);for(m=2;m0&&otherpoints[j-otherps]!=null)bottom=qy+(otherpoints[j-otherps+accumulateOffset]-qy)*(px-qx)/(otherpoints[j-otherps+keyOffset]-qx);newpoints[l+accumulateOffset]+=bottom;i+=ps}fromgap=false;if(l!=newpoints.length&&withbottom)newpoints[l+2]+=bottom}if(withsteps&&l!=newpoints.length&&l>0&&newpoints[l]!=null&&newpoints[l]!=newpoints[l-ps]&&newpoints[l+1]!=newpoints[l-ps+1]){for(m=0;m s = r * sqrt(pi)/2 24 | var size = radius * Math.sqrt(Math.PI) / 2; 25 | ctx.rect(x - size, y - size, size + size, size + size); 26 | }, 27 | diamond: function (ctx, x, y, radius, shadow) { 28 | // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) 29 | var size = radius * Math.sqrt(Math.PI / 2); 30 | ctx.moveTo(x - size, y); 31 | ctx.lineTo(x, y - size); 32 | ctx.lineTo(x + size, y); 33 | ctx.lineTo(x, y + size); 34 | ctx.lineTo(x - size, y); 35 | }, 36 | triangle: function (ctx, x, y, radius, shadow) { 37 | // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) 38 | var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); 39 | var height = size * Math.sin(Math.PI / 3); 40 | ctx.moveTo(x - size/2, y + height/2); 41 | ctx.lineTo(x + size/2, y + height/2); 42 | if (!shadow) { 43 | ctx.lineTo(x, y - height/2); 44 | ctx.lineTo(x - size/2, y + height/2); 45 | } 46 | }, 47 | cross: function (ctx, x, y, radius, shadow) { 48 | // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 49 | var size = radius * Math.sqrt(Math.PI) / 2; 50 | ctx.moveTo(x - size, y - size); 51 | ctx.lineTo(x + size, y + size); 52 | ctx.moveTo(x - size, y + size); 53 | ctx.lineTo(x + size, y - size); 54 | } 55 | }; 56 | 57 | var s = series.points.symbol; 58 | if (handlers[s]) 59 | series.points.symbol = handlers[s]; 60 | } 61 | 62 | function init(plot) { 63 | plot.hooks.processDatapoints.push(processRawData); 64 | } 65 | 66 | $.plot.plugins.push({ 67 | init: init, 68 | name: 'symbols', 69 | version: '1.0' 70 | }); 71 | })(jQuery); 72 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/jquery.flot.symbol.min.js: -------------------------------------------------------------------------------- 1 | /* Javascript plotting library for jQuery, version 0.8.3. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | */ 7 | (function($){function processRawData(plot,series,datapoints){var handlers={square:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.rect(x-size,y-size,size+size,size+size)},diamond:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI/2);ctx.moveTo(x-size,y);ctx.lineTo(x,y-size);ctx.lineTo(x+size,y);ctx.lineTo(x,y+size);ctx.lineTo(x-size,y)},triangle:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3));var height=size*Math.sin(Math.PI/3);ctx.moveTo(x-size/2,y+height/2);ctx.lineTo(x+size/2,y+height/2);if(!shadow){ctx.lineTo(x,y-height/2);ctx.lineTo(x-size/2,y+height/2)}},cross:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.moveTo(x-size,y-size);ctx.lineTo(x+size,y+size);ctx.moveTo(x-size,y+size);ctx.lineTo(x+size,y-size)}};var s=series.points.symbol;if(handlers[s])series.points.symbol=handlers[s]}function init(plot){plot.hooks.processDatapoints.push(processRawData)}$.plot.plugins.push({init:init,name:"symbols",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/jquery.flot.threshold.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for thresholding data. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | The plugin supports these options: 7 | 8 | series: { 9 | threshold: { 10 | below: number 11 | color: colorspec 12 | } 13 | } 14 | 15 | It can also be applied to a single series, like this: 16 | 17 | $.plot( $("#placeholder"), [{ 18 | data: [ ... ], 19 | threshold: { ... } 20 | }]) 21 | 22 | An array can be passed for multiple thresholding, like this: 23 | 24 | threshold: [{ 25 | below: number1 26 | color: color1 27 | },{ 28 | below: number2 29 | color: color2 30 | }] 31 | 32 | These multiple threshold objects can be passed in any order since they are 33 | sorted by the processing function. 34 | 35 | The data points below "below" are drawn with the specified color. This makes 36 | it easy to mark points below 0, e.g. for budget data. 37 | 38 | Internally, the plugin works by splitting the data into two series, above and 39 | below the threshold. The extra series below the threshold will have its label 40 | cleared and the special "originSeries" attribute set to the original series. 41 | You may need to check for this in hover events. 42 | 43 | */ 44 | 45 | (function ($) { 46 | var options = { 47 | series: { threshold: null } // or { below: number, color: color spec} 48 | }; 49 | 50 | function init(plot) { 51 | function thresholdData(plot, s, datapoints, below, color) { 52 | var ps = datapoints.pointsize, i, x, y, p, prevp, 53 | thresholded = $.extend({}, s); // note: shallow copy 54 | 55 | thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format }; 56 | thresholded.label = null; 57 | thresholded.color = color; 58 | thresholded.threshold = null; 59 | thresholded.originSeries = s; 60 | thresholded.data = []; 61 | 62 | var origpoints = datapoints.points, 63 | addCrossingPoints = s.lines.show; 64 | 65 | var threspoints = []; 66 | var newpoints = []; 67 | var m; 68 | 69 | for (i = 0; i < origpoints.length; i += ps) { 70 | x = origpoints[i]; 71 | y = origpoints[i + 1]; 72 | 73 | prevp = p; 74 | if (y < below) 75 | p = threspoints; 76 | else 77 | p = newpoints; 78 | 79 | if (addCrossingPoints && prevp != p && x != null 80 | && i > 0 && origpoints[i - ps] != null) { 81 | var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]); 82 | prevp.push(interx); 83 | prevp.push(below); 84 | for (m = 2; m < ps; ++m) 85 | prevp.push(origpoints[i + m]); 86 | 87 | p.push(null); // start new segment 88 | p.push(null); 89 | for (m = 2; m < ps; ++m) 90 | p.push(origpoints[i + m]); 91 | p.push(interx); 92 | p.push(below); 93 | for (m = 2; m < ps; ++m) 94 | p.push(origpoints[i + m]); 95 | } 96 | 97 | p.push(x); 98 | p.push(y); 99 | for (m = 2; m < ps; ++m) 100 | p.push(origpoints[i + m]); 101 | } 102 | 103 | datapoints.points = newpoints; 104 | thresholded.datapoints.points = threspoints; 105 | 106 | if (thresholded.datapoints.points.length > 0) { 107 | var origIndex = $.inArray(s, plot.getData()); 108 | // Insert newly-generated series right after original one (to prevent it from becoming top-most) 109 | plot.getData().splice(origIndex + 1, 0, thresholded); 110 | } 111 | 112 | // FIXME: there are probably some edge cases left in bars 113 | } 114 | 115 | function processThresholds(plot, s, datapoints) { 116 | if (!s.threshold) 117 | return; 118 | 119 | if (s.threshold instanceof Array) { 120 | s.threshold.sort(function(a, b) { 121 | return a.below - b.below; 122 | }); 123 | 124 | $(s.threshold).each(function(i, th) { 125 | thresholdData(plot, s, datapoints, th.below, th.color); 126 | }); 127 | } 128 | else { 129 | thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color); 130 | } 131 | } 132 | 133 | plot.hooks.processDatapoints.push(processThresholds); 134 | } 135 | 136 | $.plot.plugins.push({ 137 | init: init, 138 | options: options, 139 | name: 'threshold', 140 | version: '1.2' 141 | }); 142 | })(jQuery); 143 | -------------------------------------------------------------------------------- /dashboard/public/javascripts/flot/jquery.flot.threshold.min.js: -------------------------------------------------------------------------------- 1 | /* Javascript plotting library for jQuery, version 0.8.3. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | */ 7 | (function($){var options={series:{threshold:null}};function init(plot){function thresholdData(plot,s,datapoints,below,color){var ps=datapoints.pointsize,i,x,y,p,prevp,thresholded=$.extend({},s);thresholded.datapoints={points:[],pointsize:ps,format:datapoints.format};thresholded.label=null;thresholded.color=color;thresholded.threshold=null;thresholded.originSeries=s;thresholded.data=[];var origpoints=datapoints.points,addCrossingPoints=s.lines.show;var threspoints=[];var newpoints=[];var m;for(i=0;i0&&origpoints[i-ps]!=null){var interx=x+(below-y)*(x-origpoints[i-ps])/(y-origpoints[i-ps+1]);prevp.push(interx);prevp.push(below);for(m=2;m0){var origIndex=$.inArray(s,plot.getData());plot.getData().splice(origIndex+1,0,thresholded)}}function processThresholds(plot,s,datapoints){if(!s.threshold)return;if(s.threshold instanceof Array){s.threshold.sort(function(a,b){return a.below-b.below});$(s.threshold).each(function(i,th){thresholdData(plot,s,datapoints,th.below,th.color)})}else{thresholdData(plot,s,datapoints,s.threshold.below,s.threshold.color)}}plot.hooks.processDatapoints.push(processThresholds)}$.plot.plugins.push({init:init,options:options,name:"threshold",version:"1.2"})})(jQuery); -------------------------------------------------------------------------------- /dashboard/public/javascripts/index.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // 60s refresh interval. 4 | var refreshInterval = 60000; 5 | 6 | // Plot temperatures in Flot graph. 7 | var refreshTemperatureGraph = function() { 8 | // Get time 24h ago. 9 | var now = Math.round(new Date().getTime() / 1000); 10 | var start = now - (24 * 3600); 11 | 12 | // Load temperature data through AJAX. 13 | $.getJSON('/temps/all', { startTime: start }, function(data) { 14 | var temp_data = []; 15 | for (var i = 0; i < data.length; i++) { 16 | temp_data[i] = { 17 | 'label': data[i]['label'], 18 | 'data': [] 19 | }; 20 | for (var j = 0; j < data[i]['data'].length; j++) { 21 | var dataPoint = [data[i]['data'][j]['time'] * 1000, data[i]['data'][j]['temp']]; 22 | temp_data[i]['data'].push(dataPoint); 23 | }; 24 | } 25 | 26 | // Remove 'loading' class from graph. 27 | $('#temperature-graph').removeClass('loading'); 28 | 29 | // Plot the temperatures on the graph. 30 | $.plot("#temperature-graph", temp_data, { 31 | yaxis: { 32 | tickFormatter: function (v, axis) { 33 | return v.toFixed(axis.tickDecimals) +"°F "; 34 | }, 35 | }, 36 | xaxis: { 37 | mode: "time", 38 | timeformat: "%I:%M %p", 39 | timezone: "browser", 40 | min: start * 1000, 41 | max: now * 1000 42 | }, 43 | lines: {show: true}, 44 | legend: { 45 | show: true, 46 | position: "sw" 47 | }, 48 | grid: { 49 | markings: function (axes) { 50 | var markings = []; 51 | console.log(axes); 52 | for (var y = Math.floor(axes.ymin); y < axes.ymax; y += 2) { 53 | markings.push({yaxis: { from: y, to: y + 1 }}); 54 | } 55 | return markings; 56 | }, 57 | margin: { 58 | right: 0, 59 | left: 30 60 | } 61 | } 62 | }); 63 | }); 64 | } 65 | 66 | var refreshLatestTemps = function() { 67 | $.getJSON('/temps/latest', function(data) { 68 | var temp_data = []; 69 | console.log(data); 70 | 71 | $.each(data, function(index, value) { 72 | // Set up the date. 73 | var date = new Date(); 74 | date.setTime(value.time * 1000); 75 | dateString = date.toTimeString().slice(0, 8); 76 | 77 | // Set up the values. 78 | var title = $('

', {class: 'title'}).text(value.label) 79 | var temp = $('
', {class: 'temperature'}).text(value.temp + '°F') 80 | var time = $('
', {class: 'time'}).text('as of ' + dateString) 81 | 82 | var sensorClass = 'sensor-' + value.sensor_id; 83 | var sensorDiv = $('#temperature-latest > .' + sensorClass); 84 | // If the sensor div exists, update the data inside. 85 | if (sensorDiv.length) { 86 | sensorDiv.empty().append(title, temp, time); 87 | } 88 | // Otherwise, create the div and populate the data. 89 | else { 90 | var sensorDivToAdd = $('
', {class: 'sensor ' + sensorClass}).append(title, temp, time); 91 | $('#temperature-latest').append(sensorDivToAdd); 92 | } 93 | }); 94 | 95 | // Remove 'loading' class from latest temperature area. 96 | $('#temperature-latest').removeClass('loading'); 97 | }); 98 | } 99 | 100 | var loadData = function() { 101 | refreshTemperatureGraph(); 102 | refreshLatestTemps(); 103 | } 104 | 105 | // Load the initial data and set up an interval to reload it. 106 | loadData(); 107 | var interval = window.setInterval(loadData, refreshInterval); 108 | }); 109 | -------------------------------------------------------------------------------- /dashboard/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | /* General styles */ 2 | body { 3 | padding: 1em; 4 | font: 1em "Lucida Grande", Helvetica, Arial, sans-serif; 5 | } 6 | 7 | h1, h2, h3, h4, h5, h6 { 8 | margin: 0 .25em 0; 9 | } 10 | 11 | a { 12 | color: #00B7FF; 13 | } 14 | 15 | /* Latest Temperatures */ 16 | #temperature-latest { 17 | width: 100%; 18 | overflow: hidden; 19 | text-align: center; 20 | margin: 1em 0; 21 | } 22 | 23 | #temperature-latest .sensor { 24 | display: inline-block; 25 | background-color: #eee; 26 | padding: .75em 1.5em 1em; 27 | margin: .5em; 28 | } 29 | 30 | #temperature-latest .title { 31 | font-size: .85em; 32 | } 33 | 34 | .temperature { 35 | font-size: 1.8em; 36 | margin-bottom: .25em; 37 | } 38 | 39 | .time { 40 | font-size: .9em; 41 | } 42 | 43 | /* Temperature graph */ 44 | #temperature-graph { 45 | width: 100%; 46 | height: 30em; 47 | } 48 | 49 | .loading { 50 | background: #eee url("/images/loading.gif") no-repeat center; 51 | } 52 | 53 | .help-text { 54 | font-size: .8em; 55 | text-align: center; 56 | margin-top: 3em; 57 | } 58 | 59 | /* Responsive */ 60 | @media screen and (max-width: 480px) { 61 | 62 | h1 { 63 | font-size: 1.5em; 64 | text-align: center; 65 | } 66 | 67 | #temperature-latest { 68 | margin: 1em 0; 69 | } 70 | 71 | #temperature-latest .sensor { 72 | padding: .8em 1em; 73 | } 74 | 75 | #temperature-graph { 76 | height: 15em; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /dashboard/router/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | app.use('/', require('./routes/home')); 3 | app.use('/sensors', require('./routes/sensors')); 4 | app.use('/temps', require('./routes/temps')); 5 | } 6 | -------------------------------------------------------------------------------- /dashboard/router/routes/home.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Temperature Monitor' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /dashboard/router/routes/sensors.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET sensors route. */ 5 | /* Example: GET /sensors */ 6 | router.get('/', function(req, res, next) { 7 | var pool = req.pool; 8 | 9 | // Define the SQL query. 10 | var sqlQuery = "SELECT * FROM sensors"; 11 | 12 | // Get all temperature data for a given sensor. 13 | pool.getConnection(function(err, connection) { 14 | if (err) throw err; 15 | connection.query(sqlQuery, function(err, rows) { 16 | if (err) throw err; 17 | connection.release(); 18 | res.json(rows); 19 | }); 20 | }); 21 | }); 22 | 23 | /* POST sensors route. */ 24 | /* Example: POST /sensors?location=New+Location */ 25 | router.post('/', function(req, res, next) { 26 | var pool = req.pool; 27 | 28 | // If a location and group are given, create new sensor and return it's ID. 29 | if (typeof req.body.location !== 'undefined' 30 | && typeof req.body.group !== 'undefined') { 31 | var location = req.body.location; 32 | var group = req.body.group; 33 | 34 | // Validate the location. 35 | if (location.length > 128) { 36 | res.status(400).json({ error: 'Location must be less than or equal to 128 characters.' }); 37 | return; 38 | } 39 | 40 | // Validate the group. 41 | if (group.length > 32) { 42 | res.status(400).json({ error: 'Group must be less than or equal to 32 characters.' }); 43 | return; 44 | } 45 | 46 | // Define the SQL query. 47 | var sqlQuery = "INSERT INTO sensors SET ?"; 48 | var sqlPlaceholders = { 49 | "location": location, 50 | "group": group 51 | }; 52 | 53 | // Add the new sensor to the sensors table, return the new ID. 54 | pool.getConnection(function(err, connection) { 55 | if (err) throw err; 56 | connection.query(sqlQuery, sqlPlaceholders, function(err, result) { 57 | if (err) throw err; 58 | connection.release(); 59 | res.json({ "id": result.insertId }); 60 | }); 61 | }); 62 | } 63 | else { 64 | res.status(400).json({ error: 'location and group parameters must be present in the body of the request.' }); 65 | } 66 | }); 67 | 68 | /* DELETE sensors route. */ 69 | /* Example: DELETE /sensors/3 */ 70 | router.delete('/:id', function(req, res, next) { 71 | var sensor = req.params.id; 72 | 73 | // Warn the user this route is not yet implemented. 74 | res.status(501).json({ error: 'This functionality has not yet been implemented.' }); 75 | }); 76 | 77 | module.exports = router; 78 | -------------------------------------------------------------------------------- /dashboard/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/dashboard/screenshot.png -------------------------------------------------------------------------------- /dashboard/views/error.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /dashboard/views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1=title 5 | div#temperature-latest.loading 6 | div#temperature-graph.loading 7 | p.help-text Times are in your browser's timezone. -------------------------------------------------------------------------------- /dashboard/views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | meta(name='viewport', content='width=device-width, initial-scale=1.0') 6 | link(rel='icon', href='/favicon.ico', type='image/x-icon') 7 | link(rel='stylesheet', href='/stylesheets/style.css') 8 | block head 9 | body 10 | block content 11 | script(type='text/javascript' src='/javascripts/jquery/jquery.min.js') 12 | script(type='text/javascript' src='/javascripts/flot/jquery.flot.min.js') 13 | script(type='text/javascript' src='/javascripts/flot/jquery.flot.time.min.js') 14 | script(type='text/javascript' src='/javascripts/index.js') 15 | -------------------------------------------------------------------------------- /images/arudino-temperature-wiring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/images/arudino-temperature-wiring.jpg -------------------------------------------------------------------------------- /images/pi-temperature-wiring-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/images/pi-temperature-wiring-1.jpg -------------------------------------------------------------------------------- /images/pi-temperature-wiring-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geerlingguy/temperature-monitor/494d87d4de60776ece3ed37fe3f6a0cc9aa86c77/images/pi-temperature-wiring-2.jpg -------------------------------------------------------------------------------- /playbooks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include: master/main.yml 3 | - include: remote-monitor/main.yml 4 | -------------------------------------------------------------------------------- /playbooks/master/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: master 3 | become: true 4 | 5 | vars_files: 6 | - ../../config/config.yml 7 | 8 | pre_tasks: 9 | - name: Ensure apt cache is updated. 10 | apt: update_cache=true cache_valid_time=86400 11 | 12 | - name: Ensure apt-transport-https is installed. 13 | apt: name=apt-transport-https state=present 14 | 15 | roles: 16 | - geerlingguy.git 17 | - geerlingguy.mysql 18 | - geerlingguy.nodejs 19 | - role: geerlingguy.raspberry-pi 20 | when: deploy_target_is_pi 21 | 22 | tasks: 23 | - name: Install dependencies for Python temperature scripts. 24 | apt: 25 | name: 26 | - python-pip 27 | - python-dev 28 | state: present 29 | 30 | - name: Clean up bad Python requests package so pip works. 31 | shell: "rm -rf /usr/local/lib/python2.7/dist-packages/requests*" 32 | changed_when: false 33 | 34 | - name: Ensure the temperature-monitor repository is present. 35 | git: 36 | repo: https://github.com/geerlingguy/temperature-monitor.git 37 | dest: "{{ temperature_monitor_dir }}" 38 | update: true 39 | when: deploy_target_is_pi 40 | 41 | - name: Install required Python libraries with pip. 42 | pip: "requirements={{ temperature_monitor_dir }}/scripts/requirements.txt" 43 | 44 | - name: Configure the settings file (temps.conf). 45 | template: 46 | src: templates/temps.conf.j2 47 | dest: "{{ temperature_monitor_dir }}/scripts/temps.conf" 48 | 49 | - include: tasks/database.yml 50 | - include: tasks/app.yml 51 | - include: tasks/scripts.yml 52 | -------------------------------------------------------------------------------- /playbooks/master/tasks/app.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure Dashboard app dependencies are installed. 3 | npm: "path={{ temperature_monitor_dir }}/dashboard" 4 | 5 | - name: Get list of all running Node.js apps. 6 | command: /usr/local/lib/npm/bin/forever list 7 | register: forever_list 8 | changed_when: false 9 | 10 | - name: Ensure Dashboard app is started. 11 | command: > 12 | /usr/local/lib/npm/bin/forever start ./bin/www 13 | chdir={{ temperature_monitor_dir }}/dashboard 14 | when: "forever_list.stdout.find('bin/www') == -1" 15 | 16 | - name: Ensure cron job to start Dashboard app on boot is present. 17 | cron: 18 | name: "Start Temperature Monitor Dashboard app" 19 | special_time: reboot 20 | job: "cd {{ temperature_monitor_dir }}/dashboard && /usr/local/lib/npm/bin/forever start ./bin/www" 21 | -------------------------------------------------------------------------------- /playbooks/master/tasks/database.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Check if database has already been configured. 3 | stat: path=/etc/tempmon-db-configured 4 | register: db_configured 5 | 6 | - name: Configure the temperature logging database. 7 | shell: > 8 | mysql -u {{ mysql_root_username }} -p{{ mysql_root_password }} < 9 | {{ temperature_monitor_dir }}/dashboard/database/schema.sql 10 | when: db_configured.stat.exists == false 11 | 12 | - name: Add sample data to the database. 13 | shell: > 14 | mysql -u {{ mysql_root_username }} -p{{ mysql_root_password }} < 15 | {{ temperature_monitor_dir }}/dashboard/database/sample-data.sql 16 | when: deploy_target_is_pi == false and db_configured.stat.exists == false 17 | 18 | - name: Save a file indicating databases have successfully been configured. 19 | file: 20 | path: /etc/tempmon-db-configured 21 | state: touch 22 | mode: 0644 23 | -------------------------------------------------------------------------------- /playbooks/master/tasks/scripts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Configure Nest API credentials. 3 | template: 4 | src: templates/nest_api.j2 5 | dest: "/home/{{ temperature_monitor_user }}/.nest_api" 6 | owner: "{{ temperature_monitor_user }}" 7 | when: nest_access_token != '' 8 | 9 | - name: Configure Weather Underground credentials. 10 | template: 11 | src: templates/wu_api.j2 12 | dest: "/home/{{ temperature_monitor_user }}/.wu_api" 13 | owner: "{{ temperature_monitor_user }}" 14 | when: wu_api_key != '' 15 | 16 | - name: Ensure cron job exists for Nest temperatures. 17 | cron: 18 | name: "Check Nest Thermostat temperatures" 19 | job: ". /home/{{ temperature_monitor_user }}/.nest_api; python {{ temperature_monitor_dir }}/scripts/nest-temps.py > /dev/null 2>&1" 20 | user: "{{ temperature_monitor_user }}" 21 | minute: "*/5" 22 | when: nest_access_token != '' 23 | 24 | - name: Ensure cron job exists for Weather Underground temperatures. 25 | cron: 26 | name: "Check Weather Underground temperatures" 27 | job: ". /home/{{ temperature_monitor_user }}/.wu_api; python {{ temperature_monitor_dir }}/scripts/outdoor-temps-wu.py > /dev/null 2>&1" 28 | user: "{{ temperature_monitor_user }}" 29 | when: wu_api_key != '' 30 | -------------------------------------------------------------------------------- /playbooks/master/templates/nest_api.j2: -------------------------------------------------------------------------------- 1 | export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH 2 | export NEST_ACCESS_TOKEN={{ nest_access_token }} 3 | export NEST_THERMOSTAT_ID={{ nest_thermostat_id }} 4 | -------------------------------------------------------------------------------- /playbooks/master/templates/temps.conf.j2: -------------------------------------------------------------------------------- 1 | dashboard_uri = '{{ dashboard_uri }}' 2 | 3 | local_temp_read_delay = {{ local_temp_read_delay }} 4 | local_temp_offset = {{ local_temp_offset }} 5 | local_sensor_id = {{ local_sensor_id }} 6 | 7 | nest_sensor_id = {{ nest_sensor_id }} 8 | wu_sensor_id = {{ wu_sensor_id }} 9 | owm_sensor_id = {{ owm_sensor_id }} 10 | owm_city_id = '{{ owm_city_id }}' -------------------------------------------------------------------------------- /playbooks/master/templates/wu_api.j2: -------------------------------------------------------------------------------- 1 | export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH 2 | export WU_API_KEY={{ wu_api_key }} 3 | export WU_LOCATION="{{ wu_location }}" 4 | -------------------------------------------------------------------------------- /playbooks/remote-monitor/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: remotes 3 | become: true 4 | 5 | vars_files: 6 | - ../../config/config.yml 7 | 8 | handlers: 9 | - name: restart pi-temps 10 | service: name=pi-temps state=restarted 11 | 12 | pre_tasks: 13 | - name: Ensure apt cache is updated. 14 | apt: update_cache=yes cache_valid_time=86400 15 | 16 | roles: 17 | - geerlingguy.git 18 | - role: geerlingguy.raspberry-pi 19 | when: deploy_target_is_pi 20 | 21 | tasks: 22 | - name: Install dependencies for Python temperature scripts. 23 | apt: "name={{ item }} state=present" 24 | with_items: 25 | - python-pip 26 | - python-dev 27 | 28 | - name: Clean up bad Python requests package so pip works. 29 | shell: "rm -rf /usr/local/lib/python2.7/dist-packages/requests*" 30 | changed_when: false 31 | 32 | - name: Ensure the temperature-monitor repository is present. 33 | git: 34 | repo: https://github.com/geerlingguy/temperature-monitor.git 35 | dest: "{{ temperature_monitor_dir }}" 36 | update: true 37 | become: true 38 | become_user: "{{ temperature_monitor_user }}" 39 | when: deploy_target_is_pi 40 | notify: restart pi-temps 41 | 42 | - name: Install required Python libraries with pip. 43 | pip: "requirements={{ temperature_monitor_dir }}/scripts/requirements.txt" 44 | 45 | - name: Configure the settings file (temps.conf). 46 | template: 47 | src: ../master/templates/temps.conf.j2 48 | dest: "{{ temperature_monitor_dir }}/scripts/temps.conf" 49 | mode: 0644 50 | notify: restart pi-temps 51 | 52 | - name: Configure the init file. 53 | template: 54 | src: templates/pi-temps-init.j2 55 | dest: "/etc/init.d/pi-temps" 56 | mode: 0755 57 | notify: restart pi-temps 58 | 59 | - name: Ensure the pi-temps service is active and enabled on boot. 60 | service: name=pi-temps state=started enabled=yes 61 | -------------------------------------------------------------------------------- /playbooks/remote-monitor/templates/pi-temps-init.j2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: pi-temps 5 | # Required-Start: $remote_fs $syslog 6 | # Required-Stop: $remote_fs $syslog 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Raspberry Pi temperature logging script. 10 | # Description: Raspberry Pi temperature logging script. 11 | ### END INIT INFO 12 | 13 | # Change the next 3 lines to suit where you install your script and what you want to call it 14 | DIR={{ temperature_monitor_dir }}/scripts 15 | DAEMON=$DIR/pi-temps.py 16 | DAEMON_NAME=pi-temps 17 | 18 | # Add any command line options for your daemon here 19 | DAEMON_OPTS="" 20 | 21 | # This next line determines what user the script runs as. 22 | # Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python. 23 | DAEMON_USER={{ temperature_monitor_user }} 24 | 25 | # The process ID of the script when it runs is stored here: 26 | PIDFILE=/var/run/$DAEMON_NAME.pid 27 | 28 | . /lib/lsb/init-functions 29 | 30 | do_start () { 31 | log_daemon_msg "Starting $DAEMON_NAME daemon" 32 | start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS 33 | log_end_msg $? 34 | } 35 | do_stop () { 36 | log_daemon_msg "Stopping $DAEMON_NAME daemon" 37 | start-stop-daemon --stop --pidfile $PIDFILE --retry 10 38 | log_end_msg $? 39 | } 40 | 41 | case "$1" in 42 | 43 | start|stop) 44 | do_${1} 45 | ;; 46 | 47 | restart|reload|force-reload) 48 | do_stop 49 | do_start 50 | ;; 51 | 52 | status) 53 | status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $? 54 | ;; 55 | 56 | *) 57 | echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}" 58 | exit 1 59 | ;; 60 | 61 | esac 62 | exit 0 63 | -------------------------------------------------------------------------------- /playbooks/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - src: geerlingguy.git 3 | - src: geerlingguy.mysql 4 | - src: geerlingguy.nodejs 5 | - src: geerlingguy.raspberry-pi 6 | -------------------------------------------------------------------------------- /scripts/example.temps.conf: -------------------------------------------------------------------------------- 1 | # Configuration for temperature logging scripts. 2 | # 3 | # Create a copy of this file, adjust it to suit your needs, and save the copy as 4 | # `temps.conf` in the same directory. 5 | 6 | # The URI of the dashboard server (no trailing slash). 7 | dashboard_uri = 'http://10.0.1.33:3000' 8 | 9 | # 10 | # Local Temperature sensor settings. 11 | # 12 | 13 | # Delay between local temperature readings, in seconds. 14 | local_temp_read_delay = 60 15 | 16 | # Calibration for local temperature sensor, in degrees F (e.g. 3 -2, -1.24). 17 | local_temp_offset = 0 18 | 19 | # The local sensor ID (as configured in the dashboard app). 20 | local_sensor_id = 1 21 | 22 | # 23 | # Nest Temperature sensor settings. 24 | # 25 | 26 | # Nest sensor ID (as configured in the dashboard app). 27 | nest_sensor_id = 3 28 | 29 | # 30 | # Weather Underground sensor settings. 31 | # 32 | 33 | # Weather Underground sensor ID (as configured in the dashboard app). 34 | wu_sensor_id = 2 35 | 36 | # 37 | # Open Weather Map sensor settings. 38 | # 39 | 40 | # Open Weather Maps sensor ID (as configured in the dashboard app). 41 | owm_sensor_id = 2 42 | 43 | # Open Weather Maps city ID. 44 | owm_city_id = '4407084' 45 | -------------------------------------------------------------------------------- /scripts/nest-temps.py: -------------------------------------------------------------------------------- 1 | # Nest thermostat temperature logging script. 2 | # @author Jeff Geerling, 2015. 3 | # 4 | # To get an authorization code / 'access_token': 5 | # 1. Visit https://developer.nest.com/clients 6 | # 2. Grab the Authorization URL, visit it, log in, and grab the Pincode. 7 | # 3. Make a POST request to: 8 | # - uri: https://api.home.nest.com/oauth2/access_token 9 | # - parameters: 10 | # - code: [Pincode] 11 | # - client_id: [from clients page] 12 | # - client_secret: [from clients page] 13 | # - grant_type: authorization_code 14 | # 4. Copy out the access_token in the response (really long string). 15 | # 16 | # By default, the access_token will be valid for a period of ten years, so doing 17 | # this process manually saves a bit of OAuth code here :) 18 | # 19 | # See: 20 | # - https://developer.nest.com/documentation/cloud/rest-quick-guide 21 | # - https://developer.nest.com/documentation/cloud/authorization-reference/ 22 | # - https://developer.nest.com/clients 23 | 24 | import os 25 | import glob 26 | import json 27 | from datetime import datetime 28 | import calendar 29 | import requests 30 | from temp_api import postTempData 31 | 32 | # Import configuration from 'temps.conf' file. 33 | config = {} 34 | config_dir = os.path.dirname(os.path.abspath(__file__)) 35 | execfile(config_dir + "/temps.conf", config) 36 | 37 | # Read Nest information from the environment. 38 | try: 39 | nest_access_token = os.environ['NEST_ACCESS_TOKEN'] 40 | nest_thermostat_id = os.environ['NEST_THERMOSTAT_ID'] 41 | except KeyError: 42 | print "NEST_ACCESS_TOKEN and NEST_THERMOSTAT_ID env vars must be set." 43 | exit(1) 44 | 45 | # Current time (UNIX timestamp). 46 | date = datetime.utcnow() 47 | time = calendar.timegm(date.utctimetuple()) 48 | 49 | # Log into Nest API. 50 | uri = 'https://developer-api.nest.com/devices/thermostats/' + nest_thermostat_id 51 | payload = { 'auth': nest_access_token } 52 | headers = { 'Accept': 'application/json' } 53 | req = requests.get(uri, params=payload, headers=headers) 54 | 55 | if req.status_code != requests.codes.ok: 56 | print "Could not retrieve thermostat information from Nest API." 57 | exit(1) 58 | 59 | data = req.json() 60 | if ('ambient_temperature_f' in data.keys()): 61 | temp = "{0:.2f}".format(data['ambient_temperature_f']) 62 | 63 | # Send data to temperature logger. 64 | postTempData(config["nest_sensor_id"], temp, time, exit_on_error=True) 65 | 66 | else: 67 | print "Could not retrieve temperature data from Nest API." 68 | exit(1) 69 | 70 | # # Log the successfully-posted data. 71 | print "{0}, {1}".format(time, temp.rstrip()) 72 | -------------------------------------------------------------------------------- /scripts/outdoor-temps-owm.py: -------------------------------------------------------------------------------- 1 | # Outdoor temperature logging script - Open Weather Map. 2 | # @author Jeff Geerling, 2015. 3 | 4 | import os 5 | import glob 6 | import json 7 | from datetime import datetime 8 | import calendar 9 | import requests 10 | from temp_api import postTempData 11 | 12 | # Import configuration from 'temps.conf' file. 13 | config = {} 14 | config_dir = os.path.dirname(os.path.abspath(__file__)) 15 | execfile(config_dir + "/temps.conf", config) 16 | 17 | # Current time (UNIX timestamp). 18 | date = datetime.utcnow() 19 | time = calendar.timegm(date.utctimetuple()) 20 | 21 | # Current temperature. 22 | uri = 'http://api.openweathermap.org/data/2.5/weather?id=' + config['owm_city_id'] 23 | req = requests.get(uri) 24 | 25 | if req.status_code != requests.codes.ok: 26 | print "Could not retrieve current weather data." 27 | exit(1) 28 | 29 | # Get JSON as an object. 30 | data = req.json() 31 | 32 | # Log the data if it was returned successfully. 33 | if ('main' in data.keys()) and ('temp' in data['main'].keys()): 34 | temp_k = data['main']['temp'] 35 | temp_f = (temp_k * 9 / 5.0) - 459.67 36 | temp = "{0:.2f}".format(temp_f) 37 | 38 | # Send data to temperature logger. 39 | postTempData(config["owm_sensor_id"], temp, time, exit_on_error=True) 40 | 41 | else: 42 | print "Could not retrieve data from Open Weather Map API." 43 | exit(1) 44 | 45 | # Log the successfully-posted data. 46 | print "{0}, {1}".format(time, temp.rstrip()) 47 | -------------------------------------------------------------------------------- /scripts/outdoor-temps-wu.py: -------------------------------------------------------------------------------- 1 | # Outdoor temperature logging script - Weather Underground. 2 | # @author Jeff Geerling, 2015. 3 | 4 | import os 5 | import glob 6 | import json 7 | from datetime import datetime 8 | import calendar 9 | import requests 10 | from temp_api import postTempData 11 | 12 | # Import configuration from 'temps.conf' file. 13 | config = {} 14 | config_dir = os.path.dirname(os.path.abspath(__file__)) 15 | execfile(config_dir + "/temps.conf", config) 16 | 17 | # Read Weather Underground API key from the environment. 18 | try: 19 | wu_api_key = os.environ['WU_API_KEY'] 20 | wu_location = os.environ['WU_LOCATION'] 21 | except KeyError: 22 | print "WU_API_KEY and WU_LOCATION env vars must be set." 23 | exit(1) 24 | 25 | # Current time (UNIX timestamp). 26 | date = datetime.utcnow() 27 | time = calendar.timegm(date.utctimetuple()) 28 | 29 | # Current temperature. 30 | uri = 'http://api.wunderground.com/api/' + wu_api_key + '/conditions/q/' + wu_location + '.json' 31 | req = requests.get(uri) 32 | 33 | if req.status_code != requests.codes.ok: 34 | print "Could not retrieve current weather data." 35 | exit(1) 36 | 37 | # Get JSON as an object. 38 | data = req.json() 39 | 40 | # Log the data if it was returned successfully. 41 | if ('current_observation' in data.keys()) and ('temp_f' in data['current_observation'].keys()): 42 | temp_f = data['current_observation']['temp_f'] 43 | temp = "{0:.2f}".format(temp_f) 44 | 45 | # Send data to temperature logger. 46 | postTempData(config["wu_sensor_id"], temp, time, exit_on_error=True) 47 | 48 | else: 49 | print "Could not retrieve data from Weather Underground API." 50 | exit(1) 51 | 52 | # Log the successfully-posted data. 53 | print "{0}, {1}".format(date, temp.rstrip()) 54 | -------------------------------------------------------------------------------- /scripts/pi-temps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Raspberry Pi temperature logging script. 4 | # @author Jeff Geerling, 2015. 5 | 6 | import os 7 | import glob 8 | import time 9 | from datetime import datetime 10 | import calendar 11 | import requests 12 | from temp_api import postTempData 13 | 14 | # Import configuration from 'temps.conf' file. 15 | config = {} 16 | config_dir = os.path.dirname(os.path.abspath(__file__)) 17 | execfile(config_dir + "/temps.conf", config) 18 | 19 | # Read the temperature from a connected DS18B20 temperature sensor. 20 | def readTempFromGPIO(): 21 | base_dir = '/sys/bus/w1/devices/' 22 | device_folder = glob.glob(base_dir + '28*')[0] 23 | device_file_path = device_folder + '/w1_slave' 24 | device_file = open(device_file_path, "r") 25 | text = device_file.read() 26 | device_file.close() 27 | 28 | # Grab the second line, parse it, and find the temperature value. 29 | temp_line = text.split("\n")[1] 30 | temp_data = temp_line.split(" ")[9] 31 | temp_c = float(temp_data[2:]) / 1000 32 | temp_f = temp_c * 9.0 / 5.0 + 32.0 33 | # Adjust the temperature according to the configured offset. 34 | temp_f_adjusted = temp_f + config["local_temp_offset"] 35 | temp = "{0:.2f}".format(temp_f_adjusted) 36 | return temp 37 | 38 | while True: 39 | # Get current temperature and timestamp. 40 | temp = readTempFromGPIO() 41 | date = datetime.utcnow() 42 | timestamp = calendar.timegm(date.utctimetuple()) 43 | 44 | # Send data to temperature logger. 45 | postTempData(config["local_sensor_id"], temp, timestamp) 46 | 47 | # Log data to command line. 48 | print "{0}, {1}".format(date, temp.rstrip()) 49 | 50 | # Wait [local_temp_read_delay] seconds. 51 | time.sleep(config["local_temp_read_delay"]) 52 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.5.1 2 | -------------------------------------------------------------------------------- /scripts/temp_api.py: -------------------------------------------------------------------------------- 1 | # Temperature Dashboard API integration. 2 | # @author Jeff Geerling, 2015. 3 | 4 | import os 5 | import glob 6 | import requests 7 | 8 | # Import configuration from 'temps.conf' file. 9 | config = {} 10 | config_dir = os.path.dirname(os.path.abspath(__file__)) 11 | execfile(config_dir + "/temps.conf", config) 12 | 13 | # The URI of the dashboard app. 14 | dashboard_uri = config["dashboard_uri"] + "/temps" 15 | 16 | # Post temperature sensor data to dashboard app. 17 | def postTempData(sensor_id, temp, time, exit_on_error=False): 18 | payload = { 19 | 'sensor': sensor_id, 20 | 'temp': temp, 21 | 'time': time 22 | } 23 | 24 | try: 25 | post = requests.post(dashboard_uri, data=payload) 26 | 27 | # Print error, but don't exit, if data couldn't be written. 28 | if post.status_code != requests.codes.ok: 29 | print "Could not post data to dashboard app: " + post.json()['error'] 30 | if exit_on_error: 31 | exit(1) 32 | 33 | except requests.exceptions.ConnectionError as e: 34 | # TODO: Print the message in the ConnectionError. 35 | print "A connection could not be established with the dashboard API." 36 | if exit_on_error: 37 | exit(1) 38 | --------------------------------------------------------------------------------