Lots of remote changes.

dht is now processing commands!
hurray!
This commit is contained in:
d-popov
2020-04-09 12:16:02 +03:00
parent 794eda52af
commit b628ade4dd
18 changed files with 1810 additions and 39 deletions

10
config/config.json Normal file
View File

@@ -0,0 +1,10 @@
{
"development": {
"username": "root",
"password": "Zelen0ku4e",
"database": "iot",
"host": "127.0.0.1",
"dialect": "mysql",
"operatorsAliases": false
}
}

69
database.js Normal file
View File

@@ -0,0 +1,69 @@
const Sequelize = require("sequelize")
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let DeviceMessageSchema = new Schema({
_id: {type: Number, required: true},
device_id: {type: String, required: true, max: 100}
});
let DevicesSchema = new Schema({
id: {type: Number, required: true},
url: {type: String, required: true, max: 100}
});
var sqlz = new Sequelize('iot', 'iot', '!iot_popovi',{dialect: 'mysql'})
var Device = sqlz.define('device', {
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
name: Sequelize.STRING,
baseurl: Sequelize.TEXT,
apikey: Sequelize.TEXT,
//config: Sequelize.JSON,
lastseen: Sequelize.DATE
});
var DeviceMessage = sqlz.define('devicemessage', {
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
device_id: { type: Sequelize.INTEGER, allowNull: false},
//id,"device_id","field_name","field_value","timestamp"
field_name: { type: Sequelize.STRING(120), allowNull: false},
field_value: { type: Sequelize.TEXT, allowNull: false},
timestamp: { type: Sequelize.DATE, allowNull: false},
});
var DeviceCommand = sqlz.define("command", {
device: { type: Sequelize.STRING},
command: {type: Sequelize.TEXT},
info: {type: Sequelize.STRING},
ac_power: Sequelize.BOOLEAN,
ac_mode: Sequelize.ENUM('Auto', 'Heat', 'Cool', "Fan"),
ac_fan: Sequelize.ENUM('Auto', 'Low', 'Med', "Hi"),
ac_temp: Sequelize.FLOAT,
ac_turbo: Sequelize.BOOLEAN,
ac_swing: Sequelize.BOOLEAN,
ac_display: Sequelize.BOOLEAN,
ac_econo: Sequelize.BOOLEAN,
ac_health: Sequelize.BOOLEAN,
});
sqlz.sync(
//{ force: true }
)
.then(() => {
console.log(`Database & tables created!`)
})
module.exports = {
init: function() {
//console.log(DeviceCommand.rawAttributes.states.values);
sqlz.sync();
},
sqlz,
Device,
DeviceCommand,
DeviceMessage
//etc
}

1
db/index.js Normal file
View File

@@ -0,0 +1 @@
exports.users = require('./users');

28
db/users.js Normal file
View File

@@ -0,0 +1,28 @@
var records = [
{ id: 1, username: 'jack', password: 'secret', displayName: 'Jack', emails: [ { value: 'jack@example.com' } ] }
, { id: 2, username: 'jill', password: 'birthday', displayName: 'Jill', emails: [ { value: 'jill@example.com' } ] }
, { id: 3, username: 'popov', password: 'Zelenakrav@', displayName: 'Doby', emails: [ { value: 'db@example.com' } ] }
];
exports.findById = function(id, cb) {
process.nextTick(function() {
var idx = id - 1;
if (records[idx]) {
cb(null, records[idx]);
} else {
cb(new Error('User ' + id + ' does not exist'));
}
});
}
exports.findByUsername = function(username, cb) {
process.nextTick(function() {
for (var i = 0, len = records.length; i < len; i++) {
var record = records[i];
if (record.username === username) {
return cb(null, record);
}
}
return cb(null, null);
});
}

276
dht.js
View File

@@ -1,6 +1,7 @@
// ./src/index.js
// importing the dependencies
const util = require('util');
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
@@ -9,7 +10,81 @@ const morgan = require('morgan');
const cron = require('node-cron');
const request = require('request');
const got = require('got');
const Sequelize = require("sequelize")
//auth ++
var session = require('express-session')
var passport = require('passport')
var Strategy = require('passport-local').Strategy;
var db = require('./db');
var ensureLoggedIn = require("connect-ensure-login").ensureLoggedIn("/n/login");
// passport.use(new Strategy(
// // {
// // usernameField: 'email',
// // passwordField: 'passwd',
// // session: false
// // },
// function(username, password, done) {
// console.log("executing auth strategy: local");
// if(username == "popov" & password == "test12345")
// {
// done(null, new {username:"popov"});
// }else
// {
// return done(null, false);
// }
// }
// ));
// passport.serializeUser(function(user, cb) {
// console.log("serializeUser()");
// cb(null, user.id);
// });
// passport.deserializeUser(function(id, cb) {
// console.log("de-serializeUser()");
// cb(null, new {username:"popov"});
// });
//! Configure the local strategy for use by Passport.
//
// The local strategy require a `verify` function which receives the credentials
// (`username` and `password`) submitted by the user. The function must verify
// that the password is correct and then invoke `cb` with a user object, which
// will be set at `req.user` in route handlers after authentication.
passport.use(new Strategy(
function(username, password, cb) {
console.log('requesting authentication for user '+ username);
db.users.findByUsername(username, function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
if (user.password != password) { return cb(null, false); }
return cb(null, user);
});
}));
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
// to serialize users into and deserialize users out of the session. The
// typical implementation of this is as simple as supplying the user ID when
// serializing, and querying the user record by ID from the database when
// deserializing.
passport.serializeUser(function(user, cb) {
cb(null, user.id);
});
passport.deserializeUser(function(id, cb) {
db.users.findById(id, function (err, user) {
if (err) { return cb(err); }
cb(null, user);
});
});
// auth --
//!https endpoint
var fs = require("fs");
@@ -29,56 +104,164 @@ var con = mysql.createConnection({
password : '!iot_popovi',
database : 'iot'
});
var sqlz = new Sequelize('iot', 'iot', '!iot_popovi',{dialect: 'mysql'})
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let DeviceMessageSchema = new Schema({
_id: {type: Number, required: true},
device_id: {type: String, required: true, max: 100}
});
let DevicesSchema = new Schema({
id: {type: Number, required: true},
url: {type: String, required: true, max: 100}
});
var Device = sqlz.define('device', {
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
name: Sequelize.STRING,
baseurl: Sequelize.TEXT,
apikey: Sequelize.TEXT,
//config: Sequelize.JSON,
lastseen: Sequelize.DATE
});
sqlz.sync();
// defining the Express app
const app = express();
var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);
// defining an array to work as the database (temporary solution)
const ads = [
{title: 'Hello, world (again)!'}
];
// adding Helmet to enhance your API's security
app.use(helmet());
// using bodyParser to parse JSON bodies into JS objects
//app.use(bodyParser.text({ type: 'text/html' }))
//app.use(bodyParser.json({ type: 'application/*+json' }));
//app.use(bodyParser.text());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'))
// enabling CORS for all requests
app.use(cors());
//Authentication ++
app.use(session({
secret: 'че първият ще генерира грешка, ако изгледът не дефинира съдържание за този раздел',
resave: true,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
//Authentication --
//app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(require('express-ejs-layouts'));//https://www.npmjs.com/package/express-ejs-layouts
//app.set("layout extractScripts", true)
//app.set('view engine', 'vash');//https://www.npmjs.com/package/vash#layout-helpers
// adding morgan to log HTTP requests
app.use(morgan('combined'));
//app.use(morgan('combined'));
// defining endpoints
//Authentication ++
app.get('/login', function(req, res) {
res.render('login', { user: req.user });
});
app.post('/login',
passport.authenticate('local', {
successRedirect: '/n/accontrol',
failureRedirect: '/n/login' }),
// authenticated user.
function(req, res) {
console.log("logged in. session:" + req.session);
res.redirect(req.session);
}
);
app.get('/logout', function(req, res){
req.logout();
res.redirect('/n/login');
});
app.get('/accontrol', ensureLoggedIn, function(req, res){
console.log("viewing /accontrol as authenticated user:"+ req.user);
res.render('accontrol', { model: { user: req.user, data:{} }});
});
app.post('/accontrol', ensureLoggedIn, function(req, res){
var model = { model: {user: req.user, data: req.body} };
console.log("power:"+ req.body.power);
console.log("heat:"+ req.body.heat);
console.log("temp:"+ req.body.temp);
console.log("econo:"+ req.body.econo);
var where = {ac_power: req.body.power?true:false};
if(where.ac_power){
where.ac_mode = req.body.heat?"Heat":"Cool";
if(req.body.temp){ where.ac_temp = req.body.temp; }else{where.ac_temp = 23}
//where.ac_econo = req.body.econo?true:false;
//// if(req.body.turbo){ where.ac_turbo = req.body.turbo?true:false; }
//// if(req.body.swing){ where.ac_swing = req.body.swing?true:false; }
//// if(req.body.display){ where.ac_display = req.body.display?true:false; }
//// if(req.body.health){ where.ac_health = req.body.health?true:false; }
}
data.DeviceCommand.findAll({ where: where }).then(function(com){
console.log("FOUND "+ com.length + " RESULTS");
if(com.length > 0)
{
model.command = com[0];
model.info = model.command.info;
console.log("executing command "+ model.info+ "");
request.post(
'http://192.168.1.126/ir',
{ form: { cmd: "RAW:" + model.command.command } },
function (error, response, body) {
if (!error && response.statusCode == 200) {
//console.log("GOT " + body);
var m = model;
res.render('accontrol', {model:{data:req.body, user:req.user, command:com[0], info:com[0].info}});
}else{
model.info = "Error executing command " + model.command.info + ". Server resturned:" + req.statusCode
}
}
);
} else
{
model.info = "Command not executed. Found " + com.length + "commands"
res.render('accontrol', model);
}
});
});
app.get('/chart', ensureLoggedIn,
function(req, res){
res.render('chart', { user: req.user });
});
//Authentication --
app.post('/dht/ping', (req, res) => { (async (res) => {
try {
console.log("HEADERS:" + req.headers); res.sendStatus(200);
} catch (error) {
console.log("PING Error:" + error); res.sendStatus(500);
} })(res);
});
app.use('/ir', bodyParser.text(), function(rq, rs) {
console.log("REQ:"+rq.headers);
console.log("BODY:"+rq.body);
rs.sendStatus(200);
});
// app.post('/ir', (req, res) => {
// (async (res) => {
// try {
// //console.log(req.params);
// console.log("REQ:"+req.body);
// console.log(`post/${util.inspect(req.body,false,null)}`);
// res.sendStatus(200);
// } catch (error) {
// console.log("IR Error:" + error); //..response.body);
// }
// })(res);
// });
// defining an endpoint to return all ads
app.get('/dht', (req, res) => { (async (res) => {
try {
const response = await got('http://192.168.1.126/json')
@@ -126,16 +309,37 @@ app.put('/dht/:device_id/:field_name/:field_value', (req, res) => {
});
app.get('/ac', (req, res) => { (async (res) => {
try {
const response = await got('http://192.168.1.126/json')
res.send(response.body);
} catch (error) {
console.log("DHT Error:" + error); //..response.body);
} })(res);
});
//Startup
var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);
httpsServer.listen(8443, () => {
console.log('HTTPS server listening on port 8443');
});
httpServer.listen(81, () => {
console.log('HTTP server listening on port 81');
});
var device = Device.build({
var data = require('./database.js');
//require('./database.js')();
//data.init();
var device = data.Device.build({
name: 'A23',
url: "http://192.168.1.126/"
});
// device.save().then().catch(
// err => {console.log(err);}
// )
@@ -157,7 +361,7 @@ cron.schedule(' */30 * * * *', () => {//cron.schedule('*/5 * * * * *', () =>
StoreSensorReadingsAsync();
}).start();
StoreSensorReadings();
//StoreSensorReadings();
async function StoreSensorReadingsAsync()
{

37
models/index.js Normal file
View File

@@ -0,0 +1,37 @@
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

768
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,8 +18,13 @@
"homepage": "",
"dependencies": {
"body-parser": "^1.19.0",
"connect-ensure-login": "^0.1.1",
"cors": "2.8.5",
"ejs": "^3.0.2",
"express": "^4.8.7",
"express-ejs-layouts": "^2.5.0",
"express-namespace": "^0.1.1",
"express-session": "^1.17.0",
"got": "^10.7.0",
"helmet": "3.21.3",
"jsonwebtoken": "^7.3.0",
@@ -31,9 +36,15 @@
"mysql2": "^2.1.0",
"node-cron": "^2.0.3",
"node-uuid": "^1.4.8",
"passport": "^0.4.1",
"passport-auth0": "^1.3.2",
"passport-local": "^1.0.0",
"plaintextparser": "^1.0.3",
"request": "^2.88.2",
"sequelize": "^5.21.5",
"sequelize-cli": "^5.5.1",
"swagger-ui-express": "^2.0.13",
"sync-request": "^4.0.2"
"sync-request": "^4.0.2",
"vash": "^0.13.0"
}
}

142
public/css/bootstrap4-toggle.css vendored Normal file
View File

@@ -0,0 +1,142 @@
/*\
|*| ========================================================================
|*| Bootstrap Toggle: bootstrap4-toggle.css v3.6.1
|*| https://gitbrent.github.io/bootstrap4-toggle/
|*| ========================================================================
|*| Copyright 2018-2019 Brent Ely
|*| Licensed under MIT
|*| ========================================================================
\*/
/*
* @added 3.0.0: Return support for "*-xs" removed in Bootstrap-4
* @see: [Comment](https://github.com/twbs/bootstrap/issues/21881#issuecomment-341972830)
*/
.btn-group-xs > .btn, .btn-xs {
padding: .35rem .4rem .25rem .4rem;
font-size: .875rem;
line-height: .5;
border-radius: .2rem;
}
.checkbox label .toggle, .checkbox-inline .toggle {
margin-left: -1.25rem;
margin-right: .35rem;
}
.toggle {
position: relative;
overflow: hidden;
}
.toggle.btn.btn-light, .toggle.btn.btn-outline-light {
/* bootstrap-4 - add a border so toggle is delineated */
border-color: rgba(0, 0, 0, .15);
}
.toggle input[type="checkbox"] {
display: none;
}
.toggle-group {
position: absolute;
width: 200%;
top: 0;
bottom: 0;
left: 0;
transition: left 0.35s;
-webkit-transition: left 0.35s;
-moz-user-select: none;
-webkit-user-select: none;
}
.toggle-group label, .toggle-group span { cursor: pointer; }
.toggle.off .toggle-group {
left: -100%;
}
.toggle-on {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 50%;
margin: 0;
border: 0;
border-radius: 0;
}
.toggle-off {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
right: 0;
margin: 0;
border: 0;
border-radius: 0;
box-shadow: none; /* Bootstrap 4.0 Support via (Issue #186)[https://github.com/minhur/bootstrap-toggle/issues/186]) */
}
.toggle-handle {
position: relative;
margin: 0 auto;
padding-top: 0px;
padding-bottom: 0px;
height: 100%;
width: 0px;
border-width: 0 1px;
background-color: #fff;
}
.toggle.btn-outline-primary .toggle-handle {
background-color: var(--primary);
border-color: var(--primary);
}
.toggle.btn-outline-secondary .toggle-handle {
background-color: var(--secondary);
border-color: var(--secondary);
}
.toggle.btn-outline-success .toggle-handle {
background-color: var(--success);
border-color: var(--success);
}
.toggle.btn-outline-danger .toggle-handle {
background-color: var(--danger);
border-color: var(--danger);
}
.toggle.btn-outline-warning .toggle-handle {
background-color: var(--warning);
border-color: var(--warning);
}
.toggle.btn-outline-info .toggle-handle {
background-color: var(--info);
border-color: var(--info);
}
.toggle.btn-outline-light .toggle-handle {
background-color: var(--light);
border-color: var(--light);
}
.toggle.btn-outline-dark .toggle-handle {
background-color: var(--dark);
border-color: var(--dark);
}
.toggle[class*="btn-outline"]:hover .toggle-handle {
background-color: var(--light);
opacity: 0.5;
}
/* NOTE: Must come first, so classes below override as needed */
/* [default] (bootstrap-4.1.3 - .btn - h:38px) */
.toggle.btn { min-width: 3.7rem; min-height: 2.15rem; }
.toggle-on.btn { padding-right: 1.5rem; }
.toggle-off.btn { padding-left: 1.5rem; }
/* `lg` (bootstrap-4.1.3 - .btn - h:48px) */
.toggle.btn-lg { min-width: 5rem; min-height: 2.815rem; }
.toggle-on.btn-lg { padding-right: 2rem; }
.toggle-off.btn-lg { padding-left: 2rem; }
.toggle-handle.btn-lg { width: 2.5rem; }
/* `sm` (bootstrap-4.1.3 - .btn - h:31px) */
.toggle.btn-sm { min-width: 3.125rem; min-height: 1.938rem; }
.toggle-on.btn-sm { padding-right: 1rem; }
.toggle-off.btn-sm { padding-left: 1rem; }
/* `xs` (bootstrap-3.3 - .btn - h:22px) */
.toggle.btn-xs { min-width: 2.19rem; min-height: 1.375rem; }
.toggle-on.btn-xs { padding-right: .8rem; }
.toggle-off.btn-xs { padding-left: .8rem; }

187
public/js/bootstrap4-toggle.js vendored Normal file
View File

@@ -0,0 +1,187 @@
/*\
|*| ========================================================================
|*| Bootstrap Toggle: bootstrap4-toggle.js v3.6.1
|*| https://gitbrent.github.io/bootstrap4-toggle/
|*| ========================================================================
|*| Copyright 2018-2019 Brent Ely
|*| Licensed under MIT
|*| ========================================================================
\*/
+function ($) {
'use strict';
// TOGGLE PUBLIC CLASS DEFINITION
// ==============================
var Toggle = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, this.defaults(), options)
this.render()
}
Toggle.VERSION = '3.7.0-beta'
Toggle.DEFAULTS = {
on: 'On',
off: 'Off',
onstyle: 'primary',
offstyle: 'light',
size: 'normal',
style: '',
width: null,
height: null
}
Toggle.prototype.defaults = function() {
return {
on: this.$element.attr('data-on') || Toggle.DEFAULTS.on,
off: this.$element.attr('data-off') || Toggle.DEFAULTS.off,
onstyle: this.$element.attr('data-onstyle') || Toggle.DEFAULTS.onstyle,
offstyle: this.$element.attr('data-offstyle') || Toggle.DEFAULTS.offstyle,
size: this.$element.attr('data-size') || Toggle.DEFAULTS.size,
style: this.$element.attr('data-style') || Toggle.DEFAULTS.style,
width: this.$element.attr('data-width') || Toggle.DEFAULTS.width,
height: this.$element.attr('data-height') || Toggle.DEFAULTS.height
}
}
Toggle.prototype.render = function () {
this._onstyle = 'btn-' + this.options.onstyle
this._offstyle = 'btn-' + this.options.offstyle
var size
= this.options.size === 'large' || this.options.size === 'lg' ? 'btn-lg'
: this.options.size === 'small' || this.options.size === 'sm' ? 'btn-sm'
: this.options.size === 'mini' || this.options.size === 'xs' ? 'btn-xs'
: ''
var $toggleOn = $('<label for="'+ this.$element.prop('id') +'" class="btn">').html(this.options.on)
.addClass(this._onstyle + ' ' + size)
var $toggleOff = $('<label for="'+ this.$element.prop('id') +'" class="btn">').html(this.options.off)
.addClass(this._offstyle + ' ' + size)
var $toggleHandle = $('<span class="toggle-handle btn btn-light">')
.addClass(size)
var $toggleGroup = $('<div class="toggle-group">')
.append($toggleOn, $toggleOff, $toggleHandle)
var $toggle = $('<div class="toggle btn" data-toggle="toggle" role="button">')
.addClass( this.$element.prop('checked') ? this._onstyle : this._offstyle+' off' )
.addClass(size).addClass(this.options.style)
this.$element.wrap($toggle)
$.extend(this, {
$toggle: this.$element.parent(),
$toggleOn: $toggleOn,
$toggleOff: $toggleOff,
$toggleGroup: $toggleGroup
})
this.$toggle.append($toggleGroup)
var width = this.options.width || Math.max($toggleOn.outerWidth(), $toggleOff.outerWidth())+($toggleHandle.outerWidth()/2)
var height = this.options.height || Math.max($toggleOn.outerHeight(), $toggleOff.outerHeight())
$toggleOn.addClass('toggle-on')
$toggleOff.addClass('toggle-off')
this.$toggle.css({ width: width, height: height })
if (this.options.height) {
$toggleOn.css('line-height', $toggleOn.height() + 'px')
$toggleOff.css('line-height', $toggleOff.height() + 'px')
}
this.update(true)
this.trigger(true)
}
Toggle.prototype.toggle = function () {
if (this.$element.prop('checked')) this.off()
else this.on()
}
Toggle.prototype.on = function (silent) {
if (this.$element.prop('disabled')) return false
this.$toggle.removeClass(this._offstyle + ' off').addClass(this._onstyle)
this.$element.prop('checked', true)
if (!silent) this.trigger()
}
Toggle.prototype.off = function (silent) {
if (this.$element.prop('disabled')) return false
this.$toggle.removeClass(this._onstyle).addClass(this._offstyle + ' off')
this.$element.prop('checked', false)
if (!silent) this.trigger()
}
Toggle.prototype.enable = function () {
this.$toggle.removeClass('disabled')
this.$toggle.removeAttr('disabled')
this.$element.prop('disabled', false)
}
Toggle.prototype.disable = function () {
this.$toggle.addClass('disabled')
this.$toggle.attr('disabled', 'disabled')
this.$element.prop('disabled', true)
}
Toggle.prototype.update = function (silent) {
if (this.$element.prop('disabled')) this.disable()
else this.enable()
if (this.$element.prop('checked')) this.on(silent)
else this.off(silent)
}
Toggle.prototype.trigger = function (silent) {
this.$element.off('change.bs.toggle')
if (!silent) this.$element.change()
this.$element.on('change.bs.toggle', $.proxy(function() {
this.update()
}, this))
}
Toggle.prototype.destroy = function() {
this.$element.off('change.bs.toggle')
this.$toggleGroup.remove()
this.$element.removeData('bs.toggle')
this.$element.unwrap()
}
// TOGGLE PLUGIN DEFINITION
// ========================
function Plugin(option) {
var optArg = Array.prototype.slice.call( arguments, 1 )[0]
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.toggle')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.toggle', (data = new Toggle(this, options)))
if (typeof option === 'string' && data[option] && typeof optArg === 'boolean') data[option](optArg)
else if (typeof option === 'string' && data[option]) data[option]()
//else if (option && !data[option]) console.log('bootstrap-toggle: error: method `'+ option +'` does not exist!');
})
}
var old = $.fn.bootstrapToggle
$.fn.bootstrapToggle = Plugin
$.fn.bootstrapToggle.Constructor = Toggle
// TOGGLE NO CONFLICT
// ==================
$.fn.toggle.noConflict = function () {
$.fn.bootstrapToggle = old
return this
}
// TOGGLE DATA-API
// ===============
$(function() {
$('input[type=checkbox][data-toggle^=toggle]').bootstrapToggle()
})
$(document).on('click.bs.toggle', 'div[data-toggle^=toggle]', function(e) {
var $checkbox = $(this).find('input[type=checkbox]')
$checkbox.bootstrapToggle('toggle')
e.preventDefault()
})
}(jQuery);

11
public/js/bootstrap4-toggle.min.js vendored Normal file
View File

@@ -0,0 +1,11 @@
/*\
|*| ========================================================================
|*| Bootstrap Toggle: bootstrap4-toggle.js v3.6.1
|*| https://gitbrent.github.io/bootstrap4-toggle/
|*| ========================================================================
|*| Copyright 2018-2019 Brent Ely
|*| Licensed under MIT
|*| ========================================================================
\*/
!function(a){"use strict";function l(t,e){this.$element=a(t),this.options=a.extend({},this.defaults(),e),this.render()}l.VERSION="3.6.0",l.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"light",size:"normal",style:"",width:null,height:null},l.prototype.defaults=function(){return{on:this.$element.attr("data-on")||l.DEFAULTS.on,off:this.$element.attr("data-off")||l.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||l.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||l.DEFAULTS.offstyle,size:this.$element.attr("data-size")||l.DEFAULTS.size,style:this.$element.attr("data-style")||l.DEFAULTS.style,width:this.$element.attr("data-width")||l.DEFAULTS.width,height:this.$element.attr("data-height")||l.DEFAULTS.height}},l.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var t="large"===this.options.size||"lg"===this.options.size?"btn-lg":"small"===this.options.size||"sm"===this.options.size?"btn-sm":"mini"===this.options.size||"xs"===this.options.size?"btn-xs":"",e=a('<label for="'+this.$element.prop("id")+'" class="btn">').html(this.options.on).addClass(this._onstyle+" "+t),s=a('<label for="'+this.$element.prop("id")+'" class="btn">').html(this.options.off).addClass(this._offstyle+" "+t),o=a('<span class="toggle-handle btn btn-light">').addClass(t),i=a('<div class="toggle-group">').append(e,s,o),l=a('<div class="toggle btn" data-toggle="toggle" role="button">').addClass(this.$element.prop("checked")?this._onstyle:this._offstyle+" off").addClass(t).addClass(this.options.style);this.$element.wrap(l),a.extend(this,{$toggle:this.$element.parent(),$toggleOn:e,$toggleOff:s,$toggleGroup:i}),this.$toggle.append(i);var n=this.options.width||Math.max(e.outerWidth(),s.outerWidth())+o.outerWidth()/2,h=this.options.height||Math.max(e.outerHeight(),s.outerHeight());e.addClass("toggle-on"),s.addClass("toggle-off"),this.$toggle.css({width:n,height:h}),this.options.height&&(e.css("line-height",e.height()+"px"),s.css("line-height",s.height()+"px")),this.update(!0),this.trigger(!0)},l.prototype.toggle=function(){this.$element.prop("checked")?this.off():this.on()},l.prototype.on=function(t){if(this.$element.prop("disabled"))return!1;this.$toggle.removeClass(this._offstyle+" off").addClass(this._onstyle),this.$element.prop("checked",!0),t||this.trigger()},l.prototype.off=function(t){if(this.$element.prop("disabled"))return!1;this.$toggle.removeClass(this._onstyle).addClass(this._offstyle+" off"),this.$element.prop("checked",!1),t||this.trigger()},l.prototype.enable=function(){this.$toggle.removeClass("disabled"),this.$toggle.removeAttr("disabled"),this.$element.prop("disabled",!1)},l.prototype.disable=function(){this.$toggle.addClass("disabled"),this.$toggle.attr("disabled","disabled"),this.$element.prop("disabled",!0)},l.prototype.update=function(t){this.$element.prop("disabled")?this.disable():this.enable(),this.$element.prop("checked")?this.on(t):this.off(t)},l.prototype.trigger=function(t){this.$element.off("change.bs.toggle"),t||this.$element.change(),this.$element.on("change.bs.toggle",a.proxy(function(){this.update()},this))},l.prototype.destroy=function(){this.$element.off("change.bs.toggle"),this.$toggleGroup.remove(),this.$element.removeData("bs.toggle"),this.$element.unwrap()};var t=a.fn.bootstrapToggle;a.fn.bootstrapToggle=function(o){var i=Array.prototype.slice.call(arguments,1)[0];return this.each(function(){var t=a(this),e=t.data("bs.toggle"),s="object"==typeof o&&o;e||t.data("bs.toggle",e=new l(this,s)),"string"==typeof o&&e[o]&&"boolean"==typeof i?e[o](i):"string"==typeof o&&e[o]&&e[o]()})},a.fn.bootstrapToggle.Constructor=l,a.fn.toggle.noConflict=function(){return a.fn.bootstrapToggle=t,this},a(function(){a("input[type=checkbox][data-toggle^=toggle]").bootstrapToggle()}),a(document).on("click.bs.toggle","div[data-toggle^=toggle]",function(t){a(this).find("input[type=checkbox]").bootstrapToggle("toggle"),t.preventDefault()})}(jQuery);
//# sourceMappingURL=bootstrap4-toggle.min.js.map

110
server.js Normal file
View File

@@ -0,0 +1,110 @@
var express = require('express');
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
var db = require('./db');
// Configure the local strategy for use by Passport.
//
// The local strategy require a `verify` function which receives the credentials
// (`username` and `password`) submitted by the user. The function must verify
// that the password is correct and then invoke `cb` with a user object, which
// will be set at `req.user` in route handlers after authentication.
passport.use(new Strategy({
passReqToCallback: true},
function(username, password, cb) {
console.log('requesting authentication for user '+ username);
db.users.findByUsername(username, function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
if (user.password != password) { return cb(null, false); }
return cb(null, user);
});
}));
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
// to serialize users into and deserialize users out of the session. The
// typical implementation of this is as simple as supplying the user ID when
// serializing, and querying the user record by ID from the database when
// deserializing.
passport.serializeUser(function(user, cb) {
cb(null, user.id);
});
passport.deserializeUser(function(id, cb) {
db.users.findById(id, function (err, user) {
if (err) { return cb(err); }
cb(null, user);
});
});
// Create a new Express application.
var app = express();
// Configure view engine to render EJS templates.
//app.set('views', __dirname + '/views');
//app.set('view engine', 'ejs');
app.set('view engine', 'vash');
app.use(express.static('public'))
// Use application-level middleware for common functionality, including
// logging, parsing, and session handling.
app.use(require('morgan')('combined'));
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));
// Initialize Passport and restore authentication state, if any, from the
// session.
app.use(passport.initialize());
app.use(passport.session());
// Define routes.
app.get('/',
function(req, res) {
res.render('layout', { user: req.user });
});
app.get('/login',
function(req, res){
res.render('login');
});
app.post('/login',
passport.authenticate('local', { failureRedirect: '/n/login' }),
function(req, res) {
res.redirect('/n/');
});
app.get('/logout',
function(req, res){
req.logout();
res.redirect('/n/');
});
app.get('/accontrol',
// passport.authenticate('local', {
// failureRedirect: '/n/login' ,
// successRedirect: '/n/accontrol'}),
require('connect-ensure-login').ensureLoggedIn('/n/login'),
function(req, res){
res.render('accontrol', { user: req.user });
});
//app.listen(81);
var fs = require("fs");
var https = require('https');
var privateKey = fs.readFileSync('/etc/letsencrypt/live/iot.d-popov.com/privkey.pem', 'utf8');
var certificate = fs.readFileSync('/etc/letsencrypt/live/iot.d-popov.com/cert.pem', 'utf8');
var credentials = {key: privateKey, cert: certificate};
var httpsServer = https.createServer(credentials, app);
httpsServer.listen(8443, () => {
console.log('HTTP server listening on port 8443');
});

20
views/accontrol.ejs Normal file
View File

@@ -0,0 +1,20 @@
<h2>AC Control for <%= model.user.username %></h2>
<form action="/n/accontrol" class="form-inline" method="POST">
<div class="form-group">
<input type='checkbox' name='power' <%= model.data.power|true ? "checked" : "" %> data-toggle='toggle'
data-style='android' data-on='ON' data-off='Off' />
</div>
<div class="form-group">
<input type='checkbox' name='heat' <%= model.data.heat|true ? "checked" : "" %> data-toggle='toggle' data-on='Heat'
data-off='Cool' data-onstyle='warning' data-offstyle='info' />
</div>
<div class="form-group">
<input type='number' name='temp' value='<%= model.data.temp|23%>' min='16' max='32' step='0.5' />
</div>
<div class="form-group">
<!-- <input type='checkbox' name='econo'<%= model.data.econo ? "checked" : "" %> data-toggle='toggle' data-style='android' data-on='Eco' data-off='Normal' data-onstyle='info' data-offstyle='warning'/> -->
<input type='submit' class='button' value='SET!'>
</div>
</form>
INFO:
<%= model.info %>

128
views/chart.ejs Normal file
View File

@@ -0,0 +1,128 @@
<div id="chartContainer" style="height: 370px; width: 100%;"></div>
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
<%- contentFor('head') %>
<script>
var current;
window.onload = function () {
var dataPoints1 = [];
var dataPoints2 = [];
/*{ x: new Date(2017, 0, 3), y: 650 },
{ x: new Date(2017, 0, 4), y: 700 },
*/
var chart = new CanvasJS.Chart("chartContainer", {
animationEnabled: true,
theme: "light2",
zoomEnabled: true,
zoomType: "x",
exportEnabled: true,
title:{
text: "Retrieving current A23 conditions..."
},
/* subtitles:[{
text: "X Axis scale is Logarithmic",
fontSize: 14
}],*/
axisX:{
valueFormatString: "HH:mm DD MMM",
crosshair: {
enabled: true,
snapToDataPoint: true
}
},
axisY: {
title: "Temperature",
lineColor: "#C24642",
titleFontColor: "#C24642",
labelFontColor: "#C24642",
crosshair: {
enabled: true
}
},
axisY2: [
{
title: "Relative hum %",
titleFontColor: "#51CDA0",
labelFontColor: "#51CDA0"
}
],
//{title: "Temperature",
//lineColor: "#C24642",
//titleFontColor: "#C0504E",
//labelFontColor: "#C0504E"
//},
toolTip:{
shared:true
},
legend:{
cursor:"pointer",
verticalAlign: "bottom",
horizontalAlign: "left",
dockInsidePlotArea: true,
itemclick: toogleDataSeries
},
data: [{
name: "Temperature",
type: "spline",//line
showInLegend: true,
markerType: "square",
xValueFormatString: "HH:mm on DD MMM, YYYY",
color: "#F08080",
dataPoints: dataPoints2
},
{
name: "Humidiry",
type: "spline",
axisYType: "secondary",
axisYIndex: 1,
showInLegend: true,
lineDashType: "dash",
dataPoints: dataPoints1
}]
});
function toogleDataSeries(e){
if (typeof(e.dataSeries.visible) === "undefined" || e.dataSeries.visible) {
e.dataSeries.visible = false;
} else {
e.dataSeries.visible = true;
}
chart.render();
}
function addData(data) {
for (var i = 0; i < data.length; i++) {
if(data[i].field_name == "A23_DHT"){
var dht = JSON.parse(data[i].field_value).dht;
var date = new Date(data[i]["timestamp"]);
if(dht && dht.hum <= 100){
dataPoints1.push({
x: date,
y: dht.hum
});
dataPoints2.push({
x: date,
y: dht.temp
});
}
}
}
chart.render();
$.getJSON("/n/dht", function(data){
if(data && data.dht){
chart.title.set("text", "A23 Currently is " + data.dht.temp + "°C, " + data.dht.hum +"% RH" );
}else {
chart.title.set("text", "A23 conditions");
}
});
}
$.getJSON("/n/dht/A23_DHT", addData);
}
</script>

28
views/layout.ejs Normal file
View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<title>ESP8266 Weather Server</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="/n/css/bootstrap4-toggle.css" type="text/css" >
<style>
html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
.button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}
.button2 {background-color: #77878A;}
</style>
<%- defineContent("head") %>
</head>
<body>
<%- include('nav'); -%>
<main role='main' class='container'>
<%- body %>
</main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<!-- <script src='https://code.jquery.com/jquery-3.2.1.slim.min.js' integrity='sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN' crossorigin='anonymous'></script> -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js' integrity='sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q' crossorigin='anonymous'></script>
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js' integrity='sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl' crossorigin='anonymous'></script>
<script src="/n/js/bootstrap4-toggle.js" integrity="sha256-8hY+ssbh4ap2bCD3tlbINeezcjxRL5IlWDmzNLQBO/U="></script>
<%- defineContent("scripts") %>
</body>
</html>

13
views/login.ejs Normal file
View File

@@ -0,0 +1,13 @@
<form action="/n/login" method="post">
<div class="form-group">
<label>Username:</label>
<input type="text" name="username" class="form-control" />
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" name="password" class="form-control" />
</div>
<div class="form-group">
<input type="submit" class='btn btn-default button' value="Log In" />
</div>
</form>

4
views/nav.ejs Normal file
View File

@@ -0,0 +1,4 @@
<a href="/n/accontrol" >control</a>
<a href="/n/logout" >log out</a>
<a href="/n/chart"> chart</a>