This commit is contained in:
Martin Donnelly 2016-08-29 10:16:33 +01:00
commit 589ec833d4
22 changed files with 3341 additions and 0 deletions

2
.cfignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
/sql/

32
.editorconfig Normal file
View File

@ -0,0 +1,32 @@
; http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.txt]
insert_final_newline = false
trim_trailing_whitespace = false
[*.py]
indent_size = 4
[*.m]
indent_size = 4
[Makefile]
indent_style = tab
indent_size = 8
[*.{js,json}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

180
.gitignore vendored Normal file
View File

@ -0,0 +1,180 @@
# Created by .ignore support plugin (hsz.mobi)
### Archives template
# It's better to unpack these files and commit the raw source because
# git has its own built in compression methods.
*.7z
*.jar
*.rar
*.zip
*.gz
*.bzip
*.bz2
*.xz
*.lzma
*.cab
#packing-only formats
*.iso
*.tar
#package management formats
*.dmg
*.xpi
*.gem
*.egg
*.deb
*.rpm
*.msi
*.msm
*.msp
### Windows template
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### OSX template
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Node template
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
bower_components
### VisualStudioCode template
.settings
### Xcode template
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
dist

46
.jscsrc Normal file
View File

@ -0,0 +1,46 @@
{
"disallowKeywords": ["with"],
"disallowKeywordsOnNewLine": ["else"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleVarDecl": "exceptUndefined",
"disallowNewlineBeforeBlockStatements": true,
"disallowQuotedKeysInObjects": true,
"disallowSpaceAfterObjectKeys": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowSpacesInFunction": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideParentheses": true,
"disallowTrailingWhitespace": true,
"maximumLineLength": 160,
"requireCamelCaseOrUpperCaseIdentifiers": false,
"requireCapitalizedComments": true,
"requireCapitalizedConstructors": true,
"requireCurlyBraces": true,
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"case",
"return",
"try",
"catch",
"typeof"
],
"requireSpaceAfterLineComment": true,
"requireSpaceAfterBinaryOperators": true,
"requireSpaceBeforeBinaryOperators": true,
"requireSpaceBeforeBlockStatements": true,
"requireSpaceBeforeObjectValues": true,
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"requireTrailingComma": false,
"requireEarlyReturn": false,
"validateIndentation": 2,
"validateLineBreaks": "LF",
"validateQuoteMarks": "'"
}

37
.jshintrc Normal file
View File

@ -0,0 +1,37 @@
{
"predef": [
"Promise",
"$"
],
"globals": {
"$": false,
"MicroEvent": false
},
"node":true,
"browser": true,
"boss": true,
"curly": true,
"debug": false,
"devel": true,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false,
"eqnull": true,
"esnext": true,
"unused": true,
"supernew":true
}

120
app.js Normal file
View File

@ -0,0 +1,120 @@
var express = require('express');
var path = require('path');
var http = require('http');
var ejs = require('ejs');
var morgan = require('morgan');
var cookieparser = require('cookie-parser');
var session = require('express-session');
var methodoverride = require('method-override');
var bodyparser = require('body-parser');
var errorhandler = require('errorhandler');
var log4js = require('log4js');
var logger = log4js.getLogger();
var authentication = require('basic-authentication');
var cfenv = require('cfenv');
var WebSocketServer = require('websocket').server;
var EventEmitter = require('events');
var busEmitter = new EventEmitter();
var mqttClient = require('./lib/mqtt/mqttClient');
var mqtt = new mqttClient.mqttClient(busEmitter);
var app = express();
var port = (process.env.VCAP_APP_PORT || 3010);
var host = (process.env.VCAP_APP_HOST || 'localhost');
var isProduction = false;
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
if (process.env.NODE_ENV === 'production') {
isProduction = true;
}
logger.warn('isProduction:', isProduction);
var heartBeat = function() {
this.pingTimer = 0;
this.count = 0;
this.setupPing = function() {
logger.warn('Starting heartbeat...');
this.pingTimer = setTimeout(function() {this.ping();}.bind(this), 10000);
};
this.ping = function() {
//Logger.error('Ping!');
//this.sendRefresh();
var now = new Date;
var mod = 90000 - (now.getTime() % 90000);
this.count++;
if (this.count > 5) {
this.count = 1;
}
var _newDots = ['.','.','.','.','.'];
_newDots[this.count - 1] = 'O';
logger.info(_newDots.join(''));
this.pingTimer = setTimeout(function() {this.ping();}.bind(this), mod + 10);
};
this.setupPing();
};
app.set('port', port);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(morgan('combined'));
app.use(cookieparser('your secret here'));
app.use(session({
secret: '1234567890QWERTY', 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.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
next();
});
// Run npm start --production to use dist
var staticDir = isProduction ? 'dist' : 'app';
staticDir = 'app';
app.use(express.static(path.join(__dirname, staticDir)));
app.use(errorhandler({dumpExceptions: true, showStack: true}));
// Events and sockets
function originIsAllowed(origin) {
// Put logic here to detect whether the specified origin is allowed.
return true;
}
heartBeat();
app.get('*', function(req, res) {
res.status(404).render('404',{delimiter: '^'});
});
app.listen(port, function() {
logger.info('Express listening on ',host, port);
});

24
bower.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "sodashserver",
"description": "Smart Office Dashboard Server",
"main": "index.js",
"authors": [
"Martin Donnelly"
],
"license": "ISC",
"homepage": "",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"chroma-js": "^1.1.1",
"jquery": "^2.2.3",
"mui": "^0.5.3",
"sugarjs-date": "^1.5.1"
}
}

117
gulpfile.js Normal file
View File

@ -0,0 +1,117 @@
'use strict';
var gulp = require('gulp');
var autoprefixer = require('gulp-autoprefixer');
var cssnano = require('gulp-cssnano');
var jshint = require('gulp-jshint');
var uglify = require('gulp-uglify');
var jsmin = require('gulp-jsmin');
var rename = require('gulp-rename');
var concat = require('gulp-concat');
var notify = require('gulp-notify');
var cache = require('gulp-cache');
var livereload = require('gulp-livereload');
var htmlmin = require('gulp-htmlmin');
var inject = require('gulp-inject');
var del = require('del');
var htmlreplace = require('gulp-html-replace');
var googleWebFonts = require('gulp-google-webfonts');
var stripDebug = require('gulp-strip-debug');
var size = require('gulp-size');
var debug = require('gulp-debug');
var filePath = {
build_dir: './dist'
};
gulp.task('scripts', function() {
return gulp.src(['app/js/sowebsocket.js','app/js/colours.js','app/js/appv2.js'])
.pipe(jshint('.jshintrc'))
.pipe(jshint.reporter('default'))
.pipe(concat('app.js'))
// .pipe(stripDebug())
.pipe(jsmin())
.pipe(size({title: 'Scripts'}))
.pipe(gulp.dest('dist/js'));
});
gulp.task('vendor', function() {
return gulp.src(['bower_components/jquery/dist/jquery.min.js',
'bower_components/mui/packages/cdn/js/mui.min.js',
'bower_components/chroma-js/chroma.min.js',
'bower_components/sugar/release/sugar-full.min.js',
'app/lib/skycons.js',
'app/lib/microevent.js'])
.pipe(concat('vendor.js'))
.pipe(uglify({mangle: false}))
.pipe(size({title: 'Vendor'}))
.pipe(gulp.dest('dist/js'));
});
gulp.task('styles', function() {
return gulp.src(['app/css/mui.custom.css','app/css/material-icons.css','app/css/app.css'])
.pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
.pipe(cssnano())
.pipe(concat('app.css'))
.pipe(size({title: 'Styles'}))
.pipe(gulp.dest('dist/css'));
});
gulp.task('partials', function() {
// Gulp.src(['app/partials/**/*']).pipe(gulp.dest('dist/partials'));
// gulp.src(['app/libs/ejs_production.js']).pipe(gulp.dest('dist/libs'));
// gulp.src(['app/libs/microevent.js']).pipe(gulp.dest('dist/libs'));
gulp.src(['app/fav/**/*']).pipe(size({title: 'Partials'})).pipe(gulp.dest('dist/fav'));
gulp.src(['app/gfx/censis_logo_white.png']).pipe(gulp.dest('dist/gfx'));
});
gulp.task('migrate', function() {
return gulp.src(['dist/**/*'])
.pipe(debug({title: 'migrate:'}))
.pipe(size({title: 'Migrate'}))
.pipe(gulp.dest('/Users/martin/newdev/SODashApp/www'));
});
gulp.task('index', function() {
var sources = gulp.src(['js/apps.js', 'css/app.css'], {read: false});
return gulp.src(['app/index.html'])
.pipe(htmlreplace({
css: 'css/app.css',
js: 'js/app.js',
vendor: 'js/vendor.js',
fonts: 'fonts/fonts.css'
}))
.pipe(htmlmin({removeComments: true, collapseWhitespace: true, keepClosingSlash: true}))
.pipe(size({title: 'Index'}))
.pipe(gulp.dest('dist/'));
});
var options = { };
gulp.task('fonts', function() {
return gulp.src('./fonts.list')
.pipe(googleWebFonts(options))
.pipe(size({title: 'Fonts'}))
.pipe(gulp.dest('dist/fonts'))
;
});
gulp.task('clean', function() {
return del(['dist']);
});
gulp.task('default', ['clean'], function() {
gulp.start('styles', 'scripts', 'vendor', 'fonts', 'partials', 'index');
});
gulp.task('Cordova', ['clean'], function() {
gulp.start('styles', 'scripts', 'vendor', 'fonts', 'index');
});

130
lib/mqtt/IoTFconnector.js Normal file
View File

@ -0,0 +1,130 @@

var IoTFconnector = function (orgId, api_key, auth_token, $rootScope) {
//this.connected = '';
this.clientId = "a:" + orgId + ":" + Date.now();
console.log("clientId: " + this.clientId);
this.hostname = orgId + ".messaging.internetofthings.ibmcloud.com";
this.client = '';
this.initialize = function () {
client = new Messaging.Client(this.hostname, 8883, this.clientId);
client.onMessageArrived = function (msg) {
console.log("Message from :" + msg.destinationName);
};
var connectOptions = new Object();
connectOptions.keepAliveInterval = 3600;
connectOptions.useSSL = true;
connectOptions.userName = api_key;
connectOptions.password = auth_token;
connectOptions.onSuccess = function () {
IoTFconnector.prototype.clientStatus.connected = true;
// $rootScope.$broadcast("clientStatusUpdated", clientStatus);
console.log("MQTT connected to host: " + client.host + " port : " + client.port + " at " + Date.now());
}
connectOptions.onFailure = function (e) {
console.log("MQTT connection failed at " + Date.now() + "\nerror: " + e.errorCode + " : " + e.errorMessage);
}
console.log("about to connect to " + client.host);
client.connect(connectOptions);
//client = new Messaging.Client(this.hostname, 8883, this.clientId);
//// Initialize the Realtime Graph
////var rtGraph = new RealtimeGraph();
//client.onMessageArrived = function(msg) {
// //var topic = msg.destinationName;
// //var payload = JSON.parse(msg.payloadString);
// ////First message, instantiate the graph
// //if (firstMessage) {
// // $('#chart').empty();
// // firstMessage = false;
// // rtGraph.displayChart(null, payload);
// //} else {
// // rtGraph.graphData(payload);
// //}
// console.log("Message from :" + msg.destinationName);
//};
//client.onConnectionLost = function(e) {
// console.log("Connection Lost at " + Date.now() + " : " + e.errorCode + " : " + e.errorMessage);
// this.connect(connectOptions);
//}
//var connectOptions = new Object();
//connectOptions.keepAliveInterval = 3600;
//connectOptions.useSSL = true;
//connectOptions.userName = api_key;
//connectOptions.password = auth_token;
//connectOptions.onSuccess = function() {
// IoTFconnector.prototype.connected = true;
// console.log("MQTT connected to host: " + client.host + " port : " + client.port + " at " + Date.now());
//}
//connectOptions.onFailure = function(e) {
// console.log("MQTT connection failed at " + Date.now() + "\nerror: " + e.errorCode + " : " + e.errorMessage);
//}
//console.log("about to connect to " + client.host);
//client.connect(connectOptions);
}
this.initialize();
//var imageHTML = '<div class="iotdashboardtext">The selected device is not currently sending events to the Internet of Things Foundation</div><br><div class="iotdashboardtext">Select to view historical data or select a different device.</div> <img class="iotimagesMiddle" align="middle" alt="Chart" src="../../images/IOT_Icons_Thing02.svg">';
};
IoTFconnector.prototype.clientStatus = { connected: false, subscribeTopic: '' };;
IoTFconnector.prototype.disconnect = function disconnect() {
client.disconnect();
console.log("Connection closed");
}
IoTFconnector.prototype.connect = function connect() {
// this.initialize();
}
IoTFconnector.prototype.subscribe = function subscribe(deviceId) {
var subscribeOptions = {
qos: 0,
onSuccess: function () {
console.log("subscribed to " + IoTFconnector.prototype.clientStatus.subscribeTopic);
},
onFailure: function () {
console.log("Failed to subscribe to " + IoTFconnector.prototype.clientStatus.subscribeTopic);
console.log("As messages are not available, visualization is not possible");
}
};
if (IoTFconnector.prototype.clientStatus.subscribeTopic != "") {
console.log("Unsubscribing to " + IoTFconnector.prototype.clientStatus.subscribeTopic);
client.unsubscribe(IoTFconnector.prototype.clientStatus.subscribeTopic);
};
IoTFconnector.prototype.clientStatus.subscribeTopic = "iot-2/type/iotsample-ti-cc3200/id/" + deviceId + "/evt/+/fmt/json";
client.subscribe(IoTFconnector.prototype.clientStatus.subscribeTopic, subscribeOptions);
}

130
lib/mqtt/mqttClient.js Normal file
View File

@ -0,0 +1,130 @@
var mqtt = require('mqtt');
var logger = require('log4js').getLogger();
var EventEmitter = require('events');
var busEmitter = new EventEmitter();
var db = require('../server/db-connector').dbConnection;
var dbSave = require('../server/db-save')(db);
function dataBuilder(obj) {
'use strict';
var now = new Date();
var newObj = {device_type: 'mDot', evt_type: 'update', timestamp: {}, evt: {}};
newObj.device_id = obj.topic.split('/')[4];
newObj.evt = obj;
newObj.timestamp['$date'] = now.getTime();
return newObj;
}
var doInsertEntry = (obj) => {
// Logger.info('sendSocket: ' + JSON.stringify(obj));
// insertEntry(obj);
dbSave.addNewEvent(obj)
.then(function(d) {
'use strict';
//logger.info('Finished - Raw',d);
})
.catch(function(e) {
'use strict';
logger.error(e);
});
};
var mqttClient = function() {
var count=0;
var subscribeTopic;
var orgId = 'qz0da4';
var userName = 'a-qz0da4-dfwwdkmkzr';
var address = '.messaging.internetofthings.ibmcloud.com';
var appKey = '9txJEf3Cjy7hkSOvkv';
//Var subscribeTopic = prefix + deviceId + '/evt/+/fmt/json';
this.connected = false;
var options = {
keepalive: 10,
clientId: 'a:' + orgId + ':' + Date.now(),
username: userName,
password: new Buffer(appKey),
reconnectPeriod:1000,
connectTimeout:30 * 1000
};
this.client = mqtt.connect('mqtt://' + orgId + address, options);
this.client.on('connect', function() {
connected = true;
logger.info('Connected to ', address);
}.bind(this));
this.client.on('connected', function() {
logger.debug('mqttConnect - doConnection - Connected');
});
this.client.on('close', function() {
logger.warn('mqttConnect - Connection closed');
});
this.client.on('offline', function() {
logger.warn('mqttConnect - OFFLINE!');
});
this.client.on('error', function(e) {
logger.error('mqttConnect - error');
logger.error(e);
});
this.client.on('reconnect', function() {
logger.warn('mqttConnect - Attempting to reconnect...');
});
subscribeTopic = 'iot-2/type/+/id/+/evt/+/fmt/json';
logger.info('Subscribing:', subscribeTopic);
this.client.subscribe(subscribeTopic);
this.client.on('message', function(topic, message) {
var json = JSON.parse(message.toString());
var topicArray = topic.split('/');
json.topic = topic;
json.type = topicArray[2];
json.device = topicArray[4];
json.event = topicArray[6];
busEmitter.emit('saveData', json);
count++;
}.bind(this));
busEmitter.on('saveData', doInsertEntry);
this.getCount = function() {
'use strict';
return count;
}
};
module.exports.mqttClient = mqttClient;

353
lib/mqtt/mqttConnect.js Normal file
View File

@ -0,0 +1,353 @@
/**
* Created by Martin on 08/02/2016.
*/
'use strict';
var mqtt = require('mqtt');
var Messaging = require('mqtt_over_websockets');
var log4js = require('log4js');
var logger = log4js.getLogger();
var mqttConfig = {
orgId: 'qz0da4',
userName: 'martind2000',
appKey: 'MPReoa43',
prefix: 'iot-2/type/Ti-CC3200/id/'
};
function randomString(length) {
return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1);
}
/*
Projector: 'ProjectorISP15',
lighting: 'LightingISP15',
heating: 'HeatingISP15',
*/
var live = true;
logger.warn('!!! Live? ', live);
module.exports = {
restartTimer : 0,
pingTimer: 0,
statuses: {},
sockets: null,
wsClient: null,
watches: {},
client: null,
projector: live ? 'Projector' : 'ProjectorISP15',
lighting: live ? 'Lighting' : 'LightingISP15',
heating: live ? 'Heating' : 'HeatingISP15',
myID: 0,
connected: false,
options: {
// Keepalive: 3600,
keepalive: 60,
clientId: 'a:' + mqttConfig.orgId + ':' + Date.now(),
username: mqttConfig.userName,
password: new Buffer(mqttConfig.appKey)
},
lightList: {o: 'frontLight', f: 'frontLight',n: 'backLight', g: 'backLight'},
updateStatus: function(id, packet) {
this.statuses[id] = packet;
logger.info('Statuses:', this.statuses);
},
setEmitter: function(newEmitter) {
this.emitter = newEmitter;
},
doConnection: function(cb) {
console.log('Do connection');
var self = this;
if (this.client === null) {
this.client = mqtt.connect('mqtt://' + 'silvrtree.co.uk', this.options);
this.client.on('connect', function() {
logger.info('Connected to SilvrBroker');
self.connected = this.connected;
});
this.client.on('connected', function() {
logger.debug('mqttConnect - doConnection - Connected');
});
this.client.on('disconnect', function(e) {
logger.error('mqttConnect - doConnection - disconnect');
logger.error(e);
});
this.client.on('error', function(e) {
logger.error('mqttConnect - doConnection - disconnect');
logger.error(e);
});
}
return this;
},
isConnected: function() {
return this.connected;
},
sendCommand: function(deviceID, command) {
var payload = {
id: 'd',
text: command
};
},
projectorOn: function(callback) {
var packet;
console.log('projectorOn:');
var _callback = callback || {};
if (!this.client) {
return -1;
}
packet = {id: this.projector, status: true};
var destinationName = mqttConfig.prefix + this.projector + '/cmd/' + 'ON' + '/fmt/json';
this.client.publish(destinationName, 'ON', _callback);
this.emitter.emit('sendSocket',packet);
logger.debug('Storing status...');
this.updateStatus('projector', packet);
},
projectorOff: function(callback) {
var packet;
var _callback = callback || {};
if (!this.client) {
return -1;
}
packet = {id: this.projector, status: false};
var destinationName = mqttConfig.prefix + this.projector + '/cmd/' + 'OFF' + '/fmt/json';
this.client.publish(destinationName, 'OFF', _callback);
this.emitter.emit('sendSocket',packet);
console.log('Storing status...');
this.updateStatus('projector', packet);
},
projectorCmd: function(id, callback) {
var packet;
var _callback = callback || {};
if (!this.client) {
return -1;
}
var destinationName = mqttConfig.prefix + this.projector + '/cmd/' + id + '/fmt/json';
this.client.publish(destinationName, 'cmd', _callback);
},
heatingOn: function(callback) {
console.log('Turn heating on...');
var _callback = callback || null;
if (!this.client) {
return -1;
}
var destinationName = mqttConfig.prefix + this.heating + '/cmd/' + 'on' + '/fmt/json';
this.client.publish(destinationName, 'ON', _callback);
this.emitter.emit('sendSocket',{id: this.heating, status: true});
},
heatingOff: function(callback) {
var _callback = callback || {};
if (!this.client) {
return -1;
}
var destinationName = mqttConfig.prefix + this.heating + '/cmd/' + 'off' + '/fmt/json';
this.client.publish(destinationName, 'OFF', _callback);
this.emitter.emit('sendSocket',{id: this.heating, status: false});
},
lightingOn: function(id, callback) {
var packet;
console.log('lightingOn:' + id);
var _callback = callback || null;
if (!this.client) {
return -1;
}
var destinationName = mqttConfig.prefix + this.lighting + '/cmd/' + id + '/fmt/json';
this.client.publish(destinationName, 'ON', _callback);
packet = {id: this.lighting, device: id, status: true};
this.emitter.emit('sendSocket',packet);
logger.debug('Storing status...');
this.updateStatus(this.lightList[id], packet);
},
lightingOff: function(id, callback) {
var packet;
var _callback = callback || null;
if (!this.client) {
return -1;
}
var destinationName = mqttConfig.prefix + this.lighting + '/cmd/' + id + '/fmt/json';
this.client.publish(destinationName, 'OFF', _callback);
packet = {id: this.lighting, device: id, status: false};
this.emitter.emit('sendSocket', packet);
this.updateStatus(this.lightList[id], packet);
},
lightingCommand: function(id, callback) {
var packet;
var _callback = callback || null;
if (!this.client) {
return -1;
}
var destinationName = mqttConfig.prefix + this.lighting + '/cmd/' + id + '/fmt/json';
this.client.publish(destinationName, 'cmd', _callback);
},
setupEvents: function() {
this.emitter.on('lightingOn', this.lightingOn);
this.emitter.on('lightingOff', this.lightingOff);
this.emitter.on('heatingOn', this.heatingOn);
this.emitter.on('heatingOff', this.heatingOff);
this.emitter.on('projectorOn', this.projectorOn);
this.emitter.on('projectorOff', this.projectorOff);
},
setupPing: function() {
logger.warn('Starting ping timer...');
this.pingTimer = setTimeout(function() {this.ping();}.bind(this), 10000);
},
ping: function() {
//Logger.error('Ping!');
this.sendRefresh();
var now = new Date;
var mod = 10000 - (now.getTime() % 10000);
setTimeout(function() {this.ping();}.bind(this),mod + 10);
},
sendRefresh: function() {
// logger.debug('+ Send refresh', this.statuses);
for (var item in this.statuses) {
if (this.statuses.hasOwnProperty(item)) {
console.log(this.statuses[item]);
this.emitter.emit('sendSocket', this.statuses[item]);
}
}
// logger.debug('+ Send refresh');
},
preRestartConnection: function() {
logger.debug('Restart connection...');
//this.emitter.emit('restartMQTTSocket', this);
//logger.debug(this);
this.restartMQTTSocket();
},
startMQTTSocket:function() {
logger.warn('Restarting socket?');
this.connectWS();
},
restartMQTTSocket: function() {
this.wsClient.disconnect();
// setTimeout(this.startMQTTSocket.bind(this), 15000);
},
connectWS: function(connectCB) {
logger.debug('Going to connect WS');
var self = this;
var hostname = 'silvrtree.co.uk';
var clientId = 'a:' + 'qz0da4' + ':' + Date.now();
var api_key = 'martind2000';
var auth_token = 'MPReoa43';
this.wsClient =new Messaging.Client(hostname, 8883, clientId);
//var wsClient = new Messaging.Client(hostname, 8883, clientId);
var wsClient = this.wsClient;
var clientStatus = {connected: false, subscribed: false, deviceConnected: false};
var sensorData = {};
this.restartTimer = setTimeout(this.preRestartConnection.bind(this), 60000);
wsClient.onMessageArrived = function(msg) {
//logger.info("Message from :" + msg.destinationName);
clearTimeout(self.restartTimer);
self.restartTimer = setTimeout(self.preRestartConnection.bind(self), 60000);
clientStatus.deviceConnected = true;
sensorData = JSON.parse(msg.payloadString);
// Logger.debug(sensorData);
var temp = msg.destinationName.split('/');
if (self.watches.hasOwnProperty(temp[4])) {
// Logger.info('Emit: ' + JSON.stringify({id:self.watches[temp[4]],sensorData:sensorData}));
self.emitter.emit('sendSocket',{id: self.watches[temp[4]],sensorData: sensorData});
}
};
wsClient.onConnectionLost = function(e) {
logger.error('+ wsClient.onConnectionLost');
logger.error(e);
logger.warn('Going to force a restart of the Socket.. Hold on.');
setTimeout(self.startMQTTSocket.bind(self), 15000);
//self.emitter.emit('sendSocket',{id: 'deviceLost',data: e});
//var wsReconnectTimer = setTimeout(function() {logger.debug('TRYING TO RECONNECT TO MQTT');self.emitter.emit('connectWS');}.bind(this),30000);
logger.error('- wsClient.onConnectionLost');
};
var connectOptions = {};
connectOptions.keepAliveInterval = 3600;
connectOptions.useSSL = true;
connectOptions.userName = api_key;
connectOptions.password = auth_token;
connectOptions.onSuccess = function() {
clientStatus.connected = true;
logger.info('MQTT connected to host: ' + wsClient.host + ' port : ' + wsClient.port + ' at ' + Date.now());
self.emitter.emit('clientStatusUpdated', clientStatus);
self.emitter.emit('clientConnected', self.sockets);
};
connectOptions.onFailure = function(e) {
self.emitter.emit('sendSocket',{id: 'deviceNotConnecting'});
logger.error('wsClient onFailure', e);
logger.error('MQTT connection failed at ' + Date.now() + '\nerror: ' + e.errorCode + ' : ' + e.errorMessage);
//var wsReconnectTimer = setTimeout(function() {logger.debug('TRYING TO RECONNECT TO MQTT');self.emitter.emit('connectWS');}.bind(this),30000);
};
logger.debug('about to connect to ' + wsClient.host);
wsClient.connect(connectOptions);
this.sockets = {
getClientStatus: function() {
return clientStatus;
},
subscribe: function(deviceId, emitterId) {
logger.debug('trying to subscribe');
var subscribeTopic = 'iot-2/type/Ti-CC3200/id/' + deviceId + '/evt/+/fmt/json';
logger.debug(subscribeTopic);
var subscribeOptions = {
qos: 0,
onSuccess: function() {
logger.info('subscribed to ' + subscribeTopic);
clientStatus.subscribed = true;
// $rootScope.$broadcast("clientStatusUpdated", clientStatus);
self.emitter.emit('clientStatusUpdated', clientStatus);
self.watches[deviceId] = emitterId;
},
onFailure: function() {
logger.error('Failed to subscribe to ' + subscribeTopic);
logger.error('As messages are not available, visualization is not possible');
}
};
/*If (IoTFconnector.prototype.clientStatus.subscribeTopic != "") {
console.log("Unsubscribing to " + subscribeTopic);
wsClient.unsubscribe(IoTFconnector.prototype.clientStatus.subscribeTopic);
};*/
wsClient.subscribe(subscribeTopic, subscribeOptions);
}
};
return this.sockets;
}
};

7
lib/mqtt/mqttSocket.js Normal file
View File

@ -0,0 +1,7 @@
/**
*
* User: Martin Donnelly
* Date: 2016-07-28
* Time: 14:48
*
*/

1791
lib/mqtt/mqttws31.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
'uses strict';
/**
*
* User: Martin Donnelly
* Date: 2016-03-11
* Time: 10:22
*
*/
var pgp = require('pg-promise')();
var cn = {
host: 'localhost',
port: 5432,
database: 'mqttstore',
user: 'postgres',
password: ''
};
// ElephantSql settings
/*
var cn = {
host: 'qdjjtnkv.db.elephantsql.com',
port: 5432,
database: 'gxwxnqoh',
user: 'gxwxnqoh',
password: 'LN5Uk39GNcBCxCHv5dfL8aKuowFT6WKG'
};
*/
exports.dbConnection = pgp(cn);

130
lib/server/db-save.js Normal file
View File

@ -0,0 +1,130 @@
'use strict';
var atob = require('atob');
module.exports = function(db) {
var module = {};
module.deviceIds = ['CENSIS-LoRa-1','CENSIS-LoRa-2','CENSIS-LoRa-3','CENSIS-LoRa-4','HIE-mobile-1','HIE-demo','HIE-mobile-2','HIE-smart-campus-1','HIE-smart-campus-2','HIE-smart-campus-3','HIE-smart-campus-4','HIE-smart-campus-5','HIE-smart-campus-6','HIE-smart-campus-7','HIE-mDot-1'];
module.sqlInsertRawEvent = function(data) {
let _data = data;
return new Promise(function(resolve, reject) {
db.func('insert_raw',
[_data.timestamp, _data.data.type, _data.data.device, _data.data.event, _data.data])
.then(()=> {
return resolve('ok');
})
.catch((err)=> {
console.error(err);
return reject(err);
});
});
};
module.sqlInsertDecoded = function(data) {
let _data = data;
return new Promise(function(resolve, reject) {
db.func('insert_decoded',
[_data.deviceid, _data.timestamp, _data.lux, _data.co2, _data.temp, _data.humidity, _data.sound])
.then(()=> {
return resolve('ok');
})
.catch((err)=> {
console.error(err);
return reject(err);
});
});
};
module.addNewEvent = function(data) {
var self = this;
return new Promise((resolve, reject) => {
let _data = {};
_data.timestamp = new Date();
_data.data = data;
self.sqlInsertRawEvent(_data)
.then((d)=> {
//console.log('Postgres returns', d);
return resolve({reply: 'raw event inserted'});
})
.catch((err)=> {
console.error(err);
return reject(err);
});
});
};
module.addProcessedEvent = function(data) {
var self = this;
return new Promise((resolve, reject) => {
let _data = self.rawBreaker(data);
self.sqlInsertDecoded(_data)
.then((d)=> {
// console.log('Postgres returns', d);
return resolve({reply: 'Processed event inserted'});
})
.catch((err)=> {
console.error(err);
return reject(err);
});
});
};
module.decoder = function(data) {
var _obj = {};
var _data = atob(data).split('');
var bytes = _data.map(i => i.charCodeAt());
_obj.light = parseInt('0x' + ('0' + bytes[0]).substr(-2) + ('0' + bytes[1]).substr(-2));
_obj.co2 = parseInt(_data[2] + _data[3] + _data[4] + _data[5] + _data[6], 10);
_obj.temp = (parseInt(_data[7] + _data[8] + _data[9] + _data[10] + _data[11], 10) - 1000) / 10;
_obj.humid = (parseInt(_data[12] + _data[13] + _data[14] + _data[15] + _data[16], 10) / 10);
_obj.noise = parseInt('0x' + ('0' + bytes[17]).substr(-2) + ('0' + bytes[18]).substr(-2));
_obj.binData = bytes;
console.log(_obj);
return _obj;
};
module.rawBreaker = function(data) {
var self = this;
var workObj = {};
var device_name = data.topic.split('/')[4];
console.log('Device_name', device_name);
workObj.deviceid = self.deviceIds.indexOf(device_name);
if (data.hasOwnProperty('data')) {
var _data = self.decoder(data.data);
workObj.lux = _data.light;
workObj.co2 = _data.co2;
workObj.temp = _data.temp;
workObj.humidity = _data.humid;
workObj.sound = _data.noise;
workObj.timestamp = new Date();
return workObj;
} else {
console.error('Data does not have base64 data');
return null;
}
};
return module;
};

8
manifest.yml Normal file
View File

@ -0,0 +1,8 @@
applications:
- path: .
memory: 256M
instances: 1
domain: mybluemix.net
name: mdotmqtt
host: mdotmqtt
disk_quota: 1024M

81
package.json Normal file
View File

@ -0,0 +1,81 @@
{
"name": "mdot_mqtt",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node app.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"atob": "^2.0.3",
"basic-authentication": "^1.6.2",
"body-parser": "^1.15.2",
"cfenv": "1.0.x",
"cookie-parser": "^1.4.3",
"ejs": "^2.5.1",
"errorhandler": "^1.4.3",
"events": "^1.1.1",
"express": "^4.14.0",
"express-session": "^1.14.1",
"http": "0.0.0",
"log4js": "^0.6.36",
"method-override": "^2.3.6",
"morgan": "^1.7.0",
"mqtt": "^1.10.0",
"mqtt_over_websockets": "0.0.1",
"path": "^0.12.7",
"pg-promise": "^5.2.7",
"request": "^2.72.0",
"routes": "^2.1.0",
"websocket": "^1.0.22"
},
"devDependencies": {
"after": "^0.8.1",
"apn": "^1.7.8",
"apns": "^0.1.0",
"basic-authentication": "^1.6.2",
"chai": "^3.5.0",
"cheerio": "^0.20.0",
"clone": "^1.0.2",
"del": "^2.2.0",
"elapsed": "0.0.7",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.0",
"gulp-cache": "^0.4.5",
"gulp-concat": "^2.6.0",
"gulp-cssmin": "^0.1.7",
"gulp-cssnano": "^2.1.2",
"gulp-debug": "^2.1.2",
"gulp-google-webfonts": "0.0.13",
"gulp-html-replace": "^1.5.5",
"gulp-htmlmin": "^2.0.0",
"gulp-inject": "^4.0.0",
"gulp-jshint": "^2.0.1",
"gulp-jsmin": "^0.1.5",
"gulp-livereload": "^3.8.1",
"gulp-notify": "^2.2.0",
"gulp-rename": "^1.2.2",
"gulp-size": "^2.1.0",
"gulp-strip-debug": "^1.1.0",
"gulp-uglify": "^2.0.0",
"jshint": "^2.9.2",
"jsonfile": "^2.3.1",
"mocha": "^3.0.2",
"mqtt-ws": "^0.2.0",
"nano": "^6.2.0",
"node-cron": "^1.1.1",
"require-dir": "^0.3.0",
"should": "^10.0.0",
"string": "^3.3.1",
"sugar": "^2.0.1",
"sugar-date": "^2.0.0",
"superagent": "^2.1.0",
"supertest": "^2.0.0"
},
"author": "Martin Donnelly <martind2000@gmail.com>",
"license": "ISC",
"engines": {
"node": "5.7.0"
}
}

26
pg_hba.conf Normal file
View File

@ -0,0 +1,26 @@
# Allow any user on the local system to connect to any database with
# any database user name using Unix-domain sockets (the default for local
# connections).
#
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
# The same using local loopback TCP/IP connections.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all 127.0.0.1/32 trust
# The same as the previous line, but using a separate netmask column
#
# TYPE DATABASE USER IP-ADDRESS IP-MASK METHOD
host all all 127.0.0.1 255.255.255.255 trust
# The same over IPv6.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all ::1/128 trust
# The same using a host name (would typically cover both IPv4 and IPv6).
#
# TYPE DATABASE USER ADDRESS METHOD
host all all localhost trust

20
process.json Normal file
View File

@ -0,0 +1,20 @@
{
"apps": [
{
"name": "SODashServer",
"script": "app.js",
"cwd": "/home/azureuser/live",
"watch": true,
"instances": 1,
"exec_mode": "cluster",
"combine_logs": true,
"max_memory_restart": "150M",
"restart_delay": 5000,
"ignore_watch": [
"[\\/\\\\]\\./",
"node_modules",
"server/static"
]
}
]
}

38
tools/dbconfig.js Normal file
View File

@ -0,0 +1,38 @@
/**
*
* User: Martin Donnelly
* Date: 2016-04-04
* Time: 14:46
*
*/
var exec = require('child_process').exec;
function prepare_db() {
exec('psql -Upostgres -h localhost -f ./mdot.sql', function(err) {
if (err !== null) {
console.log('exec error: ' + err);
return -1;
} else {
console.log('Done?');
}
});
}
function createDB() {
'use strict';
exec('createdb -Upostgres -h localhost mdot', function(err) {
if (err !== null) {
console.log('exec error: ' + err);
return -1;
} else {
prepare_db();
}
});
}
prepare_db();

20
tools/maker.js Normal file
View File

@ -0,0 +1,20 @@
/**
*
* User: Martin Donnelly
* Date: 2016-06-28
* Time: 14:50
*
*/
var nano = require('nano')('http://localhost:5984');
var db_name = 'mqtt';
// Clean up the database we created previously
nano.db.destroy(db_name, function() {
// Create a new database
nano.db.create(db_name, function() {
// Specify the database we are going to use
var newDB = nano.use(db_name);
});
});

15
views/404.ejs Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>404</title>
<link href="css/mui.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div class="mui-container">
<div class="mui-panel">
<div class="mui--text-dark-secondary mui--text-display4">404</div>
<div class="mui--text-dark-secondary mui--text-display2">Not Found</div>
</div>
</div>
</body>
</html>