Added mqtt and the first recipe

This commit is contained in:
Martin Donnelly 2017-11-09 00:05:30 +00:00
parent 071384201b
commit cdd55ccc18
31 changed files with 45741 additions and 7 deletions

55
.eslintrc Normal file
View File

@ -0,0 +1,55 @@
{
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": false
}
},
"env": {
"browser": true,
"node": true,
"es6": true
},
"rules": {
"arrow-spacing": "error",
"block-scoped-var": "error",
"block-spacing": "error",
"brace-style": ["error", "stroustrup", {}],
"camelcase": "error",
"comma-dangle": ["error", "never"],
"comma-spacing": ["error", { "before": false, "after": true }],
"comma-style": [1, "last"],
"consistent-this": [1, "_this"],
"curly": [1, "multi"],
"eol-last": 1,
"eqeqeq": 1,
"func-names": 1,
"indent": ["error", 2, { "SwitchCase": 1 }],
"lines-around-comment": ["error", { "beforeBlockComment": true, "allowArrayStart": true }],
"max-len": [1, 240, 2], // 2 spaces per tab, max 80 chars per line
"new-cap": 1,
"newline-before-return": "error",
"no-array-constructor": 1,
"no-inner-declarations": [1, "both"],
"no-mixed-spaces-and-tabs": 1,
"no-multi-spaces": 2,
"no-new-object": 1,
"no-shadow-restricted-names": 1,
"object-curly-spacing": ["error", "always"],
"padded-blocks": ["error", { "blocks": "never", "switches": "always" }],
"prefer-const": "error",
"prefer-template": "error",
"one-var": 0,
"quote-props": ["error", "always"],
"quotes": [1, "single"],
"radix": 1,
"semi": [1, "always"],
"space-before-blocks": [1, "always"],
"space-infix-ops": 1,
"vars-on-top": 1,
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
"spaced-comment": ["error", "always", { "markers": ["/"] }]
}
}

2
.gitignore vendored
View File

@ -178,3 +178,5 @@ xcuserdata
*.xcuserstate
dist
/jspm_packages/npm/
/jspm_packages/github/

276
app/css/app.css Normal file
View File

@ -0,0 +1,276 @@
body {
font-family: Ubuntu, "Helvetica Neue", Helvetica, arial, sans-serif;
background-color: #004c6d;
}
html, body {
height: 100%;
overflow: hidden;
}
#weatherIcon {
margin-left:25%;
margin-right:25%;
height:70px;
width:70px;
}
#lightR, #projR { color: red !important; }
#lightG, #projG { color: green !important; }
#lightB, #projB { color: blue !important; }
#lightW, #projW { background-color: #aabbcc; }
.lightBG, .heatingBG, .projectorBG {
float: right;
}
/*.lightBG {
background-color: rgba(255, 255, 0, 0.3);
}
.heatingBG {
background-color: rgba(255, 0, 255, 0.3);
}
.projectorBG {
background-color: rgba(0, 255, 255, 0.3);
}*/
.mui-panel {
background-color: #015579;
}
.h105 {
height: 100px;
}
.mdHeading {
overflow: hidden;
}
.mui--text-title {
color: #ffffff;
}
.item_content {
height: 100px;
/* border: 1px solid grey;*/
min-height: 100px;
overflow: hidden;
}
.item_content a.title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #ffffff;
}
.item_content div.body, .item_content div.site, .item_content div.tags {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #313131;
}
.time, .date, .temp {
font-family: 'Ubuntu Condensed', sans-serif;
font-size: 80px;
color: #bad649;
}
.time span.hour:after {
content: ":";
}
.date {
font-size: 35px;
line-height: 1;
}
.temp::after {
content: "°c";
}
.item_content div.tags {
color: blue;
}
.noConnection {
color: rgb(244, 150, 26);
}
#caltext {
color: #fff;
}
/* Smartphones (portrait and landscape) ----------- */
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
/* Styles */
.time, .date, .temp {
font-family: 'Ubuntu Condensed', sans-serif;
font-size: 33px;
/*color: #ff0000;*/
}
.time {
font-size: 50px;
line-height: 1;
}
.time span.hour:after {
content: "\a";
white-space: pre;
}
.temp {
font-size: 70px;
}
.temp::after {
content: "°";
}
.wd-we {
font-size: 75%;
}
.mo {
font-size: 85%;
}
.mo.mo-1, .mo.mo-10 {
font-size: 70%;
}
.mo.mo-2 {
font-size: 65%;
}
.mo.mo-8 {
font-size: 80%;
}
.mo.mo-9 {
font-size: 55%;
}
.mo.mo-11, .mo.mo-12 {
font-size: 60%;
}
}
/* Smartphones (landscape) ----------- */
@media only screen and (min-width: 321px) {
/* Styles */
}
/* Smartphones (portrait) ----------- */
@media only screen and (max-width: 320px) {
/* Styles */
}
.spinner {
margin: 25px auto 0;
width: 70px;
text-align: center;
}
.spinner > div {
width: 18px;
height: 18px;
background-color: rgb(244, 150, 26);
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
}
40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}
.material-icons {
color: #e5f7fd;
}
.material-icons.md-18 { font-size: 18px; }
.material-icons.md-24 { font-size: 24px; }
.material-icons.md-36 { font-size: 36px; }
.material-icons.md-48 { font-size: 48px; }
.material-icons.md-100 { font-size: 100px; }
/* Rules for using icons as black on a light background. */
.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); }
.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); }
/* Rules for using icons as white on a dark background. */
.material-icons.md-light { color: rgba(255, 255, 255, 1); }
.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); }
.material-icons.md-bulb {
content: ""
}
/*
fan : toys
<i class="material-icons">&#xE332;</i>
bulb : lightbulb_outline
<i class="material-icons">&#xE90F;</i>
calendar: event_note
<i class="material-icons">&#xE616;</i>
projector: cast
<i class="material-icons">&#xE307;</i>
*/
.md-display {
opacity: 1;
transition: opacity 0.3s, visibility 0.3s;
}
.lostConnection {
opacity: 0.5;
transition: opacity 0.3s, visibility 0.3s;
}

View File

@ -0,0 +1,15 @@
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}

1912
app/css/mui.css Normal file

File diff suppressed because it is too large Load Diff

1912
app/css/mui.custom.css Normal file

File diff suppressed because it is too large Load Diff

1
app/css/mui.min.css vendored Normal file

File diff suppressed because one or more lines are too long

321
app/index.html Normal file
View File

@ -0,0 +1,321 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Console</title>
<!-- build:fonts -->
<link rel="stylesheet"
href="http://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700"
type="text/css">
<link href='https://fonts.googleapis.com/css?family=Ubuntu+Condensed'
rel='stylesheet' type='text/css'>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<!-- endbuild -->
<!-- build:css -->
<link href="css/mui.css" rel="stylesheet" type="text/css"/>
<link href="css/app.css" rel="stylesheet" type="text/css"/>
<!-- endbuild -->
<link rel="apple-touch-icon" sizes="57x57"
href="/fav/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60"
href="/fav/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72"
href="/fav/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76"
href="/fav/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114"
href="/fav/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120"
href="/fav/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144"
href="/fav/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152"
href="/fav/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180"
href="/fav/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="/fav/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/fav/android-chrome-192x192.png"
sizes="192x192">
<link rel="icon" type="image/png" href="/fav/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/fav/favicon-16x16.png" sizes="16x16">
<!--<link rel="manifest" href="/fav/manifest.json">-->
<link rel="mask-icon" href="/fav/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="/fav/favicon.ico">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-TileImage" content="/fav/mstile-144x144.png">
<meta name="msapplication-config" content="/fav/browserconfig.xml">
<meta name="theme-color" content="#00aeef">
</head>
<body class="mui--no-user-select">
<div id="iosTaskbar" style="height:25px;display:none;"></div>
<div class="mui-container">
<div class="mui-panel" style="display: ;">
<div class="mui-row mui--text-center">
<img src="gfx/censis_logo_white.png">
</div>
</div>
<div class="mui-panel" id="noSocket" style="display: none;">
<div class="mui-row">
<div
class="mui--text-body2 mui--text-center noConnection">Lost connection to server. Waiting for connection
</div>
<div id='longWait' class="spinner" style="display: none;">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</div>
</div>
<div class="mui-panel" id="noDevice" style="display: none;">
<div class="mui-row">
<div
class="mui--text-body2 mui--text-center noConnection">We are having problems connecting to one or more of the devices. Please restart them and wait for them to reconnect.
</div>
</div>
</div>
<div class="mui-panel" id="clock">
<div class="mui-row">
<div class="mui-col-xs-4 ">
<div id="time" class="mui--text-center time">12:34</div>
</div>
<div class="mui-col-xs-4 item_content">
<div id="date" class="mui--text-center date">14 April<br>2016</div>
</div>
<div class="mui-col-xs-4 item_content">
<div id="weather" class="mui--text-center date">
<div id="weatherIcon">
<canvas id="icon1" width="210" height="210"
style="max-width: 70px; max-height:70px"></canvas>
</div>
<div class="mui--text-center" id="weatherText"></div>
</div>
</div>
</div>
</div>
<div class="mui-panel" id="extender" style="display: none;">
<div class="mui-row">
<div class="mui-col-xs-12 mui--text-center">
<div class="mui--text-title ">
Extend meeting
</div>
</div>
</div>
<div class="mui-row">
<div class="mui-col-xs-12 mui--text-center">
<button id="extend05" class="mui-btn mui-btn--fab">5</button>
<button id="extend10" class="mui-btn mui-btn--fab">10</button>
<button id="extend15" class="mui-btn mui-btn--fab">15</button>
<button id="extend30" class="mui-btn mui-btn--fab">30</button>
</div>
</div>
</div>
<div class="mui-panel" id="calendar" style="display: none;">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><span
class="material-icons md-100">&#xE616;</span></div>
<div class="mui-col-xs-8 item_content">
<div class="mui-row ">
<div class="mui--text-title "><a class='title'
href="#">Meeting room</a>
</div>
</div>
<div class="mui-row">
<div id="caltext"></div>
<div id="extendInfo" class="mui--text-subhead mui--text-accent"></div>
</div>
</div>
</div>
</div>
<div id="front-light" class="md-display">
<div class="mui-panel">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><span
class="material-icons md-100">&#xE90F;</span></div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row mui--text-center">
<div class="mui--text-title mui--text-center"><a class='title'
href="#">Front light</a>
</div>
</div>
<div class="mui-row mui--text-center">
<button id="frontLightOn" class="mui-btn mui-btn--primary lightOn">On
</button>
<button id="frontLightOff" class="mui-btn mui-btn--danger lightOff"
style="display: none;">Off
</button>
</div>
</div>
<div class="mui-col-xs-4 mui--text-center" id="auxFront"
style="display:none;">
<div>
<button class="mui-btn mui-btn--small mui-btn--accent lightUp"
id="frontUp">BRIGHTER
</button>
</div>
<div>
<button class="mui-btn mui-btn--small mui-btn--accent lightDown"
id="frontDown">DARKER
</button>
</div>
</div>
</div>
</div>
</div>
<!--<div class="mui-panel" id="middle-light">
<div class="mui-row">
<div class="mui-col-xs-4 mui&#45;&#45;text-center"><i class="material-icons md-100">&#xE90F;</i></div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row">
<div class="mui&#45;&#45;text-title mui&#45;&#45;text-center"><a class='title'
href="#">Middle light</a>
</div>
</div>
<div class="mui-row mui&#45;&#45;text-center">
<button id="middleLightOn"
class="mui-btn mui-btn&#45;&#45;primary">On
</button>
<button id="middleLightOff" class="mui-btn mui-btn&#45;&#45;danger"
style="display: none;">Off
</button>
</div>
</div>
</div>
</div>-->
<div class="mui-panel" id="back-light">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><i
class="material-icons md-100">&#xE90F;</i></div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row">
<div class="mui--text-title mui--text-center"><a class='title'
href="#">Back light</a>
</div>
</div>
<div class="mui-row mui--text-center">
<button id="backLightOn" class="mui-btn mui-btn--primary lightOn">On
</button>
<button id="backLightOff" class="mui-btn mui-btn--danger lightOff"
style="display: none;">Off
</button>
</div>
</div>
<div class="mui-col-xs-4 mui--text-center" id="auxBack"
style="display: none;">
<div>
<button class="mui-btn mui-btn--small mui-btn--accent lightUp"
id="backUp">BRIGHTER
</button>
</div>
<div>
<button class="mui-btn mui-btn--small mui-btn--accent lightDown"
id="backDown">DARKER
</button>
</div>
</div>
</div>
</div>
<div class="mui-panel" id="heating-panel" style="display: none ;">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><i
class="material-icons md-100">&#xE332;</i>
</div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row">
<div class="mui--text-title mui--text-center"><a class='title'
href="#">Fan</a>
</div>
</div>
<div class="mui-row mui--text-center">
<button id="heatingOn" class="mui-btn mui-btn--primary">On
</button>
<button id="heatingOff" class="mui-btn mui-btn--danger"
style="display: none;">Off
</button>
</div>
</div>
<div class="mui-col-xs-4 mui--text-center">
<div id="curTemp" class="temp"></div>
</div>
</div>
</div>
<div class="mui-panel" id="projector-panel" style="display:;">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><i
class="material-icons md-100">&#xE307;</i></div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row">
<div class="mui--text-title mui--text-center"><a class='title'
href="#">Projector</a>
</div>
</div>
<div class="mui-row mui--text-center">
<button id="projectorOn" class="mui-btn mui-btn--primary">On
</button>
<button id="projectorOff" class="mui-btn mui-btn--danger"
style="display: none;">Off
</button>
</div>
</div>
<div class="mui-col-xs-4 mui--text-center" id="auxProjector"
style="display:none;">
<div>
<button class="mui-btn mui-btn--small mui-btn--accent"
id="projectorHDMI">HDMI
</button>
</div>
<div>
<button class="mui-btn mui-btn--small mui-btn--accent"
id="projectorVGA">VGA
</button>
</div>
</div>
</div>
</div>
<!--<div class="mui-panel">
<div class="mui-row">
<div class="mui-col-md-3"><span id="lightR">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="lightG">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="lightB">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="lightW">-&#45;&#45;</span></div>
</div>
<div class="mui-row">
<div class="mui-col-md-3"><span id="projR">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="projG">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="projB">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="projW">-&#45;&#45;</span></div>
</div>
</div>-->
</div>
<!--<script src="cordova.js"></script>-->
<!-- build:vendor -->
<script src="//cdnjs.cloudflare.com/ajax/libs/systemjs/0.20.19/system.js"></script>
<script src="lib/mui.js"></script>
<script src="lib/jquery.js"></script>
<!--<script src="lib/chroma.js"></script>-->
<script src="lib/underscore.js"></script>
<script src="lib/backbone.js"></script>
<script src="lib/sugar-date.js"></script>
<!--<script src="lib/sugar.js"></script>-->
<!--<script src="lib/skycons.js"></script>-->
<!--<script src="lib/microevent.js"></script>-->
<!-- endbuild -->
<!-- build:js -->
<!--
<script src="js/sowebsocket.js"></script>
<script src="js/colours.js"></script>
-->
<script src="js/modules/clock.js"></script>
<!--
<script src="js/parts/meetings.js"></script>
<script src="js/parts/lights.js"></script>
<script src="js/parts/projector.js"></script>
-->
<script src="js/app.js"></script>
</body>
<!-- endbuild -->
</html>

2
app/js/app.js Normal file
View File

@ -0,0 +1,2 @@
this.clock = new Clock({ 'model': new ClockModel() });

122
app/js/modules/clock.js Normal file
View File

@ -0,0 +1,122 @@
'use strict';
/**
*
* User: Martin Donnelly
* Date: 2016-07-27
* Time: 09:23
*
*/
const WeatherModel = Backbone.Model.extend({
'initialize': function() {
this.set('url', `https://api.forecast.io/forecast/0657dc0d81c037cbc89ca88e383b6bbf/${ this.get('lat').toString() },${ this.get('long').toString() }?units=uk2&exclude=minutely,hourly,daily,alerts,flags`);
console.log(this.get('url'));
this.update();
},
'update': function() {
this.getWeather();
const now = new Date;
const mod = 1800000 - (now.getTime() % 1800000);
const weatherTrigger = function () {
this.update();
};
setTimeout(weatherTrigger.bind(this), mod + 10);
},
'getWeather': function() {
const self = this;
$.ajax({
'type': 'GET',
'url': self.get('url'),
'data': '',
'dataType': 'jsonp',
'timeout': 10000,
'context': $('body'),
'contentType': ('application/json'),
'headers': {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'PUT, GET, POST, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
},
'success': function(data) {
const stored = {
'temperature': data.currently.temperature,
'icon': data.currently.icon
};
self.set('data', stored);
},
'error': function(xhr, type) {
console.error('ajax error');
console.error(xhr);
console.error(type);
}
});
}
});
const ClockModel = Backbone.Model.extend({
'initialize': function() {
const now = new Sugar.Date();
this.set('now', now);
this.update();
},
'update': function() {
const now = new Sugar.Date();
const mod = 60000 - (now.getTime().raw % 60000);
this.set('now', now);
const clockFn = function () {
this.update();
};
setTimeout(clockFn.bind(this), mod + 10);
}
});
const Clock = Backbone.View.extend({
'tagName': 'div',
'initialize': function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.$date = $('#date');
this.$time = $('#time');
this.render();
},
'render': function() {
const now = this.model.get('now');
console.log('now', now);
const curTime = now.format('<span class="hour">{24hr}</span>{mm}').raw;
const curDate = now.format('{yyyy}-{MM}-{dd}').raw;
console.log('curTime', curTime);
console.log('curDate', curDate);
if (this.prevTime !== curTime) {
this.$time.html(curTime);
this.prevTime = curTime;
}
if (this.prevDate !== curDate) {
this.$date.html(now.format(
'<span class="wd-{do}">{Weekday}</span><br><span class="mo mo-{M}">{Month} {dd}</span><br>{yyyy}'));
this.prevDate = curDate;
}
}
});
const Weather = Backbone.View.extend({
'tagName': 'div',
'initialize': function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.$weatherText = $('#weatherText');
},
'render': function() {
console.log('Weather:Render');
const data = this.model.get('data');
this.$weatherText.html(`${parseInt(data.temperature) }&deg;c&nbsp;`);
skycons.remove('icon1');
skycons.add('icon1', data.icon);
}
});

1920
app/lib/backbone.js Normal file

File diff suppressed because it is too large Load Diff

10253
app/lib/jquery.js vendored Normal file

File diff suppressed because it is too large Load Diff

2118
app/lib/mui.js Normal file

File diff suppressed because it is too large Load Diff

730
app/lib/skycons.js Normal file
View File

@ -0,0 +1,730 @@
(function(global) {
"use strict";
/* Set up a RequestAnimationFrame shim so we can animate efficiently FOR
* GREAT JUSTICE. */
var requestInterval, cancelInterval;
(function() {
var raf = global.requestAnimationFrame ||
global.webkitRequestAnimationFrame ||
global.mozRequestAnimationFrame ||
global.oRequestAnimationFrame ||
global.msRequestAnimationFrame ,
caf = global.cancelAnimationFrame ||
global.webkitCancelAnimationFrame ||
global.mozCancelAnimationFrame ||
global.oCancelAnimationFrame ||
global.msCancelAnimationFrame ;
if(raf && caf) {
requestInterval = function(fn, delay) {
var handle = {value: null};
function loop() {
handle.value = raf(loop);
fn();
}
loop();
return handle;
};
cancelInterval = function(handle) {
caf(handle.value);
};
}
else {
requestInterval = setInterval;
cancelInterval = clearInterval;
}
}());
/* Catmull-rom spline stuffs. */
/*
function upsample(n, spline) {
var polyline = [],
len = spline.length,
bx = spline[0],
by = spline[1],
cx = spline[2],
cy = spline[3],
dx = spline[4],
dy = spline[5],
i, j, ax, ay, px, qx, rx, sx, py, qy, ry, sy, t;
for(i = 6; i !== spline.length; i += 2) {
ax = bx;
bx = cx;
cx = dx;
dx = spline[i ];
px = -0.5 * ax + 1.5 * bx - 1.5 * cx + 0.5 * dx;
qx = ax - 2.5 * bx + 2.0 * cx - 0.5 * dx;
rx = -0.5 * ax + 0.5 * cx ;
sx = bx ;
ay = by;
by = cy;
cy = dy;
dy = spline[i + 1];
py = -0.5 * ay + 1.5 * by - 1.5 * cy + 0.5 * dy;
qy = ay - 2.5 * by + 2.0 * cy - 0.5 * dy;
ry = -0.5 * ay + 0.5 * cy ;
sy = by ;
for(j = 0; j !== n; ++j) {
t = j / n;
polyline.push(
((px * t + qx) * t + rx) * t + sx,
((py * t + qy) * t + ry) * t + sy
);
}
}
polyline.push(
px + qx + rx + sx,
py + qy + ry + sy
);
return polyline;
}
function downsample(n, polyline) {
var len = 0,
i, dx, dy;
for(i = 2; i !== polyline.length; i += 2) {
dx = polyline[i ] - polyline[i - 2];
dy = polyline[i + 1] - polyline[i - 1];
len += Math.sqrt(dx * dx + dy * dy);
}
len /= n;
var small = [],
target = len,
min = 0,
max, t;
small.push(polyline[0], polyline[1]);
for(i = 2; i !== polyline.length; i += 2) {
dx = polyline[i ] - polyline[i - 2];
dy = polyline[i + 1] - polyline[i - 1];
max = min + Math.sqrt(dx * dx + dy * dy);
if(max > target) {
t = (target - min) / (max - min);
small.push(
polyline[i - 2] + dx * t,
polyline[i - 1] + dy * t
);
target += len;
}
min = max;
}
small.push(polyline[polyline.length - 2], polyline[polyline.length - 1]);
return small;
}
*/
/* Define skycon things. */
/* FIXME: I'm *really really* sorry that this code is so gross. Really, I am.
* I'll try to clean it up eventually! Promise! */
var KEYFRAME = 500,
STROKE = 0.08,
TAU = 2.0 * Math.PI,
TWO_OVER_SQRT_2 = 2.0 / Math.sqrt(2);
function circle(ctx, x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, TAU, false);
ctx.fill();
}
function line(ctx, ax, ay, bx, by) {
ctx.beginPath();
ctx.moveTo(ax, ay);
ctx.lineTo(bx, by);
ctx.stroke();
}
function puff(ctx, t, cx, cy, rx, ry, rmin, rmax) {
var c = Math.cos(t * TAU),
s = Math.sin(t * TAU);
rmax -= rmin;
circle(
ctx,
cx - s * rx,
cy + c * ry + rmax * 0.5,
rmin + (1 - c * 0.5) * rmax
);
}
function puffs(ctx, t, cx, cy, rx, ry, rmin, rmax) {
var i;
for(i = 5; i--; )
puff(ctx, t + i / 5, cx, cy, rx, ry, rmin, rmax);
}
function cloud(ctx, t, cx, cy, cw, s, color) {
t /= 30000;
var a = cw * 0.21,
b = cw * 0.12,
c = cw * 0.24,
d = cw * 0.28;
ctx.fillStyle = color;
puffs(ctx, t, cx, cy, a, b, c, d);
ctx.globalCompositeOperation = 'destination-out';
puffs(ctx, t, cx, cy, a, b, c - s, d - s);
ctx.globalCompositeOperation = 'source-over';
}
function sun(ctx, t, cx, cy, cw, s, color) {
t /= 120000;
var a = cw * 0.25 - s * 0.5,
b = cw * 0.32 + s * 0.5,
c = cw * 0.50 - s * 0.5,
i, p, cos, sin;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.arc(cx, cy, a, 0, TAU, false);
ctx.stroke();
for(i = 8; i--; ) {
p = (t + i / 8) * TAU;
cos = Math.cos(p);
sin = Math.sin(p);
line(ctx, cx + cos * b, cy + sin * b, cx + cos * c, cy + sin * c);
}
}
function moon(ctx, t, cx, cy, cw, s, color) {
t /= 15000;
var a = cw * 0.29 - s * 0.5,
b = cw * 0.05,
c = Math.cos(t * TAU),
p = c * TAU / -16;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
cx += c * b;
ctx.beginPath();
ctx.arc(cx, cy, a, p + TAU / 8, p + TAU * 7 / 8, false);
ctx.arc(cx + Math.cos(p) * a * TWO_OVER_SQRT_2, cy + Math.sin(p) * a * TWO_OVER_SQRT_2, a, p + TAU * 5 / 8, p + TAU * 3 / 8, true);
ctx.closePath();
ctx.stroke();
}
function rain(ctx, t, cx, cy, cw, s, color) {
t /= 1350;
var a = cw * 0.16,
b = TAU * 11 / 12,
c = TAU * 7 / 12,
i, p, x, y;
ctx.fillStyle = color;
for(i = 4; i--; ) {
p = (t + i / 4) % 1;
x = cx + ((i - 1.5) / 1.5) * (i === 1 || i === 2 ? -1 : 1) * a;
y = cy + p * p * cw;
ctx.beginPath();
ctx.moveTo(x, y - s * 1.5);
ctx.arc(x, y, s * 0.75, b, c, false);
ctx.fill();
}
}
function sleet(ctx, t, cx, cy, cw, s, color) {
t /= 750;
var a = cw * 0.1875,
b = TAU * 11 / 12,
c = TAU * 7 / 12,
i, p, x, y;
ctx.strokeStyle = color;
ctx.lineWidth = s * 0.5;
ctx.lineCap = "round";
ctx.lineJoin = "round";
for(i = 4; i--; ) {
p = (t + i / 4) % 1;
x = Math.floor(cx + ((i - 1.5) / 1.5) * (i === 1 || i === 2 ? -1 : 1) * a) + 0.5;
y = cy + p * cw;
line(ctx, x, y - s * 1.5, x, y + s * 1.5);
}
}
function snow(ctx, t, cx, cy, cw, s, color) {
t /= 3000;
var a = cw * 0.16,
b = s * 0.75,
u = t * TAU * 0.7,
ux = Math.cos(u) * b,
uy = Math.sin(u) * b,
v = u + TAU / 3,
vx = Math.cos(v) * b,
vy = Math.sin(v) * b,
w = u + TAU * 2 / 3,
wx = Math.cos(w) * b,
wy = Math.sin(w) * b,
i, p, x, y;
ctx.strokeStyle = color;
ctx.lineWidth = s * 0.5;
ctx.lineCap = "round";
ctx.lineJoin = "round";
for(i = 4; i--; ) {
p = (t + i / 4) % 1;
x = cx + Math.sin((p + i / 4) * TAU) * a;
y = cy + p * cw;
line(ctx, x - ux, y - uy, x + ux, y + uy);
line(ctx, x - vx, y - vy, x + vx, y + vy);
line(ctx, x - wx, y - wy, x + wx, y + wy);
}
}
function fogbank(ctx, t, cx, cy, cw, s, color) {
t /= 30000;
var a = cw * 0.21,
b = cw * 0.06,
c = cw * 0.21,
d = cw * 0.28;
ctx.fillStyle = color;
puffs(ctx, t, cx, cy, a, b, c, d);
ctx.globalCompositeOperation = 'destination-out';
puffs(ctx, t, cx, cy, a, b, c - s, d - s);
ctx.globalCompositeOperation = 'source-over';
}
/*
var WIND_PATHS = [
downsample(63, upsample(8, [
-1.00, -0.28,
-0.75, -0.18,
-0.50, 0.12,
-0.20, 0.12,
-0.04, -0.04,
-0.07, -0.18,
-0.19, -0.18,
-0.23, -0.05,
-0.12, 0.11,
0.02, 0.16,
0.20, 0.15,
0.50, 0.07,
0.75, 0.18,
1.00, 0.28
])),
downsample(31, upsample(16, [
-1.00, -0.10,
-0.75, 0.00,
-0.50, 0.10,
-0.25, 0.14,
0.00, 0.10,
0.25, 0.00,
0.50, -0.10,
0.75, -0.14,
1.00, -0.10
]))
];
*/
var WIND_PATHS = [
[
-0.7500, -0.1800, -0.7219, -0.1527, -0.6971, -0.1225,
-0.6739, -0.0910, -0.6516, -0.0588, -0.6298, -0.0262,
-0.6083, 0.0065, -0.5868, 0.0396, -0.5643, 0.0731,
-0.5372, 0.1041, -0.5033, 0.1259, -0.4662, 0.1406,
-0.4275, 0.1493, -0.3881, 0.1530, -0.3487, 0.1526,
-0.3095, 0.1488, -0.2708, 0.1421, -0.2319, 0.1342,
-0.1943, 0.1217, -0.1600, 0.1025, -0.1290, 0.0785,
-0.1012, 0.0509, -0.0764, 0.0206, -0.0547, -0.0120,
-0.0378, -0.0472, -0.0324, -0.0857, -0.0389, -0.1241,
-0.0546, -0.1599, -0.0814, -0.1876, -0.1193, -0.1964,
-0.1582, -0.1935, -0.1931, -0.1769, -0.2157, -0.1453,
-0.2290, -0.1085, -0.2327, -0.0697, -0.2240, -0.0317,
-0.2064, 0.0033, -0.1853, 0.0362, -0.1613, 0.0672,
-0.1350, 0.0961, -0.1051, 0.1213, -0.0706, 0.1397,
-0.0332, 0.1512, 0.0053, 0.1580, 0.0442, 0.1624,
0.0833, 0.1636, 0.1224, 0.1615, 0.1613, 0.1565,
0.1999, 0.1500, 0.2378, 0.1402, 0.2749, 0.1279,
0.3118, 0.1147, 0.3487, 0.1015, 0.3858, 0.0892,
0.4236, 0.0787, 0.4621, 0.0715, 0.5012, 0.0702,
0.5398, 0.0766, 0.5768, 0.0890, 0.6123, 0.1055,
0.6466, 0.1244, 0.6805, 0.1440, 0.7147, 0.1630,
0.7500, 0.1800
],
[
-0.7500, 0.0000, -0.7033, 0.0195, -0.6569, 0.0399,
-0.6104, 0.0600, -0.5634, 0.0789, -0.5155, 0.0954,
-0.4667, 0.1089, -0.4174, 0.1206, -0.3676, 0.1299,
-0.3174, 0.1365, -0.2669, 0.1398, -0.2162, 0.1391,
-0.1658, 0.1347, -0.1157, 0.1271, -0.0661, 0.1169,
-0.0170, 0.1046, 0.0316, 0.0903, 0.0791, 0.0728,
0.1259, 0.0534, 0.1723, 0.0331, 0.2188, 0.0129,
0.2656, -0.0064, 0.3122, -0.0263, 0.3586, -0.0466,
0.4052, -0.0665, 0.4525, -0.0847, 0.5007, -0.1002,
0.5497, -0.1130, 0.5991, -0.1240, 0.6491, -0.1325,
0.6994, -0.1380, 0.7500, -0.1400
]
],
WIND_OFFSETS = [
{start: 0.36, end: 0.11},
{start: 0.56, end: 0.16}
];
function leaf(ctx, t, x, y, cw, s, color) {
var a = cw / 8,
b = a / 3,
c = 2 * b,
d = (t % 1) * TAU,
e = Math.cos(d),
f = Math.sin(d);
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.arc(x , y , a, d , d + Math.PI, false);
ctx.arc(x - b * e, y - b * f, c, d + Math.PI, d , false);
ctx.arc(x + c * e, y + c * f, b, d + Math.PI, d , true );
ctx.globalCompositeOperation = 'destination-out';
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
ctx.stroke();
}
function swoosh(ctx, t, cx, cy, cw, s, index, total, color) {
t /= 2500;
var path = WIND_PATHS[index],
a = (t + index - WIND_OFFSETS[index].start) % total,
c = (t + index - WIND_OFFSETS[index].end ) % total,
e = (t + index ) % total,
b, d, f, i;
ctx.strokeStyle = color;
ctx.lineWidth = s;
ctx.lineCap = "round";
ctx.lineJoin = "round";
if(a < 1) {
ctx.beginPath();
a *= path.length / 2 - 1;
b = Math.floor(a);
a -= b;
b *= 2;
b += 2;
ctx.moveTo(
cx + (path[b - 2] * (1 - a) + path[b ] * a) * cw,
cy + (path[b - 1] * (1 - a) + path[b + 1] * a) * cw
);
if(c < 1) {
c *= path.length / 2 - 1;
d = Math.floor(c);
c -= d;
d *= 2;
d += 2;
for(i = b; i !== d; i += 2)
ctx.lineTo(cx + path[i] * cw, cy + path[i + 1] * cw);
ctx.lineTo(
cx + (path[d - 2] * (1 - c) + path[d ] * c) * cw,
cy + (path[d - 1] * (1 - c) + path[d + 1] * c) * cw
);
}
else
for(i = b; i !== path.length; i += 2)
ctx.lineTo(cx + path[i] * cw, cy + path[i + 1] * cw);
ctx.stroke();
}
else if(c < 1) {
ctx.beginPath();
c *= path.length / 2 - 1;
d = Math.floor(c);
c -= d;
d *= 2;
d += 2;
ctx.moveTo(cx + path[0] * cw, cy + path[1] * cw);
for(i = 2; i !== d; i += 2)
ctx.lineTo(cx + path[i] * cw, cy + path[i + 1] * cw);
ctx.lineTo(
cx + (path[d - 2] * (1 - c) + path[d ] * c) * cw,
cy + (path[d - 1] * (1 - c) + path[d + 1] * c) * cw
);
ctx.stroke();
}
if(e < 1) {
e *= path.length / 2 - 1;
f = Math.floor(e);
e -= f;
f *= 2;
f += 2;
leaf(
ctx,
t,
cx + (path[f - 2] * (1 - e) + path[f ] * e) * cw,
cy + (path[f - 1] * (1 - e) + path[f + 1] * e) * cw,
cw,
s,
color
);
}
}
var Skycons = function(opts) {
this.list = [];
this.interval = null;
this.color = opts && opts.color ? opts.color : "black";
this.resizeClear = !!(opts && opts.resizeClear);
};
Skycons.CLEAR_DAY = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
sun(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};
Skycons.CLEAR_NIGHT = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
moon(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};
Skycons.PARTLY_CLOUDY_DAY = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
sun(ctx, t, w * 0.625, h * 0.375, s * 0.75, s * STROKE, color);
cloud(ctx, t, w * 0.375, h * 0.625, s * 0.75, s * STROKE, color);
};
Skycons.PARTLY_CLOUDY_NIGHT = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
moon(ctx, t, w * 0.667, h * 0.375, s * 0.75, s * STROKE, color);
cloud(ctx, t, w * 0.375, h * 0.625, s * 0.75, s * STROKE, color);
};
Skycons.CLOUDY = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
cloud(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};
Skycons.RAIN = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
rain(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
cloud(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
};
Skycons.SLEET = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
sleet(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
cloud(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
};
Skycons.SNOW = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
snow(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
cloud(ctx, t, w * 0.5, h * 0.37, s * 0.9, s * STROKE, color);
};
Skycons.WIND = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h);
swoosh(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, 0, 2, color);
swoosh(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, 1, 2, color);
};
Skycons.FOG = function(ctx, t, color) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
s = Math.min(w, h),
k = s * STROKE;
fogbank(ctx, t, w * 0.5, h * 0.32, s * 0.75, k, color);
t /= 5000;
var a = Math.cos((t ) * TAU) * s * 0.02,
b = Math.cos((t + 0.25) * TAU) * s * 0.02,
c = Math.cos((t + 0.50) * TAU) * s * 0.02,
d = Math.cos((t + 0.75) * TAU) * s * 0.02,
n = h * 0.936,
e = Math.floor(n - k * 0.5) + 0.5,
f = Math.floor(n - k * 2.5) + 0.5;
ctx.strokeStyle = color;
ctx.lineWidth = k;
ctx.lineCap = "round";
ctx.lineJoin = "round";
line(ctx, a + w * 0.2 + k * 0.5, e, b + w * 0.8 - k * 0.5, e);
line(ctx, c + w * 0.2 + k * 0.5, f, d + w * 0.8 - k * 0.5, f);
};
Skycons.prototype = {
_determineDrawingFunction: function(draw) {
if(typeof draw === "string")
draw = Skycons[draw.toUpperCase().replace(/-/g, "_")] || null;
return draw;
},
add: function(el, draw) {
var obj;
if(typeof el === "string")
el = document.getElementById(el);
// Does nothing if canvas name doesn't exists
if(el === null)
return;
draw = this._determineDrawingFunction(draw);
// Does nothing if the draw function isn't actually a function
if(typeof draw !== "function")
return;
obj = {
element: el,
context: el.getContext("2d"),
drawing: draw
};
this.list.push(obj);
this.draw(obj, KEYFRAME);
},
set: function(el, draw) {
var i;
if(typeof el === "string")
el = document.getElementById(el);
for(i = this.list.length; i--; )
if(this.list[i].element === el) {
this.list[i].drawing = this._determineDrawingFunction(draw);
this.draw(this.list[i], KEYFRAME);
return;
}
this.add(el, draw);
},
remove: function(el) {
var i;
if(typeof el === "string")
el = document.getElementById(el);
for(i = this.list.length; i--; )
if(this.list[i].element === el) {
this.list.splice(i, 1);
return;
}
},
draw: function(obj, time) {
var canvas = obj.context.canvas;
if(this.resizeClear)
canvas.width = canvas.width;
else
obj.context.clearRect(0, 0, canvas.width, canvas.height);
obj.drawing(obj.context, time, this.color);
},
play: function() {
var self = this;
this.pause();
this.interval = requestInterval(function() {
var now = Date.now(),
i;
for(i = self.list.length; i--; )
self.draw(self.list[i], now);
}, 1000 / 60);
},
pause: function() {
var i;
if(this.interval) {
cancelInterval(this.interval);
this.interval = null;
}
}
};
global.Skycons = Skycons;
}(this));

6615
app/lib/sugar-date.js Normal file

File diff suppressed because it is too large Load Diff

13407
app/lib/sugar.js Normal file

File diff suppressed because it is too large Load Diff

5
app/lib/system.min.js vendored Normal file
View File

@ -0,0 +1,5 @@
// system.js - http://github.com/mrdoob/system.js
'use strict';var System={browser:function(){var a=navigator.userAgent;return/Arora/i.test(a)?"Arora":/Opera|OPR/.test(a)?"Opera":/Maxthon/i.test(a)?"Maxthon":/Vivaldi/i.test(a)?"Vivaldi":/YaBrowser/i.test(a)?"Yandex":/Chrome/i.test(a)?"Chrome":/Epiphany/i.test(a)?"Epiphany":/Firefox/i.test(a)?"Firefox":/Mobile(\/.*)? Safari/i.test(a)?"Mobile Safari":/MSIE/i.test(a)?"Internet Explorer":/Midori/i.test(a)?"Midori":/Safari/i.test(a)?"Safari":!1}(),os:function(){var a=navigator.userAgent;return/Android/i.test(a)?
"Android":/CrOS/i.test(a)?"Chrome OS":/iP[ao]d|iPhone/i.test(a)?"iOS":/Linux/i.test(a)?"Linux":/Mac OS/i.test(a)?"Mac OS":/windows/i.test(a)?"Windows":!1}(),support:{canvas:!!window.CanvasRenderingContext2D,localStorage:function(){try{return!!window.localStorage.getItem}catch(a){return!1}}(),file:!!window.File&&!!window.FileReader&&!!window.FileList&&!!window.Blob,fileSystem:!!window.requestFileSystem||!!window.webkitRequestFileSystem,getUserMedia:!!window.navigator.getUserMedia||!!window.navigator.webkitGetUserMedia||
!!window.navigator.mozGetUserMedia||!!window.navigator.msGetUserMedia,requestAnimationFrame:!!window.mozRequestAnimationFrame||!!window.webkitRequestAnimationFrame||!!window.oRequestAnimationFrame||!!window.msRequestAnimationFrame,sessionStorage:function(){try{return!!window.sessionStorage.getItem}catch(a){return!1}}(),svg:function(){try{return!!document.createElementNS&&!!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect}catch(a){return!1}}(),webgl:function(){try{return!!window.WebGLRenderingContext&&
!!document.createElement("canvas").getContext("experimental-webgl")}catch(a){return!1}}(),worker:!!window.Worker}};

1548
app/lib/underscore.js Normal file

File diff suppressed because it is too large Load Diff

17
app/test.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
</head>
<body>
<script>
SystemJS.import('js/app.js');
</script>
</body>
</html>

70
lib/aida.js Normal file
View File

@ -0,0 +1,70 @@
const em = require('events').EventEmitter;
const util = require('util');
const logger = require('log4js').getLogger('Aida');
const _ = require('lodash');
const Lights = require('./light-controller');
const Wemo = require('./wemo-controller');
const HS100 = require('./hs100-controller');
const Hive = require('./hive-controller');
const MqttController = require('./mqtt-controller');
const { BedroomRecipe } = require('./recipes');
logger.level = 'debug';
const devices = {
'sensors': {},
'lights': {},
'wemo': {},
'heating': {}
};
const Aida = function() {
logger.debug('Aida!!');
const self = this;
this.on('newListener', function(listener) {
logger.debug(`Event Listener: ${ listener}`);
});
this.init();
};
util.inherits(Aida, em);
Aida.prototype.init = function() {
this.lights = new Lights();
this.wemo = new Wemo();
this.hs100 = new HS100();
// this.hive = new Hive();
this.mqtt = new MqttController();
this.lights.on('found', (d) => {
if (devices.lights[d.id] === undefined) {
logger.debug('Adding to light list');
devices.lights[d.id] = d;
}
});
this.wemo.on('found', (d) => {
logger.debug('Found Wemo switch:', d.macAddress);
});
this.hs100.on('found', (d) => {
logger.debug('Found hs100 switch:', d.deviceId);
});
/* this.hive.on('update', (d) => {
logger.debug('Heating updated');
devices.heating = d;
});*/
this.mqtt.on('found', () => {
logger.debug('MQTT found device');
});
// this.hive.update();
this.BedroomRecipe = new BedroomRecipe({ 'mqtt':this.mqtt, 'wemo':this.wemo });
};
module.exports = Aida;

61
lib/hive-controller.js Normal file
View File

@ -0,0 +1,61 @@
const Hive = require('bg-hive-api');
const ClimateControl = require('bg-hive-api/climateControl');
const em = require('events').EventEmitter;
const util = require('util');
const logger = require('log4js').getLogger('Hive');
logger.level = 'debug';
const config = {
'login': 'martind2000@gmail.com',
'password': '1V3D4m526i'
};
const hive = new Hive(config.login, config.password);
const HiveController = function() {
const _this = this;
hive.on('login', function(context) {
logger.debug('Connected');
// hive.Logout();
const climate = new ClimateControl(context);
// Handle the on complete event.
climate.on('complete', function(response) {
// write the response state object to the console.
// console.log(response);
_this.emit('update', response);
// log out
hive.Logout();
});
climate.GetState();
});
// On logout call this event handler
hive.on('logout', () => {
logger.info('Connection Closed');
});
// On invalid username or password
hive.on('not_authorised', () => {
logger.error('Connection Refused');
});
// Log in
this.on('newListener', listener => {
logger.info(`Event Listener: ${ listener}`);
});
return this;
};
HiveController.prototype.update = function() {
hive.Login();
};
util.inherits(HiveController, em);
module.exports = HiveController;

39
lib/hs100-controller.js Normal file
View File

@ -0,0 +1,39 @@
const Hs100Api = require('hs100-api');
const client = new Hs100Api.Client();
const em = require('events').EventEmitter;
const util = require('util');
const logger = require('log4js').getLogger('hs100');
logger.level = 'debug';
const Hs100Controller = function() {
const _this = this;
client.startDiscovery().on('device-new', (device) => {
device.getSysInfo().then((deviceInfo) => {
logger.debug(deviceInfo);
_this.emit('found', deviceInfo);
});
device.setPowerState(true);
device.startPolling(10000);
device.on('power-on', (d) => {
logger.debug('Power on', d);
});
device.on('power-off', (d) => {
logger.debug('Power off', d);
});
});
//
this.on('newListener', listener => {
logger.info(`Event Listener: ${ listener}`);
});
return this;
};
util.inherits(Hs100Controller, em);
module.exports = Hs100Controller;

77
lib/light-controller.js Normal file
View File

@ -0,0 +1,77 @@
const LifxClient = require('node-lifx').Client;
const client = new LifxClient();
const em = require('events').EventEmitter;
const util = require('util');
const logger = require('log4js').getLogger('Lights');
logger.level = 'debug';
const LightController = function() {
const _this = this;
client.on('light-new', light => {
_this.emit('found', light);
logger.debug('New light found.');
logger.debug(`ID: ${ light.id}`);
logger.debug(`IP: ${ light.address }:${ light.port}`);
light.getState((err, info) => {
if (err)
logger.debug(err);
logger.debug(`Label: ${ info.label}`);
logger.debug('Power:', (info.power === 1) ? 'on' : 'off');
logger.debug('Color:', info.color);
logger.debug('info', info);
});
light.getHardwareVersion((err, info) => {
if (err)
logger.debug(err);
logger.debug(`Device Info: ${ info.vendorName } - ${ info.productName}`);
logger.debug('Features: ', info.productFeatures, '\n');
});
});
client.on('listening', () => {
const address = client.address();
logger.debug(
`Started LIFX listening on ${
address.address }:${ address.port }\n`
);
});
client.on('light-offline', light => {
logger.debug(`Light: ${light.id} has gone offline`);
});
client.on('light-online', light => {
logger.debug(`Light: ${light.id} has come online`);
});
client.on('message', msg => {
// logger.debug('>> msg', msg);
});
client.init({
'lightOfflineTolerance': 3, // A light is offline if not seen for the given amount of discoveries
'messageHandlerTimeout': 45000, // in ms, if not answer in time an error is provided to get methods
'startDiscovery': true, // start discovery after initialization
'resendPacketDelay': 150, // delay between packages if light did not receive a packet (for setting methods with callback)
'resendMaxTimes': 3, // resend packages x times if light did not receive a packet (for setting methods with callback)
'debug': false, // logs all messages in console if turned on
'address': '0.0.0.0', // the IPv4 address to bind the udp connection to
'broadcast': '255.255.255.255', // set's the IPv4 broadcast address which is addressed to discover bulbs
'lights': [] // Can be used provide a list of known light IPv4 ip addresses if broadcast packets in network are not allowed
// For example: ['192.168.0.112', '192.168.0.114'], this will then be addressed directly
});
this.on('newListener', listener => {
logger.info(`Event Listener: ${ listener}`);
});
return this;
};
util.inherits(LightController, em);
module.exports = LightController;

47
lib/mqtt-controller.js Normal file
View File

@ -0,0 +1,47 @@
const mqtt = require('mqtt');
const em = require('events').EventEmitter;
const util = require('util');
const logger = require('log4js').getLogger('MQTT');
logger.level = 'trace';
const MqttController = function() {
const _this = this;
const options = {
'keepalive': 3600,
'clientId': 'aida',
'clean' : false
};
this.on('newListener', listener => {
logger.info(`Event Listener: ${ listener}`);
});
logger.debug('Trying to connect');
this.client = mqtt.connect('mqtt://192.168.1.150', options);
this.client.on('error', function(m) {
logger.error(m);
}.bind(this));
this.client.on('connect', function () {
logger.debug('MQTT Connected');
this.client.subscribe('bedroomTemp');
this.emit('found');
}.bind(this));
this.client.on('message', function (topic, message) {
// message is Buffer
const msg = message.toString();
const json = JSON.parse(msg);
// client.end()
this.emit(topic, json.temp);
}.bind(this));
return this;
};
util.inherits(MqttController, em);
module.exports = MqttController;

98
lib/recipes.js Normal file
View File

@ -0,0 +1,98 @@
const em = require('events').EventEmitter;
const util = require('util');
const logger = require('log4js').getLogger('BedroomRecipe');
logger.level = 'debug';
const BedroomRecipe = function(devices) {
const _this = this;
this.mqtt = devices.mqtt;
this.wemo = devices.wemo;
this.globalMode = 'FanOff';
this.bedroom = {
'temp': 0,
'last': 0,
'data': []
};
this.ranges = {
'day': { 'low': 20.2, 'high': 22.0, 'max': 22.7 },
'night': { 'low': 20.2, 'high': 22.0, 'max': 22.7 }
};
logger.debug('Setting up bedroom recipe');
this.fanTimer = function() {
let n;
const onTime = 900000;
const now = new Date;
const mod = onTime - (now.getTime() % onTime);
const day = now.getDay();
const daytimeLimits = { 'low': 27900000, 'high': 63000000 };
const nowMS = (now.getHours() * 3600000) + (now.getMinutes() * 60000);
const curRange = (nowMS < 25200000) ? this.ranges.night : this.ranges.day;
if (this.globalMode === 'FanOff') {
logger.info(`Fans off, temp should be <= ${curRange.low}`);
mode = (parseFloat(this.bedroom.temp) <= curRange.low) ? 'FanOn' : 'FanOff';
}
else {
logger.info(`Fans on, temp should not be less than ${curRange.high}`);
mode = (parseFloat(this.bedroom.temp) <= curRange.high) ? 'FanOn' : 'FanOff';
}
if ((this.globalMode !== 'FanOff' || mode !== 'FanOff') && ((day >= 1 && day <= 5) && (nowMS >= daytimeLimits.low && nowMS <= daytimeLimits.high))) {
logger.info('Week day');
mode = 'FanOff';
}
n = now.getTime() - this.lastMsg;
logger.info('Last msg', n);
if (n >= 600000) {
logger.error('No message received for over 10 minutes');
mode = 'FanOff';
logger.warn('Setting quit for 15 seconds.');
setTimeout(() => {
throw new error('Ejecting for restart');
}, 15000);
}
logger.info('LR temp:', this.bedroom.temp);
// const data = { 'id': 'temperature', 'data': { 'mode': this.globalMode, 'temp': this.bedroom.temp } };
logger.debug('Mode', mode);
if (this.bedroom.temp !== 0) {
logger.debug('trying to turn fann off');
_this.wemo.emit('wemo', mode);
}
setTimeout(this.fanTimer.bind(this), mod + 500);
};
const updateTemp = (temp) => {
const now = new Date;
const nowMS = (now.getHours() * 3600000) + (now.getMinutes() * 60000);
const curRange = (nowMS < 25200000) ? this.ranges.night : this.ranges.day;
this.bedroom.temp = parseFloat(temp);
logger.info(this.bedroom.temp, this.bedroom.temp >= curRange.max);
if (this.bedroom.temp >= curRange.max) {
logger.warn('Max temp reached, turn off');
_this.wemo.emit('wemo', 'fanOff');
}
};
this.mqtt.on('bedroomTemp', (d) => {
updateTemp(d);
});
setTimeout(this.fanTimer.bind(this), 10000);
};
util.inherits(BedroomRecipe, em);
module.exports = { BedroomRecipe };

60
lib/wemo-controller.js Normal file
View File

@ -0,0 +1,60 @@
const Wemo = require('wemo-client');
const wemo = new Wemo();
const em = require('events').EventEmitter;
const util = require('util');
const logger = require('log4js').getLogger('Wemo');
logger.level = 'debug';
const WemoController = function() {
const _this = this;
wemo.discover(function(err, deviceInfo) {
logger.debug('Wemo Device Found: %j', deviceInfo);
_this.emit('found', deviceInfo);
// Get the client for the found device
_this.client = wemo.client(deviceInfo);
// You definitely want to listen to error events (e.g. device went offline),
// Node will throw them as an exception if they are left unhandled
_this.client.on('error', function(err) {
logger.error('Error: %s', err.code);
});
// Handle BinaryState events
_this.client.on('binaryState', function(value) {
logger.info('Binary State changed to: %s', value);
});
// Turn the switch on
_this.client.setBinaryState(1);
});
this.on('newListener', listener => {
logger.info(`Event Listener: ${ listener}`);
});
this.on('wemo-off', function() {
_this.client.setBinaryState(0);
});
this.on('wemo-on', function() {
_this.client.setBinaryState(1);
});
this.on('wemo', function(d) {
logger.debug('onWemo msg', d);
if (d === 'FanOn')
this.emit('wemo-on');
if (d === 'FanOff')
this.emit('wemo-off');
});
return this;
};
util.inherits(WemoController, em);
module.exports = WemoController;

50
lib/wshandlerv2.js Normal file
View File

@ -0,0 +1,50 @@
/**
*
* User: Martin Donnelly
* Date: 2016-09-07
* Time: 15:33
*
*/
const url = require('url');
const logger = require('log4js').getLogger();
module.exports = function(events, wsServer) {
'use strict';
logger.debug('>> new WS', wsServer);
wsServer.on('connection', function connection(ws) {
// console.log('>> WS:', ws);
// const location = url.parse(ws.upgradeReq.url, true);
logger.info('Creating event for this connection');
logger.info(`${new Date() } Connection accepted.`);
const sendSocketHandler = (obj) => {
logger.debug('sendSocketHandler', obj);
try {
ws.send(JSON.stringify(obj));
}
catch (err) {
logger.error(err);
logger.warn('Offending object: ', obj);
}
};
events.on('sendSocket', sendSocketHandler);
ws.on('message', function(message) {
console.log('received:', message);
if (message === 'update')
events.emit('update');
});
ws.on('close', function(reasonCode, description) {
logger.info(`${new Date() } Peer ${ connection.remoteAddress } disconnected.`);
events.removeListener('sendSocket', sendSocketHandler);
});
});
return module;
};

3581
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "aida-server",
"version": "1.0.0",
"description": "Artificial Intelligent Digital Assistant (A.I.D.A.)",
"main": "index.js",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
@ -13,12 +13,38 @@
"author": "",
"license": "ISC",
"devDependencies": {
"express": "^4.15.3"
"babel-eslint": "^8.0.0",
"eslint": "^4.7.1",
"express": "^4.15.4",
"ws": "^3.2.0"
},
"dependencies": {
"bg-hive-api": "^1.0.5",
"hs100-api": "^0.4.0",
"body-parser": "^1.18.1",
"cookieparser": "^0.1.0",
"errorhandler": "^1.5.0",
"express-session": "^1.15.5",
"hs100-api": "^0.14.0",
"lodash": "^4.17.4",
"log4js": "^2.3.3",
"method-override": "^2.3.9",
"morgan": "^1.8.2",
"mqtt": "^2.14.0",
"node-lifx": "^0.8.0",
"wemo-client": "^0.13.0"
"trend": "^0.3.0",
"wemo-client": "^0.14.0"
},
"jspm": {
"directories": {
"baseURL": "app"
},
"dependencies": {
"backbone": "npm:backbone@^1.3.3"
},
"devDependencies": {
"babel": "npm:babel-core@^5.8.24",
"babel-runtime": "npm:babel-runtime@^5.8.24",
"core-js": "npm:core-js@^1.1.4"
}
}
}

View File

@ -1,8 +1,84 @@
/**
* Created by mdonnel on 07/06/2017.
*/
const express = require('express');
const path = require('path');
const http = require('http');
var express = require('express');
var path = require('path');
var http = require('http');
const morgan = require('morgan');
const cookieParser = require('cookieparser');
const session = require('express-session');
const methodoverride = require('method-override');
const bodyparser = require('body-parser');
const errorhandler = require('errorhandler');
const logger = require('log4js').getLogger();
const SocketHandler = require('./lib/wshandlerv2');
const Aida = require('./lib/aida');
const EventEmitter = require('events').EventEmitter;
const WebSocket = require('ws');
const app = express();
GLOBAL.lastcheck = 0;
const port = process.env.PORT || 9010;
const isProduction = (process.env.NODE_ENV === 'production');
const staticDir = isProduction ? 'dist' : 'app';
logger.warn('isProduction:', isProduction);
// app.configure(function () {
app.set('port', port);
app.set('view engine', 'ejs');
app.use(morgan('dev'));
// app.use(cookieParser('your secret here'));
/* app.use(session({
secret: 'd2jRT6ZpYFsXsF3kGS21ZszKbPAaEa', resave: false,
saveUninitialized: false
}));*/
/* 'default', 'short', 'tiny', 'dev' */
app.use(methodoverride());
app.use(bodyparser.urlencoded({ 'extended': false }));
// parse application/json
app.use(bodyparser.json());
app.all('/*', (req, res, next) => {
// CORS headers
res.header('Access-Control-Allow-Origin', '*'); // restrict it to the required domain
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
// Set custom headers for CORS
res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key');
if (req.method === 'OPTIONS')
res.status(200).end();
else
next();
});
// app.use(app.router);
app.use(express.static(path.join(__dirname, staticDir)));
app.use(errorhandler({ 'dumpExceptions': true, 'showStack': true }));
/*app.get('/', function(req, res) {
// res.render('pages/slackV2-min');
res.render('pages/index');
});*/
const aidaServer = new Aida();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
const webSocket = new SocketHandler(EventEmitter, wss);
// server.on('request', app);
server.listen(port, () => {
logger.info(`New server listening on ${ server.address().port}`);
});

318
views/pages/index.ejs Normal file
View File

@ -0,0 +1,318 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Console</title>
<!-- build:fonts -->
<link rel="stylesheet"
href="http://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700"
type="text/css">
<link href='https://fonts.googleapis.com/css?family=Ubuntu+Condensed'
rel='stylesheet' type='text/css'>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<!-- endbuild -->
<!-- build:css -->
<link href="css/mui.css" rel="stylesheet" type="text/css"/>
<link href="css/app.css" rel="stylesheet" type="text/css"/>
<!-- endbuild -->
<link rel="apple-touch-icon" sizes="57x57"
href="/fav/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60"
href="/fav/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72"
href="/fav/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76"
href="/fav/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114"
href="/fav/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120"
href="/fav/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144"
href="/fav/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152"
href="/fav/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180"
href="/fav/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="/fav/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/fav/android-chrome-192x192.png"
sizes="192x192">
<link rel="icon" type="image/png" href="/fav/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/fav/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="/fav/manifest.json">
<link rel="mask-icon" href="/fav/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="/fav/favicon.ico">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-TileImage" content="/fav/mstile-144x144.png">
<meta name="msapplication-config" content="/fav/browserconfig.xml">
<meta name="theme-color" content="#00aeef">
</head>
<body class="mui--no-user-select">
<div id="iosTaskbar" style="height:25px;display:none;"></div>
<div class="mui-container">
<div class="mui-panel" style="display: ;">
<div class="mui-row mui--text-center">
<img src="gfx/censis_logo_white.png">
</div>
</div>
<div class="mui-panel" id="noSocket" style="display: none;">
<div class="mui-row">
<div
class="mui--text-body2 mui--text-center noConnection">Lost connection to server. Waiting for connection
</div>
<div id='longWait' class="spinner" style="display: none;">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</div>
</div>
<div class="mui-panel" id="noDevice" style="display: none;">
<div class="mui-row">
<div
class="mui--text-body2 mui--text-center noConnection">We are having problems connecting to one or more of the devices. Please restart them and wait for them to reconnect.
</div>
</div>
</div>
<div class="mui-panel" id="clock">
<div class="mui-row">
<div class="mui-col-xs-4 ">
<div id="time" class="mui--text-center time">12:34</div>
</div>
<div class="mui-col-xs-4 item_content">
<div id="date" class="mui--text-center date">14 April<br>2016</div>
</div>
<div class="mui-col-xs-4 item_content">
<div id="weather" class="mui--text-center date">
<div id="weatherIcon">
<canvas id="icon1" width="210" height="210"
style="max-width: 70px; max-height:70px"></canvas>
</div>
<div class="mui--text-center" id="weatherText"></div>
</div>
</div>
</div>
</div>
<div class="mui-panel" id="extender" style="display: none;">
<div class="mui-row">
<div class="mui-col-xs-12 mui--text-center">
<div class="mui--text-title ">
Extend meeting
</div>
</div>
</div>
<div class="mui-row">
<div class="mui-col-xs-12 mui--text-center">
<button id="extend05" class="mui-btn mui-btn--fab">5</button>
<button id="extend10" class="mui-btn mui-btn--fab">10</button>
<button id="extend15" class="mui-btn mui-btn--fab">15</button>
<button id="extend30" class="mui-btn mui-btn--fab">30</button>
</div>
</div>
</div>
<div class="mui-panel" id="calendar" style="display: none;">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><span
class="material-icons md-100">&#xE616;</span></div>
<div class="mui-col-xs-8 item_content">
<div class="mui-row ">
<div class="mui--text-title "><a class='title'
href="#">Meeting room</a>
</div>
</div>
<div class="mui-row">
<div id="caltext"></div>
<div id="extendInfo" class="mui--text-subhead mui--text-accent"></div>
</div>
</div>
</div>
</div>
<div id="front-light" class="md-display">
<div class="mui-panel">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><span
class="material-icons md-100">&#xE90F;</span></div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row mui--text-center">
<div class="mui--text-title mui--text-center"><a class='title'
href="#">Front light</a>
</div>
</div>
<div class="mui-row mui--text-center">
<button id="frontLightOn" class="mui-btn mui-btn--primary lightOn">On
</button>
<button id="frontLightOff" class="mui-btn mui-btn--danger lightOff"
style="display: none;">Off
</button>
</div>
</div>
<div class="mui-col-xs-4 mui--text-center" id="auxFront"
style="display:none;">
<div>
<button class="mui-btn mui-btn--small mui-btn--accent lightUp"
id="frontUp">BRIGHTER
</button>
</div>
<div>
<button class="mui-btn mui-btn--small mui-btn--accent lightDown"
id="frontDown">DARKER
</button>
</div>
</div>
</div>
</div>
</div>
<!--<div class="mui-panel" id="middle-light">
<div class="mui-row">
<div class="mui-col-xs-4 mui&#45;&#45;text-center"><i class="material-icons md-100">&#xE90F;</i></div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row">
<div class="mui&#45;&#45;text-title mui&#45;&#45;text-center"><a class='title'
href="#">Middle light</a>
</div>
</div>
<div class="mui-row mui&#45;&#45;text-center">
<button id="middleLightOn"
class="mui-btn mui-btn&#45;&#45;primary">On
</button>
<button id="middleLightOff" class="mui-btn mui-btn&#45;&#45;danger"
style="display: none;">Off
</button>
</div>
</div>
</div>
</div>-->
<div class="mui-panel" id="back-light">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><i
class="material-icons md-100">&#xE90F;</i></div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row">
<div class="mui--text-title mui--text-center"><a class='title'
href="#">Back light</a>
</div>
</div>
<div class="mui-row mui--text-center">
<button id="backLightOn" class="mui-btn mui-btn--primary lightOn">On
</button>
<button id="backLightOff" class="mui-btn mui-btn--danger lightOff"
style="display: none;">Off
</button>
</div>
</div>
<div class="mui-col-xs-4 mui--text-center" id="auxBack"
style="display: none;">
<div>
<button class="mui-btn mui-btn--small mui-btn--accent lightUp"
id="backUp">BRIGHTER
</button>
</div>
<div>
<button class="mui-btn mui-btn--small mui-btn--accent lightDown"
id="backDown">DARKER
</button>
</div>
</div>
</div>
</div>
<div class="mui-panel" id="heating-panel" style="display: none ;">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><i
class="material-icons md-100">&#xE332;</i>
</div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row">
<div class="mui--text-title mui--text-center"><a class='title'
href="#">Fan</a>
</div>
</div>
<div class="mui-row mui--text-center">
<button id="heatingOn" class="mui-btn mui-btn--primary">On
</button>
<button id="heatingOff" class="mui-btn mui-btn--danger"
style="display: none;">Off
</button>
</div>
</div>
<div class="mui-col-xs-4 mui--text-center">
<div id="curTemp" class="temp"></div>
</div>
</div>
</div>
<div class="mui-panel" id="projector-panel" style="display:;">
<div class="mui-row">
<div class="mui-col-xs-4 mui--text-center"><i
class="material-icons md-100">&#xE307;</i></div>
<div class="mui-col-xs-4 item_content">
<div class="mui-row">
<div class="mui--text-title mui--text-center"><a class='title'
href="#">Projector</a>
</div>
</div>
<div class="mui-row mui--text-center">
<button id="projectorOn" class="mui-btn mui-btn--primary">On
</button>
<button id="projectorOff" class="mui-btn mui-btn--danger"
style="display: none;">Off
</button>
</div>
</div>
<div class="mui-col-xs-4 mui--text-center" id="auxProjector"
style="display:none;">
<div>
<button class="mui-btn mui-btn--small mui-btn--accent"
id="projectorHDMI">HDMI
</button>
</div>
<div>
<button class="mui-btn mui-btn--small mui-btn--accent"
id="projectorVGA">VGA
</button>
</div>
</div>
</div>
</div>
<!--<div class="mui-panel">
<div class="mui-row">
<div class="mui-col-md-3"><span id="lightR">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="lightG">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="lightB">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="lightW">-&#45;&#45;</span></div>
</div>
<div class="mui-row">
<div class="mui-col-md-3"><span id="projR">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="projG">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="projB">-&#45;&#45;</span></div>
<div class="mui-col-md-3"><span id="projW">-&#45;&#45;</span></div>
</div>
</div>-->
</div>
<script src="cordova.js"></script>
<!-- build:vendor -->
<script src="lib/mui.js"></script>
<script src="lib/jquery.js"
integrity="sha256-laXWtGydpwqJ8JA+X9x2miwmaiKhn8tVmOVEigRNtP4="
crossorigin="anonymous"></script>
<script src="lib/chroma.js"></script>
<script src="lib/underscore.js"></script>
<script src="lib/backbone.js"></script>
<!--<script src="lib/sugar-date.js"></script>-->
<script src="lib/sugar.js"></script>
<script src="lib/skycons.js"></script>
<script src="lib/microevent.js"></script>
<!-- endbuild -->
<!-- build:js -->
<script src="js/sowebsocket.js"></script>
<script src="js/colours.js"></script>
<script src="js/modules/clock.js"></script>
<script src="js/parts/meetings.js"></script>
<script src="js/parts/lights.js"></script>
<script src="js/parts/projector.js"></script>
<script src="js/app.js"></script>
</body>
<!-- endbuild -->
</html>