Skip to content

Commit

Permalink
Merge branch 'master' into camille500/new_interactive
Browse files Browse the repository at this point in the history
  • Loading branch information
camille500 committed Jun 28, 2017
2 parents 58eb8d0 + 77c4d3b commit 0446a36
Show file tree
Hide file tree
Showing 23 changed files with 167 additions and 135 deletions.
63 changes: 51 additions & 12 deletions README.md
Expand Up @@ -8,6 +8,8 @@ We have 2 specific target audiences that both needed their own approach.
- **The operators** of the Biogasboot, they have to see all the data that is coming from the sensors of the Biogasboot.
- **The customers** of café De Ceuvel, they don't need all the data but only a fraction of it so realize what the Biogasboot does.

![fe-biogasboot](md-media/fe-biogasboot.png)

### Operators
The operator is the person who takes control of the food waste digester. He or she wants to know the current state of the digester with the current values of the process parameters. Historical data should be accessible to the operator to determine if a new adjustment had a good effect on the process compared to a previous run. Remarkable trends in the data should also be notified which the operator gets the response of.

Expand Down Expand Up @@ -73,8 +75,10 @@ function filterData(format, date, data) {
```

## Screenshots
![Live Operator Dashboard](screenshots/dashboard1.png)
![History Data Overview](screenshots/dashboard2.png)
All views of the dashboard currently in the app
![Live Operator Dashboard](md-media/dashboard.png)
![History Data Overview](md-media/historic-dashboard.png)
![Live mobile Dashboard bundle](md-media/mobile-dashboard-bundle.png)

## Dependencies
* [x] [`BCryptjs`](https://www.npmjs.com/package/bcryptjs) Password hashing
Expand Down Expand Up @@ -116,15 +120,13 @@ function filterData(format, date, data) {
* [x] [`Watchify`](https://www.npmjs.com/package/watchify) Watch mode for Browserify builds

## ToDo
* [ ] Push notification for operators if something goes wrong
* [ ] Smart defaults for selecting range's / views in the backend
* [ ] Layout for the history section
* [ ] Filters for the history section
* [ ] D3 Graphs for comparing time ranges
* [ ] Interactive view of the Biogasboot
* [ ] Dashboard view for in Café de Ceuvel
* [ ] Possibility for admins to add event data to the dashboard
* [ ] Build a guide for the operator application
* [x] Push notification for operators if something goes wrong
* [x] Layout for the history section
* [x] Filters for the history section
* [x] D3 Graphs for comparing time ranges
* [x] Interactive view of the Biogasboot
* [x] Dashboard view for in Café de Ceuvel
* [x] Build a guide for the operator application

## Finished ToDo's
* [x] Range selector to select two dates and pass the range to the front-end
Expand All @@ -136,6 +138,10 @@ function filterData(format, date, data) {

## Wishlist
* [ ] Live data from the Biogasboot
* [ ] Plotting of status data
* [ ] Piechart of process devices
* [ ] Possibility for admins to add event data to the dashboard
* [ ] Smart defaults for selecting range's / views in the backend

## API Endpoints
The application has multiple API endpoints. This is an overview of all the possibilities.
Expand All @@ -156,6 +162,37 @@ This call returns the data of a specific day. All values are added up and the 'c
`/api/all?dateStart=1489720679&dateEnd=1490268059&format=d`
Get the average per day in a specific range. Use a UNIX timestamp as date, followed by `&format=d`

## Notifications with ServiceWorker
As addition on the websockets we made a ServiceWorker that can send notifications to devices that are subscribed. This is very usefull when a warning state is triggered but the operator isn't watching is phone dashboard. The subscriptions are saved in the MongoDB database so when the server restarts the subscriptions aren't gone To send notifications we needed a GCM_API_KEY (Google Cloud Messaging).

The notifications are used for the real-time dashboard so the operator doesn't have to watch his phone all the time.

## Calculation with the data
The Biogasboot stores the data in a CSV file but this RAW data and we can't do everything with this that. Some stakeholder want a bundle of multiple values but those bundles aren't found in the CSV files.

### Usage calculations
The calculations we did are mostly found in modules/usage-calculation.js here is calculated how long a device is ON in 1 month. When we know how long it's ON we can calculate the energy usage in Wh and kWh. Those calculations are stored in an object and then pushed to the front-end. For every calculation there is a comment how the calculation is working.

## Config variables
The application makes use variables that aren't clear yet so we made a config file where all the different variables are stored.

The config can be founded in the folder "modules/config.js". Here you can define the following things:
* The min, max, low, high values of a parameter like PH value or gasbag.
* The above has impact on the tileStatus function that will define the state of a value.
* Every device has it's own usage per hour this can also changed here.
* When the control panel of the Biogasboot is connected to a FTP server you can also modify the FTP settings.

```javascript

// It can be included in front-end and backend files you only need to call the right function that you needed

// For backend modules
const config = require('./config');

// For front-end modules
const config = require('../../../modules/config');
```

## Build / Install and start project

### Clone this repo
Expand Down Expand Up @@ -204,6 +241,8 @@ npm run start-update
Diego Staphorst   | Sjoerd Beentjes | Timo Verkroost | Camille Sébastien
--- | --- | --- | ---
![Diego Staphorst][diego] | ![Sjoerd Beentjes][sjoerd] | ![Timo Verkroost][timo] | ![Camille Sébastien][camille]
[Contributor link](contributors/diego.md) | [Contributor link](contributors/sjoerd.md) | [Contributor link](contributors/timo.md) | [Contributor link](Contributor/camille.md)

## License
MIT © Diego Staphorst, Sjoerd Beentjes, Timo Verkroost, Camille Sébastien

Expand All @@ -213,4 +252,4 @@ MIT © Diego Staphorst, Sjoerd Beentjes, Timo Verkroost, Camille Sébastien

[timo]: https://avatars2.githubusercontent.com/u/17787175?v=3&s=400 "Timo Verkroost"

[Camille]: https://avatars1.githubusercontent.com/u/8942820?v=3&s=460 "Camille Sébastien"
[Camille]: https://avatars1.githubusercontent.com/u/8942820?v=3&s=400 "Camille Sébastien"
1 change: 1 addition & 0 deletions contributors/camille.md
@@ -0,0 +1 @@
# Contribution of Camille
35 changes: 6 additions & 29 deletions contributors/timo.md
@@ -1,22 +1,3 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Contribution of Timo Verkroost](#contribution-of-timo-verkroost)
- [Table of contents](#table-of-contents)
- [Process](#process)
- [Builded modules](#builded-modules)
- [Module 1](#module-1)
- [Subjects](#subjects)
- [Web App from Scratch](#web-app-from-scratch)
- [CSS to the Rescue](#css-to-the-rescue)
- [Performance Matters](#performance-matters)
- [Browser Technologies](#browser-technologies)
- [Real-Time Web](#real-time-web)
- [Web of Things](#web-of-things)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

# Contribution of Timo Verkroost
Text

Expand All @@ -41,7 +22,7 @@ Explain your process
Intro

### Config file
To make it easy for everyone who's working with our server I created a config file where all the variables are stored. This file is included in other modules that need the variables. So when the config is changed it will change everwhere where it's included.
To make it easy for everyone who's working with our server I created a config file where all the variables are stored. This file is included in other modules that need the variables. So when the config is changed it will change everywhere where it's included.

This a combine of multiple module settings placed in 1 file.

Expand Down Expand Up @@ -578,9 +559,9 @@ label[for="showFilters"]::after {
</details>

### Notifications with ServiceWorker
As addition on the websockets I made a ServiceWorker that can send notifications to devices that are subscribed. This is very usefull when a warning state is triggered but the operator isn't watching is phone dashboard. The subscriptions are saved in the MongoDB database so when the server restarts the subscriptions aren't gone. For the ServiceWorker I used the source [ServiceWorkers Cookbook](https://serviceworke.rs/). Also I used the Web-push module to send the notifcations. To send notifcations I needed a GCM_API_KEY (Google Cloud Messaging).
As addition on the websockets I made a ServiceWorker that can send notifications to devices that are subscribed. This is very usefull when a warning state is triggered but the operator isn't watching is phone dashboard. The subscriptions are saved in the MongoDB database so when the server restarts the subscriptions aren't gone. For the ServiceWorker I used the source [ServiceWorkers Cookbook](https://serviceworke.rs/). Also I used the Web-push module to send the notifications. To send notifications I needed a GCM_API_KEY (Google Cloud Messaging).

For me it was a challange to let this work because working with ServiceWorkers can be hard. I tried many things and I'm proud of the result.
For me it was a challenge to let this work because working with ServiceWorkers can be hard. I tried many things and I'm proud of the result.
#### NPM modules used
* [`Web-push`](https://www.npmjs.com/package/web-push) Library for push notifications
Expand Down Expand Up @@ -747,20 +728,16 @@ function sendGasBagLow() {
</details>
### Compare table in history + switch between Wh and kWh (CSS only)
Previously I made the usage calculation module now I'm gonna use it to render the history page compare months. First I made it possible to loop through the database to find all the year and months available to fill in the compare months.

On the history dashboard it is possible to compare 2 months also to compare the usages of those months. But sometimes you want to see the energy usage in Wh (watt hour) and sometimes in kWh (kilo watt hour). The values ar already saved in the usageCalculation module but it's overkill to show them both at the same time.
So I decided to make a switch possible with only CSS, later JS was added but it works without JS.
#### Code snippets
* [Hitory route](https://github.com/sjoerdbeentjes/biogasboot/blob/master/routes/operator/dashboard-history.js)
* [Builded SCSS file](https://github.com/sjoerdbeentjes/biogasboot/blob/master/src/scss/03-proteins/aside.scss)

```javascript
// JavaScript code
```

```html
<!-- HTML Code -->
Expand Down
Binary file added md-media/dashboard.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added md-media/historic-dashboard.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added md-media/mobile-dashboard-bundle.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added md-media/mobile-dashboard.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added md-media/mobile-notifcation.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 17 additions & 14 deletions modules/websockets.js
Expand Up @@ -9,10 +9,10 @@ const dataPoint = require('../models/dataPoint');
const Subscription = require('../models/subscription');

// Make objects for D3.js
const getUsedValues = function(){
const getUsedValues = function () {
let i = 0;
let values = [];
for (let key in config.defineValues) {
const values = [];
for (const key in config.defineValues) {
i++;
values.push({
name: config.defineValues[key].name,
Expand Down Expand Up @@ -43,9 +43,11 @@ function sendNotification(subscription, payload) {
}
}, payload).then(() => {
console.log('Push Application Server - Notification sent to ' + subscription.endpoint);
}).catch((err) => {
}).catch(err => {
// Remove from subscription list in DB when there is a error
Subscription.findOneAndRemove({endpoint: subscription.endpoint}, function (err, docs) {});
Subscription.findOneAndRemove({
endpoint: subscription.endpoint
}, (err, docs) => {});
console.log('ERROR in sending Notification, endpoint removed ' + subscription.endpoint);
console.log(err);
});
Expand Down Expand Up @@ -82,25 +84,26 @@ function webSokets(app, io) {
const startDate = moment(Number(range) * 1000);
const endDate = moment(Number(startDate + months));
dataPoint.find({
Date: {
$gte: startDate.toDate(),
$lt: endDate.toDate()
}
},
(err, dataPoints) => {
Date: {
$gte: startDate.toDate(),
$lt: endDate.toDate()
}
})
.sort([['Date', 'ascending']])
.exec((err, dataPoints) => {
let i = 0;
const sendItemsCount = 30;
let sendTimeOutHigh = false;
let sendTimeOutLow = false;
setInterval(() => {

setInterval(() => {
if (!dataPoints[i + sendItemsCount]) {
i = 0;
}

const dataCollection = [];

for (let x = 0; x <= sendItemsCount; x++) {
for (let x = 0; x < sendItemsCount; x++) {
dataCollection.push(dataPoints[x + i]);
if (dataPoints[x + i].Bag_Height >= usedValues[2].high) {
if (dataPoints[x + i - 1].Bag_Height < usedValues[2].high && sendTimeOutHigh === false) {
Expand All @@ -119,7 +122,7 @@ function webSokets(app, io) {
sendTimeOutHigh = false;
sendTimeOutLow = false;
io.sockets.emit('dataPoint', dataCollection, config.tileStatus(dataPoints[i]));
}, 10000);
}, 50);
});
}

Expand Down
2 changes: 1 addition & 1 deletion routes/auth.js
Expand Up @@ -20,7 +20,7 @@ router.get('/register', (req, res) => {
if (res.locals.user && res.locals.user.role === 'admin') {
res.render('register');
} else {
res.status(404).render('404');
//res.status(404).render('login');
}
});

Expand Down
71 changes: 37 additions & 34 deletions routes/operator/dashboard-history.js
Expand Up @@ -7,40 +7,43 @@ const dataPoint = require('../../models/dataPoint');

/* Operator | Dashboard */
router.get('/', (req, res, next) => {
dataPoint.aggregate([
{$project : {
year : {$year : "$Date"},
month : {$month : "$Date"},
}},
{$group : {
_id : {year : "$year",month : "$month"}
}},
{ $sort: {'_id.year':1, '_id.month':1} }
], (err, result) => {
if (err) {
console.log(err);
} else {
// Get years
let years = [];
for (let i = 0; i < result.length; i++) {
// Only adds to years when not exist to prevent duplicates
years.indexOf(result[i]._id.year) === -1 ? years.push(result[i]._id.year): years;
}
// Get months
let months = [];
for (let i = 0; i < result.length; i++) {
// Only adds to months when not exist to prevent duplicates
months.indexOf(result[i]._id.month) === -1 ? months.push(result[i]._id.month): months;
}
// Init month names
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
res.locals.dorpdownYears = years;
res.locals.dorpdownMonths = months;
res.locals.dorpdownMonthNames = monthNames;
res.render('operator/history', {title: 'Operator | Dashboard'});
}
});

if (res.locals.user) {
dataPoint.aggregate([
{$project : {
year : {$year : "$Date"},
month : {$month : "$Date"},
}},
{$group : {
_id : {year : "$year",month : "$month"}
}},
{ $sort: {'_id.year':1, '_id.month':1} }
], (err, result) => {
if (err) {
console.log(err);
} else {
// Get years
let years = [];
for (let i = 0; i < result.length; i++) {
// Only adds to years when not exist to prevent duplicates
years.indexOf(result[i]._id.year) === -1 ? years.push(result[i]._id.year): years;
}
// Get months
let months = [];
for (let i = 0; i < result.length; i++) {
// Only adds to months when not exist to prevent duplicates
months.indexOf(result[i]._id.month) === -1 ? months.push(result[i]._id.month): months;
}
// Init month names
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
res.locals.dorpdownYears = years;
res.locals.dorpdownMonths = months;
res.locals.dorpdownMonthNames = monthNames;
res.render('operator/history', {title: 'Operator | Dashboard'});
}
});
} else {
res.status(404).render('login');
}
});

module.exports = router;
18 changes: 8 additions & 10 deletions routes/operator/dashboard.js
Expand Up @@ -6,16 +6,14 @@ const User = require('../../models/user');

/* Operator | Dashboard */
router.get('/', (req, res, next) => {
// if (res.locals.user) {
// User.find((err, users) => {
// // res.locals.users = users;
// // console.log(res.locals.users);
// res.render('operator/dashboard', {title: 'Operator | Dashboard'});
// });
// } else {
// res.status(404).render('404');
// }
res.render('operator/dashboard', {title: 'Operator | Dashboard'});
if (res.locals.user) {
User.find((err, users) => {
res.locals.isRealTime = true;
res.render('operator/dashboard', {title: 'Operator | Dashboard'});
});
} else {
res.status(404).render('login');
}
});

module.exports = router;
Binary file removed screenshots/dashboard1.png
Binary file not shown.
Binary file removed screenshots/dashboard2.png
Binary file not shown.
6 changes: 3 additions & 3 deletions src/js/index.js
@@ -1,7 +1,7 @@
//require('./modules/real-time-graph');
require('./modules/real-time-graph');
require('./modules/history-graph');
require('./modules/customer-dashboard');
require('./modules/interactive-dashboard');
//require('./modules/customer-dashboard');
//require('./modules/interactive-dashboard');
require('./modules/range-selector');
require('./modules/toggleWh');

Expand Down
2 changes: 1 addition & 1 deletion src/js/modules/history-graph.js
@@ -1,7 +1,7 @@
const d3 = require('d3');
const config = require('../../../modules/config');

if (document.querySelector('#history-graph')) {
if (document.querySelector('#history-graph') && document.querySelector('#history-graph').clientWidth) {
const containerWidth = document.querySelector('#history-graph').parentNode.clientWidth;
const containerHeight = document.querySelector('#history-graph').parentNode.clientHeight;

Expand Down
3 changes: 2 additions & 1 deletion src/js/modules/real-time-graph.js
Expand Up @@ -24,7 +24,8 @@ const getUsedValues = function(){
// Fill the values
const usedValues = getUsedValues();

if (document.querySelector('#chart')) {
if (document.querySelector('#chart') && document.querySelector('#chart').clientWidth) {
console.log('loaded');
const socket = io.connect();

let data = [];
Expand Down

0 comments on commit 0446a36

Please sign in to comment.