├── server ├── .htaccess ├── views │ ├── icon │ │ ├── brewicon_25x25.png │ │ ├── brewicon_30x30.png │ │ ├── brewicon_35x35.png │ │ ├── brewicon_192x192.png │ │ ├── brewicon_196x196.png │ │ ├── brewicon_apple_114x114.png │ │ ├── brewicon_apple_144x144.png │ │ ├── brewicon_apple_192x192.png │ │ └── brewicon_apple_72x72.png │ ├── change.ejs │ ├── login.ejs │ ├── connect-local.ejs │ ├── profile.ejs │ ├── signup.ejs │ ├── index.ejs │ └── reset.ejs ├── libs │ ├── version-self.json │ ├── telegram.js │ ├── brewdate.js │ ├── defaults.js │ ├── hostname.js │ ├── plausible.js │ ├── cpuid.js │ ├── brewlog.js │ ├── common.js │ ├── restoreclear.js │ └── version.js ├── config │ ├── database.js │ └── passport.js ├── app │ └── models │ │ ├── hardware.js │ │ ├── user.js │ │ └── appsetting.js ├── package.json └── setup.js ├── doc ├── img │ ├── wiring.png │ ├── screen_logs.png │ ├── screen_manual.png │ ├── screen_recipes.png │ ├── screen_automatic.png │ ├── screen_automatic1.png │ ├── screen_settings.png │ ├── BierBot-Logo_338x70.png │ ├── screen_logs_detail.png │ ├── screen_recipes_editstep.png │ └── screen_settings_hardware.png ├── SETUP_MANUAL.MD ├── README_DE.MD └── CONTRIBUTING.MD ├── client ├── media │ └── plop.mp3 ├── css │ ├── brew │ ├── font-awesome-4.5.0 │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── less │ │ │ ├── fixed-width.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── core.less │ │ │ ├── font-awesome.less │ │ │ ├── stacked.less │ │ │ ├── bordered-pulled.less │ │ │ ├── rotated-flipped.less │ │ │ ├── path.less │ │ │ ├── animated.less │ │ │ └── mixins.less │ │ └── scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _larger.scss │ │ │ ├── _list.scss │ │ │ ├── font-awesome.scss │ │ │ ├── _core.scss │ │ │ ├── _stacked.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _path.scss │ │ │ ├── _animated.scss │ │ │ └── _mixins.scss │ └── brew.css ├── icon │ ├── brewicon_25x25.png │ ├── brewicon_30x30.png │ ├── brewicon_35x35.png │ ├── brewicon_192x192.png │ ├── brewicon_196x196.png │ ├── brewicon_apple_72x72.png │ ├── brewicon_apple_114x114.png │ ├── brewicon_apple_144x144.png │ └── brewicon_apple_192x192.png ├── modules │ ├── home │ │ ├── views │ │ │ └── home.html │ │ └── controllers.js │ └── authentication │ │ ├── controllers.js │ │ ├── views │ │ └── login.html │ │ └── services.js ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── js │ ├── common.js │ ├── json2xml.js │ ├── angular-hammer.js │ ├── countdown.min.js │ └── xml2json.js ├── template │ ├── tooltip │ │ ├── tooltip-popup.html │ │ ├── tooltip-html-popup.html │ │ ├── tooltip-html-unsafe-popup.html │ │ └── tooltip-template-popup.html │ ├── datepicker │ │ ├── datepicker.html │ │ ├── popup.html │ │ ├── year.html │ │ ├── month.html │ │ └── day.html │ └── timepicker │ │ └── timepicker.html ├── partials │ ├── frontpage.html │ ├── logdetailcsv.html │ ├── directives │ │ ├── wait-dlg.html │ │ ├── recipe-step.html │ │ ├── recipe.html │ │ └── log-overview.html │ ├── logs.html │ ├── logdetail.html │ ├── manual.html │ └── auto.html ├── bower.json └── index.html ├── .gitignore ├── sys ├── interfaces ├── bierbot.service └── w.sh ├── setup └── bierbot-setup-mongo.js ├── LICENSE ├── bierbot-setup.sh ├── bierbot-setup-develop.sh └── README.MD /server/.htaccess: -------------------------------------------------------------------------------- 1 | Order Deny,Allow 2 | deny from all -------------------------------------------------------------------------------- /doc/img/wiring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/wiring.png -------------------------------------------------------------------------------- /client/media/plop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/media/plop.mp3 -------------------------------------------------------------------------------- /doc/img/screen_logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/screen_logs.png -------------------------------------------------------------------------------- /doc/img/screen_manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/screen_manual.png -------------------------------------------------------------------------------- /doc/img/screen_recipes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/screen_recipes.png -------------------------------------------------------------------------------- /client/css/brew: -------------------------------------------------------------------------------- 1 | .vcenter { 2 | display: inline-block; 3 | vertical-align: middle; 4 | float: none; 5 | } -------------------------------------------------------------------------------- /doc/img/screen_automatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/screen_automatic.png -------------------------------------------------------------------------------- /doc/img/screen_automatic1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/screen_automatic1.png -------------------------------------------------------------------------------- /doc/img/screen_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/screen_settings.png -------------------------------------------------------------------------------- /client/icon/brewicon_25x25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/icon/brewicon_25x25.png -------------------------------------------------------------------------------- /client/icon/brewicon_30x30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/icon/brewicon_30x30.png -------------------------------------------------------------------------------- /client/icon/brewicon_35x35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/icon/brewicon_35x35.png -------------------------------------------------------------------------------- /client/modules/home/views/home.html: -------------------------------------------------------------------------------- 1 |

Home

2 |

You're logged in!!

3 |

Logout

-------------------------------------------------------------------------------- /doc/img/BierBot-Logo_338x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/BierBot-Logo_338x70.png -------------------------------------------------------------------------------- /doc/img/screen_logs_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/screen_logs_detail.png -------------------------------------------------------------------------------- /client/icon/brewicon_192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/icon/brewicon_192x192.png -------------------------------------------------------------------------------- /client/icon/brewicon_196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/icon/brewicon_196x196.png -------------------------------------------------------------------------------- /client/icon/brewicon_apple_72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/icon/brewicon_apple_72x72.png -------------------------------------------------------------------------------- /doc/img/screen_recipes_editstep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/screen_recipes_editstep.png -------------------------------------------------------------------------------- /doc/img/screen_settings_hardware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/doc/img/screen_settings_hardware.png -------------------------------------------------------------------------------- /server/views/icon/brewicon_25x25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/server/views/icon/brewicon_25x25.png -------------------------------------------------------------------------------- /server/views/icon/brewicon_30x30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/server/views/icon/brewicon_30x30.png -------------------------------------------------------------------------------- /server/views/icon/brewicon_35x35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/server/views/icon/brewicon_35x35.png -------------------------------------------------------------------------------- /client/icon/brewicon_apple_114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/icon/brewicon_apple_114x114.png -------------------------------------------------------------------------------- /client/icon/brewicon_apple_144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/icon/brewicon_apple_144x144.png -------------------------------------------------------------------------------- /client/icon/brewicon_apple_192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/icon/brewicon_apple_192x192.png -------------------------------------------------------------------------------- /server/views/icon/brewicon_192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/server/views/icon/brewicon_192x192.png -------------------------------------------------------------------------------- /server/views/icon/brewicon_196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/server/views/icon/brewicon_196x196.png -------------------------------------------------------------------------------- /server/views/icon/brewicon_apple_114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/server/views/icon/brewicon_apple_114x114.png -------------------------------------------------------------------------------- /server/views/icon/brewicon_apple_144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/server/views/icon/brewicon_apple_144x144.png -------------------------------------------------------------------------------- /server/views/icon/brewicon_apple_192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/server/views/icon/brewicon_apple_192x192.png -------------------------------------------------------------------------------- /server/views/icon/brewicon_apple_72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/server/views/icon/brewicon_apple_72x72.png -------------------------------------------------------------------------------- /client/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /client/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /client/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/css/font-awesome-4.5.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/css/font-awesome-4.5.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/css/font-awesome-4.5.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/css/font-awesome-4.5.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BernhardSchlegel/BierBot/HEAD/client/css/font-awesome-4.5.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /client/modules/home/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('Home') 4 | 5 | .controller('HomeController', 6 | ['$scope', 7 | function ($scope) { 8 | 9 | }]); -------------------------------------------------------------------------------- /server/libs/version-self.json: -------------------------------------------------------------------------------- 1 | { 2 | "updateFinished": false, 3 | "version": "2.0.0", 4 | "url": "https://codeload.github.com/BernhardSchlegel/BierBot/zip/master" 5 | } 6 | -------------------------------------------------------------------------------- /client/js/common.js: -------------------------------------------------------------------------------- 1 | Array.prototype.max = function() { 2 | return Math.max.apply(null, this); 3 | }; 4 | 5 | Array.prototype.min = function() { 6 | return Math.min.apply(null, this); 7 | }; -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /logs/ 2 | /server/node_modules/ 3 | /client/bower_components/ 4 | /update/ 5 | beererr.log 6 | beerlog.log 7 | server/package-lock.json 8 | personal.txt 9 | /private/ 10 | .jshintrc 11 | -------------------------------------------------------------------------------- /server/config/database.js: -------------------------------------------------------------------------------- 1 | // config/database.js 2 | module.exports = { 3 | 'url': 'mongodb://nodejsbierbot:bierbotmongo123@localhost/brewdb' 4 | // looks like mongodb://:@mongo.onmodulus.net:27017/Mikha4ot 5 | }; 6 | -------------------------------------------------------------------------------- /sys/interfaces: -------------------------------------------------------------------------------- 1 | auto lo 2 | iface lo inet loopback 3 | 4 | auto eth0 5 | iface eth0 inet dhcp 6 | 7 | auto wlan0 8 | allow-hotplug wlan0 9 | iface wlan0 inet dhcp 10 | wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf 11 | iface default inet dhcp 12 | -------------------------------------------------------------------------------- /client/template/tooltip/tooltip-popup.html: -------------------------------------------------------------------------------- 1 |
5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /client/template/tooltip/tooltip-html-popup.html: -------------------------------------------------------------------------------- 1 |
5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /client/template/tooltip/tooltip-html-unsafe-popup.html: -------------------------------------------------------------------------------- 1 |
5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /client/template/datepicker/datepicker.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
-------------------------------------------------------------------------------- /client/partials/frontpage.html: -------------------------------------------------------------------------------- 1 |

{{'HOME_H' | translate}}

2 | 3 |

4 | {{'HOME_T' | translate}} 5 |

6 | 7 |

{{'HOME_HMANUAL' | translate}}

8 |

9 | {{'HOME_TMANUAL' | translate}} 10 |

11 | 12 |

{{'HOME_HAUTO' | translate}}

13 |

14 | {{'HOME_TAUTO' | translate}} 15 |

16 | -------------------------------------------------------------------------------- /server/libs/telegram.js: -------------------------------------------------------------------------------- 1 | const TelegramBot = require('node-telegram-bot-api'); 2 | var bot; 3 | var chatId; 4 | 5 | module.exports.init = function(token, id) { 6 | bot = new TelegramBot(token, {polling: false}); 7 | chatId = id; 8 | }; 9 | 10 | module.exports.sendMessage = function(message) { 11 | bot.sendMessage(chatId, message); 12 | }; 13 | -------------------------------------------------------------------------------- /setup/bierbot-setup-mongo.js: -------------------------------------------------------------------------------- 1 | use admin 2 | db.addUser({ 3 | user: "mongoadmin", 4 | pwd: "bierbotmongo123a", 5 | roles: ["userAdminAnyDatabase"] 6 | }) 7 | use admin 8 | db.auth("mongoadmin", "bierbotmongo123a") 9 | use brewdb 10 | db.addUser({ 11 | user: "nodejsbierbot", 12 | pwd: "bierbotmongo123", 13 | roles: ["readWrite"] 14 | }) 15 | exit 16 | -------------------------------------------------------------------------------- /sys/bierbot.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=runs the BierBot server after startup 3 | After=network.target 4 | 5 | [Service] 6 | ExecStart=node /home/pi/BierBot/server/server.js 7 | WorkingDirectory=/home/pi/BierBot/server 8 | StandardOutput=inherit 9 | StandardError=inherit 10 | Restart=always 11 | User=root 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /client/template/tooltip/tooltip-template-popup.html: -------------------------------------------------------------------------------- 1 |
5 |
6 |
9 |
10 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /client/partials/logdetailcsv.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /server/app/models/hardware.js: -------------------------------------------------------------------------------- 1 | // load the things we need 2 | var mongoose = require('mongoose'); 3 | 4 | // define the schema for our hardware model 5 | var hardwareSchema = mongoose.Schema({ 6 | name: String, 7 | description: { 8 | dede: String, // description on german 9 | enen: String // description on english 10 | }, 11 | pd: { 12 | manual: Boolean, // adaptation disabled 13 | kd: Number, // parameter of pid controller 14 | kp: Number, // parameter of pid controller 15 | hysteresis: Number // controlling the hysteris before turning on again 16 | } 17 | }); 18 | 19 | // create the model for users and expose it to our app 20 | module.exports = mongoose.model('hardware', hardwareSchema); 21 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /server/libs/brewdate.js: -------------------------------------------------------------------------------- 1 | var exports = module.exports = {}; 2 | 3 | var module = {}; 4 | 5 | var useSystemDate = true; 6 | var fakeDate = new Date(); 7 | 8 | var addSecondIntervalID = setInterval(function() { 9 | fakeDate.setSeconds(fakeDate.getSeconds() + 1); 10 | }, 1000); 11 | 12 | exports.setBierBotDate = function(date) { 13 | if (date instanceof Date) { 14 | fakeDate = date; 15 | } else { 16 | fakeDate = new Date(date); 17 | } 18 | }; 19 | 20 | exports.setUsingSystemDate = function(val) { 21 | useSystemDate = val; 22 | }; 23 | 24 | // gcd = getCurrentDate 25 | exports.gcd = function() { 26 | if (useSystemDate == true) { 27 | return new Date(); 28 | } else { 29 | return fakeDate; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /client/template/datepicker/popup.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /server/app/models/user.js: -------------------------------------------------------------------------------- 1 | // load the things we need 2 | var mongoose = require('mongoose'); 3 | var bcrypt = require('bcrypt-nodejs'); 4 | 5 | // define the schema for our user model 6 | var userSchema = mongoose.Schema({ 7 | 8 | local: { 9 | username: String, 10 | password: String, 11 | } 12 | 13 | }); 14 | 15 | // generating a hash 16 | userSchema.methods.generateHash = function(password) { // old was 8 17 | return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); 18 | }; 19 | 20 | // checking if password is valid 21 | userSchema.methods.validPassword = function(password) { 22 | return bcrypt.compareSync(password, this.local.password); 23 | }; 24 | 25 | // create the model for users and expose it to our app 26 | module.exports = mongoose.model('User', userSchema); 27 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /client/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bierbot-client", 3 | "homepage": "https://github.com/BernhardSchlegel/BierBot", 4 | "authors": [ 5 | "Bernhard Schlegel " 6 | ], 7 | "description": "Frontend for the BierBot Brewing software", 8 | "main": "index.html", 9 | "keywords": [ 10 | "beer", 11 | "brewing" 12 | ], 13 | "license": "na", 14 | "private": true, 15 | "dependencies": { 16 | "angular": "1.4.4", 17 | "angular-route": "1.4.4", 18 | "angular-cookies": "1.4.4", 19 | "angular-translate": "2.7.2", 20 | "angularjs-nvd3-directives": "0.0.7", 21 | "moment": "2.8.3", 22 | "nvd3": "1.1.15-beta", 23 | "jquery": "1.11.1", 24 | "angular-google-analytics": "1.1.9", 25 | "ng-sortable": "^1.3.8" 26 | }, 27 | "resolutions": { 28 | "d3": "~3.4.1", 29 | "angular": "1.4.4" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /client/partials/directives/wait-dlg.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server/libs/defaults.js: -------------------------------------------------------------------------------- 1 | var hardwareRevision = "rev2.0"; // "rev1.0" = Model B+, "rev2.0" = Mobel 2 B 2 | var defaultSettings = { 3 | "bierBotName": "Ben", 4 | "boilingTempC": 95, 5 | "defaultSudSize": 42, 6 | "displayLastMinutesCool": 1440, 7 | "displayLastMinutesHeat": 60, 8 | "hardwareRevision": hardwareRevision, 9 | "languageKey": "de_DE", 10 | "logEveryXsTempValToDBCool": 300, 11 | "logEveryXsTempValToDBHeat": 10, 12 | "passwordActivated": false, 13 | "sendStatistics": true, 14 | "sudNumber": "2016001", 15 | "temperatureCalibration": {}, 16 | "voltsMotorCalibValue": 8, 17 | "wifiEnabled": false, 18 | "wlanPassphrase": "deinPW", 19 | "wlanSSID": "unbekanntes WLAN", 20 | "motorWarningChecked": false, 21 | "addToSensorVal": 0, 22 | "telegram": { 23 | "enabled": false, 24 | "token": "", 25 | "chatId": "" 26 | } 27 | }; 28 | exports.defaultSettings = defaultSettings; 29 | -------------------------------------------------------------------------------- /client/modules/authentication/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('Authentication') 4 | 5 | .controller('LoginController', 6 | ['$scope', '$rootScope', '$location', 'AuthenticationService', 7 | function ($scope, $rootScope, $location, AuthenticationService) { 8 | // reset login status 9 | AuthenticationService.ClearCredentials(); 10 | 11 | $scope.login = function () { 12 | $scope.dataLoading = true; 13 | AuthenticationService.Login($scope.username, $scope.password, function (response) { 14 | if (response.success) { 15 | AuthenticationService.SetCredentials($scope.username, $scope.password); 16 | $location.path('/'); 17 | } else { 18 | $scope.error = response.message; 19 | $scope.dataLoading = false; 20 | } 21 | }); 22 | }; 23 | }]); -------------------------------------------------------------------------------- /server/libs/hostname.js: -------------------------------------------------------------------------------- 1 | // hostname 2 | var exec = require('child_process').exec; 3 | var brewlog = require('./brewlog'); 4 | var hostile = require('hostile') 5 | 6 | 7 | module.exports.changeHostname = function(newHostname) { 8 | newHostname = newHostname.replace(/[^0-9a-zA-Z]/g, ""); 9 | var process = exec('echo ' + newHostname + ' | sudo tee /etc/hostname'); 10 | 11 | process.stdout.on('data', function(data) { 12 | console.log('stdout: ' + data); 13 | }); 14 | process.stderr.on('data', function(data) { 15 | console.log('stdout: ' + data); 16 | }); 17 | process.on('close', function(code, signal) { 18 | brewlog.log('etc/hostname set to ' + newHostname); 19 | }); 20 | 21 | hostile.set('127.0.1.1', newHostname, function(err) { 22 | if (err) { 23 | brewlog.log('setting etc/hosts/ failed: ' + err); 24 | } else { 25 | brewlog.log('set /etc/hosts 127.0.1.1. successfully to ' + newHostname); 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /client/partials/logs.html: -------------------------------------------------------------------------------- 1 |

{{'NAV_LOGS' | translate}}

2 | 3 |

{{'LOGS_NOLOGS' | translate}}

4 | 5 |
6 |
7 |
8 | 9 | 10 | 13 | 14 |
15 |
16 |
17 | 18 | 19 |
20 |
    21 |
  • 22 | 23 |
  • 24 |
25 |
26 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | -------------------------------------------------------------------------------- /client/css/font-awesome-4.5.0/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | -------------------------------------------------------------------------------- /server/libs/plausible.js: -------------------------------------------------------------------------------- 1 | var brewlog = require('./brewlog'); 2 | 3 | var toleranceDifference = 3; 4 | var lastPlausibleValue = null; 5 | var lastValue = null; 6 | module.exports.temp = function(value) { 7 | 8 | if (typeof(value) != "number") { 9 | //brewlog.log('temperature is no number ... returning.'); 10 | return false; 11 | } 12 | 13 | 14 | if (lastPlausibleValue == null) { 15 | lastPlausibleValue = value; 16 | } 17 | 18 | // value within toleranceDifference 19 | if (value <= (lastPlausibleValue + toleranceDifference) && 20 | value >= (lastPlausibleValue - toleranceDifference)) { 21 | 22 | lastPlausibleValue = value; 23 | lastValue = value; 24 | 25 | return true; 26 | } 27 | 28 | // two times the same value 29 | if (value == lastValue) { 30 | brewlog.log('two times same value (' + value + ')'); 31 | lastPlausibleValue = value; 32 | return true; 33 | } 34 | 35 | 36 | brewlog.log('unplausible value detected (val=' + value + ', lastVal=' + lastValue + ')'); 37 | lastValue = value; 38 | return false; 39 | }; 40 | -------------------------------------------------------------------------------- /server/views/change.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Node Authentication 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 | 15 |

16 | 17 | <% if (message.length > 0) { %> 18 |
<%= message %>
19 | <% } %> 20 | 21 | 22 |
23 | 27 |
28 | 29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 |
38 |
39 | 40 | -------------------------------------------------------------------------------- /server/app/models/appsetting.js: -------------------------------------------------------------------------------- 1 | // load the things we need 2 | var mongoose = require('mongoose'); 3 | 4 | // define the schema for our settings model 5 | var settingSchema = mongoose.Schema({ 6 | voltsMotorCalibValue: Number, 7 | sudNumber: Number, 8 | bierBotName: String, 9 | passwordActivated: Boolean, 10 | selectedHardware: mongoose.Schema.Types.ObjectId, 11 | logEveryXsTempValToDBHeat: Number, 12 | logEveryXsTempValToDBCool: Number, 13 | displayLastMinutesHeat: Number, 14 | displayLastMinutesCool: Number, 15 | manualSetTime: Boolean, 16 | manualTime: Date, 17 | languageKey: String, 18 | sendStatistics: Boolean, 19 | wifiEnabled: Boolean, 20 | wlanPassphrase: String, 21 | wlanSSID: String, 22 | hardwareRevision: String, 23 | boilingTempC: Number, // how many degrees celsius are necessary to be considered as boiling? 24 | defaultSudSize: Number, 25 | motorWarningChecked: Boolean, 26 | addToSensorVal: Number, 27 | telegram: { 28 | enabled: Boolean, 29 | token: String, 30 | chatId: String 31 | } 32 | }); 33 | 34 | // create the model for users and expose it to our app 35 | module.exports = mongoose.model('setting', settingSchema); 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Code is licensed under MIT License 2 | 3 | Copyright (c) 2014-2020 Bernhard Schlegel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | Orange Mash BierBot Logo: (c) by Bernhard Schlegel, all rights reserved. 24 | -------------------------------------------------------------------------------- /client/template/datepicker/year.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 |
12 | 13 |
17 | -------------------------------------------------------------------------------- /client/template/datepicker/month.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 |
12 | 13 |
17 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BierBot", 3 | "main": "server.js", 4 | "dependencies": { 5 | "bcrypt-nodejs": "0.0.3", 6 | "body-parser": "1.0.2", 7 | "connect": "2.28.0", 8 | "connect-busboy": "0.0.2", 9 | "connect-flash": "0.1.1", 10 | "connect-mongo": "0.5.3", 11 | "cookie": "^0.1.0", 12 | "cookie-parser": "1.0.1", 13 | "cors": "2.5.2", 14 | "cpuinfo": "0.1.0", 15 | "ds18x20": "0.2.0", 16 | "ejs": "0.8.8", 17 | "express": "4.0.0", 18 | "express-session": "1.0.4", 19 | "formidable": "1.0.15", 20 | "fs-extra": "0.12.0", 21 | "hostile": "1.0.2", 22 | "method-override": "1.0.2", 23 | "mkdirp": "0.5.1", 24 | "mongojs": "^1.4.1", 25 | "mongoose": "^4.13.20", 26 | "morgan": "1.0.1", 27 | "node-telegram-bot-api": "^0.51.0", 28 | "node-w1bus": "0.1.2", 29 | "nodemailer": "1.3.0", 30 | "passport": "0.1.18", 31 | "passport-local": "0.1.6", 32 | "passport.socketio": "3.4.1", 33 | "pigpio": "^3.0.0", 34 | "request": "^2.51.0", 35 | "setup": "0.0.3", 36 | "socket.io": "1.2.1", 37 | "uglify-js": "2.6.1", 38 | "uid-number": "0.0.6", 39 | "winston": "0.8.3", 40 | "wireless-tools": "^0.19.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/views/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Node Authentication 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 | 15 |

Login

16 | 17 | <% if (message.length > 0) { %> 18 |
<%= message %>
19 | <% } %> 20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 |

Need an account? Signup

38 |

Or go home.

39 | 40 |
41 |

A demo by Scotch.

42 |

Visit the tutorial.

43 |
44 | 45 |
46 |
47 | 48 | -------------------------------------------------------------------------------- /server/views/connect-local.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Node Authentication 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 | 15 |

Add Local Account

16 | 17 | <% if (message.length > 0) { %> 18 |
<%= message %>
19 | <% } %> 20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 |

Go back to profile

38 | 39 |
40 |

A demo by Scotch.

41 |

Visit the tutorial.

42 |
43 | 44 |
45 |
46 | 47 | -------------------------------------------------------------------------------- /server/views/profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Node Authentication 5 | 6 | 7 | 10 | 11 | 12 |
13 | 14 | 18 | 19 |
20 | 21 | 22 |
23 |
24 |

Local

25 | 26 | <% if (user.local.email) { %> 27 |

28 | id: <%= user._id %>
29 | email: <%= user.local.email %>
30 | password: <%= user.local.password %> 31 |

32 | 33 | Unlink 34 | <% } else { %> 35 | Connect Local 36 | <% } %> 37 | 38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 |
50 | 51 | -------------------------------------------------------------------------------- /server/views/signup.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Node Authentication 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 | 15 |

Signup

16 | 17 | <% if (message.length > 0) { %> 18 |
<%= message %>
19 | <% } %> 20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 |

Already have an account? Login

38 |

Or go home.

39 | 40 |
41 |

A demo by Scotch.

42 |

Visit the tutorial.

43 |
44 | 45 |
46 |
47 | 48 | -------------------------------------------------------------------------------- /server/libs/cpuid.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn, 2 | util = require('util'), 3 | events = require('events'), 4 | Module = function() { 5 | events.EventEmitter.call(this); 6 | 7 | this.update = function() { 8 | var ls = spawn('cat', ['/proc/cpuinfo']), 9 | self = this; 10 | ls.stdout.on('data', function(data) { 11 | 12 | var result = {}; 13 | var all = []; 14 | var cpuid; 15 | var i = 0; 16 | data.toString().split('\n').forEach(function(line) { 17 | 18 | line = line.replace(/\t/g, ''); 19 | 20 | var parts = line.split(':'); 21 | if (parts.length === 2) { 22 | result[parts[0].replace(/\s/g, '_')] = parts[1].trim().split(' ', 1)[0]; 23 | } 24 | 25 | if (parts[0].replace(/\s/g, '_') === 'Serial') { 26 | cpuid = parts[1].trim().split(' ', 1)[0]; 27 | } 28 | 29 | if (line.length < 1) { 30 | all.push(result); 31 | result = {}; 32 | } 33 | 34 | i = i + 1; 35 | }); 36 | all.pop(); 37 | 38 | 39 | self.emit('update', cpuid); 40 | }); 41 | 42 | ls.stderr.on('data', function(data) { 43 | console.log('stderr: ' + data); 44 | }); 45 | } 46 | 47 | return this; 48 | } 49 | 50 | util.inherits(Module, events.EventEmitter); 51 | 52 | module.exports = new Module(); 53 | -------------------------------------------------------------------------------- /client/js/json2xml.js: -------------------------------------------------------------------------------- 1 | /* This work is licensed under Creative Commons GNU LGPL License. 2 | 3 | License: http://creativecommons.org/licenses/LGPL/2.1/ 4 | Version: 0.9 5 | Author: Stefan Goessner/2006 6 | Web: http://goessner.net/ 7 | */ 8 | function json2xml(o, tab) { 9 | var toXml = function(v, name, ind) { 10 | var xml = ""; 11 | if (v instanceof Array) { 12 | for (var i=0, n=v.length; i" : "/>"; 25 | if (hasChild) { 26 | for (var m in v) { 27 | if (m == "#text") 28 | xml += v[m]; 29 | else if (m == "#cdata") 30 | xml += ""; 31 | else if (m.charAt(0) != "@") 32 | xml += toXml(v[m], m, ind+"\t"); 33 | } 34 | xml += (xml.charAt(xml.length-1)=="\n"?ind:"") + ""; 35 | } 36 | } 37 | else { 38 | xml += ind + "<" + name + ">" + v.toString() + ""; 39 | } 40 | return xml; 41 | }, xml=""; 42 | for (var m in o) 43 | xml += toXml(o[m], m, ""); 44 | return tab ? xml.replace(/\t/g, tab) : xml.replace(/\t|\n/g, ""); 45 | } 46 | -------------------------------------------------------------------------------- /client/template/datepicker/day.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 |
{{::label.abbr}}
{{ weekNumbers[$index] }} 17 | 18 |
22 | -------------------------------------------------------------------------------- /client/template/timepicker/timepicker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
 
11 | 12 | : 15 | 16 |
 
27 | -------------------------------------------------------------------------------- /client/modules/authentication/views/login.html: -------------------------------------------------------------------------------- 1 |
2 | Username: test
3 | Password: test 4 |
5 |
{{error}}
6 |
7 |
8 | 9 | 10 | 11 | Username is required 12 |
13 |
14 | 15 | 16 | 17 | Password is required 18 |
19 |
20 | 21 | 22 |
23 |
-------------------------------------------------------------------------------- /server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BierBot 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |

BierBot Login

28 | 29 | <% if (message.length > 0) { %> 30 |
<%= message %>
31 | <% } %> 32 | 33 | 34 |
35 | 39 |
40 | 41 | 42 | 43 | 44 |
45 | 46 |
47 | 48 |
49 |
50 |
51 | 52 | -------------------------------------------------------------------------------- /sys/w.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # raspi-wifi-blindscript v4 4 | # A minimium command line script to configure Wi-Fi network on Raspbian 5 | # system for Raspberry Pi(R) ARM computer. 6 | # Project HP: https://github.com/shamiao/raspi-wifi-blindscript 7 | # 8 | # Copyright (C) 2013 Sha Miao 9 | # This program is released under the MIT license, see LICENSE file or 10 | # for full text. 11 | # 12 | # See README for usage. 13 | 14 | ############################################################### 15 | #################### PLEASE EDIT THIS PART #################### 16 | ############################################################### 17 | 18 | # SSID (aka. network name). 19 | SSID=$1 20 | 21 | # Network encryption method. 22 | # * 'WPA' for WPA-PSK/WPA2-PSK (note: most Wi-Fi networks use WPA); 23 | # * 'WEP' for WEP; 24 | # * 'Open' for open network (aka. no password). 25 | ENCRYPTION=$2 26 | 27 | # Network password. (WPA-PSK/WPA2-PSK password, or WEP key) 28 | PASSWORD=$3 29 | 30 | ############################################################### 31 | #################### OK. STOP EDITING! #################### 32 | ############################################################### 33 | 34 | if [ $(id -u) -ne 0 ]; then 35 | printf "This script must be run as root. \n" 36 | exit 1 37 | fi 38 | 39 | NETID=$(wpa_cli add_network | tail -n 1) 40 | if [ $NETID -gt 0 ]; then 41 | printf 'theres already a network configured... overwriting.\n' 42 | NETID=0 43 | fi 44 | 45 | printf 'Netid is %s...\n' "$NETID" 46 | printf 'Using SSID %s...\n' "$SSID" 47 | wpa_cli set_network $NETID ssid \""$SSID"\" 48 | case $ENCRYPTION in 49 | 'WPA') 50 | wpa_cli set_network $NETID key_mgmt WPA-PSK 51 | printf 'Setting password ...\n' 52 | #printf 'Using password %s...\n' "$PASSWORD" 53 | wpa_cli set_network $NETID psk \"$PASSWORD\" 54 | ;; 55 | 'WEP') 56 | wpa_cli set_network $NETID wep_key0 $PASSWORD 57 | wpa_cli set_network $NETID wep_key1 $PASSWORD 58 | wpa_cli set_network $NETID wep_key2 $PASSWORD 59 | wpa_cli set_network $NETID wep_key3 $PASSWORD 60 | ;; 61 | *) 62 | ;; 63 | esac 64 | wpa_cli enable_network $NETID 65 | wpa_cli save_config 66 | 67 | sudo ifdown --force wlan0 68 | sudo ifup --force wlan0 69 | 70 | -------------------------------------------------------------------------------- /bierbot-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$EUID" -ne 0 ] 4 | then echo "bierbot-setup must be run as root (prefix with sudo)" 5 | exit 6 | fi 7 | 8 | echo updating package lists... 9 | sudo apt-get update -y 10 | 11 | echo installing mongodb... 12 | sudo apt-get install mongodb-server -y 13 | 14 | echo starting mongodb service... 15 | sudo service mongodb start 16 | 17 | echo installing NodeJS... 18 | curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - 19 | sudo apt-get install -y nodejs 20 | 21 | echo installing git... 22 | sudo apt-get install git -y 23 | 24 | echo cloning repo into /home/pi/BierBot 25 | cd /home/pi 26 | git clone --depth=1 https://github.com/BernhardSchlegel/BierBot.git 27 | 28 | echo installing bower... 29 | sudo npm install -g bower 30 | 31 | echo installing gpio interface... 32 | sudo apt-get install pigpio -y 33 | 34 | echo chmodding rights of wireless tools and BierBot directory 35 | sudo chmod -R u+x /home/pi/BierBot/sys 36 | sudo chown -R 1000:0 /home/pi/BierBot/ 37 | sudo chmod -R u+w /home/pi/BierBot/ 38 | 39 | echo setting up users in mongodb 40 | mongo < /home/pi/BierBot/setup/bierbot-setup-mongo.js 41 | 42 | echo creating autostart... 43 | sudo cp /home/pi/BierBot/sys/bierbot.service /etc/systemd/system/bierbot.service 44 | sudo systemctl enable bierbot.service 45 | 46 | #echo modifying network settings to enable wifi 47 | #sudo tee /etc/network/interfaces < /home/pi/BierBot/sys/interfaces 48 | 49 | echo installing dependencies for backend... 50 | cd /home/pi/BierBot/server 51 | sudo -u pi npm install 52 | 53 | echo inizializing backend... 54 | cd /home/pi/BierBot/server 55 | sudo node setup.js 56 | 57 | echo installing dependencies for frontend... 58 | cd /home/pi/BierBot/client 59 | sudo -u pi bower install 60 | 61 | echo trying to bring wlan0 interface up. 62 | sudo ifconfig wlan0 up 63 | echo an error above is OK when you dont have an wifi adapter 64 | 65 | echo enabling 1-wire protocol 66 | if ! grep -q dtoverlay=w1-gpio /boot/config.txt; then echo dtoverlay=w1-gpio,gpiopin=4,pullup=on | sudo tee -a /boot/config.txt > /dev/null; fi 67 | if ! grep -q w1-gpio /etc/modules; then echo w1-gpio | sudo tee -a /etc/modules > /dev/null; fi 68 | if ! grep -q w1-therm /etc/modules; then echo w1-therm | sudo tee -a /etc/modules > /dev/null; fi 69 | 70 | echo You may want to restart now: sudo reboot now 71 | echo BierBot says: gut Sud! 72 | -------------------------------------------------------------------------------- /bierbot-setup-develop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$EUID" -ne 0 ] 4 | then echo "bierbot-setup must be run as root (prefix with sudo)" 5 | exit 6 | fi 7 | 8 | echo updating package lists... 9 | sudo apt-get update -y 10 | 11 | echo installing mongodb... 12 | sudo apt-get install mongodb-server -y 13 | 14 | echo starting mongodb service... 15 | sudo service mongodb start 16 | 17 | echo installing NodeJS... 18 | curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - 19 | sudo apt-get install -y nodejs 20 | 21 | echo installing git... 22 | sudo apt-get install git -y 23 | 24 | echo cloning repo into /home/pi/BierBot 25 | cd /home/pi 26 | git clone --depth=1 https://github.com/BernhardSchlegel/BierBot.git 27 | cd /home/pi/BierBot 28 | git checkout develop 29 | cd /home/pi 30 | 31 | echo installing bower... 32 | sudo npm install -g bower 33 | 34 | echo installing gpio interface... 35 | sudo apt-get install pigpio -y 36 | 37 | echo chmodding rights of wireless tools and BierBot directory 38 | sudo chmod -R u+x /home/pi/BierBot/sys 39 | sudo chown -R 1000:0 /home/pi/BierBot/ 40 | sudo chmod -R u+w /home/pi/BierBot/ 41 | 42 | echo setting up users in mongodb 43 | mongo < /home/pi/BierBot/setup/bierbot-setup-mongo.js 44 | 45 | echo creating autostart... 46 | sudo cp /home/pi/BierBot/sys/bierbot.service /etc/systemd/system/bierbot.service 47 | sudo systemctl enable bierbot.service 48 | 49 | #echo modifying network settings to enable wifi 50 | #sudo tee /etc/network/interfaces < /home/pi/BierBot/sys/interfaces 51 | 52 | echo installing dependencies for backend... 53 | cd /home/pi/BierBot/server 54 | sudo -u pi npm install 55 | 56 | echo inizializing backend... 57 | cd /home/pi/BierBot/server 58 | sudo node setup.js 59 | 60 | echo installing dependencies for frontend... 61 | cd /home/pi/BierBot/client 62 | sudo -u pi bower install 63 | 64 | echo trying to bring wlan0 interface up. 65 | sudo ifconfig wlan0 up 66 | echo an error above is OK when you dont have an wifi adapter 67 | 68 | echo enabling 1-wire protocol 69 | if ! grep -q dtoverlay=w1-gpio /boot/config.txt; then echo dtoverlay=w1-gpio,gpiopin=4,pullup=on | sudo tee -a /boot/config.txt > /dev/null; fi 70 | if ! grep -q w1-gpio /etc/modules; then echo w1-gpio | sudo tee -a /etc/modules > /dev/null; fi 71 | if ! grep -q w1-therm /etc/modules; then echo w1-therm | sudo tee -a /etc/modules > /dev/null; fi 72 | 73 | echo You may want to restart now: sudo reboot now 74 | echo BierBot says: gut Sud! 75 | -------------------------------------------------------------------------------- /client/partials/directives/recipe-step.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

{{ step.targetTemperature + '°C'}}

6 |
7 |
8 |
9 |
10 | {{step.name}} 11 |
12 |
13 | 14 | {{'MANUAL' | translate}} 15 | 16 | 17 | 18 | {{'TIME' | translate}} {{ Math.floor(step.timeLimit/60) + ':'}}{{step.timeLimit%60 | numberFixedLen:2}}h 19 | 20 | 21 | 22 | {{'STIRRING' | translate}} 23 | 24 | 25 |
26 |
27 |
28 |
29 | 32 | 33 | 36 | 37 | 40 |
41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /server/setup.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | function patchMongo(path) { 4 | return new Promise(resolve => { 5 | // https://github.com/mongo-js/mongojs/issues/300 6 | console.info("trying to patch " + path); 7 | var strToFind = "Proxy\.create\\("; 8 | var regexToFind = new RegExp(strToFind, 'g'); 9 | var strToInject = "new Proxy({}, "; 10 | 11 | fs.readFile(path, 'utf8', function(err, data) { 12 | if (err) { 13 | console.log(err); 14 | return; 15 | } 16 | 17 | console.info("mongojs/index.js ready for patching"); 18 | 19 | var result = data.replace(regexToFind, strToInject); 20 | 21 | fs.writeFile(path, result, 'utf8', function(err) { 22 | if (err) { 23 | console.log(err); 24 | resolve('err'); 25 | return; 26 | } else { 27 | console.info("node_modules patched."); 28 | resolve('done'); 29 | } 30 | }); 31 | }); 32 | }); 33 | } 34 | 35 | function createUser() { 36 | return new Promise(resolve => { 37 | var configDB = require('./config/database.js'); 38 | var mongoose = require('mongoose'); 39 | mongoose.connect(configDB.url); // connect to our database 40 | var User = require('./app/models/user'); 41 | 42 | var usr = new User({ 43 | local: { 44 | username: "bierbot", 45 | password: "password", 46 | } 47 | }); 48 | usr.local.password = usr.generateHash("bierbot123"); 49 | 50 | usr.save(function(err) { 51 | if (err) { 52 | console.error("user creation failed: " + err); 53 | resolve('err'); 54 | } else { 55 | console.info("user successfully created"); 56 | resolve('done'); 57 | } 58 | }); 59 | }); 60 | } 61 | 62 | async function setup() { 63 | console.log('setup started'); 64 | 65 | console.info("patching mongo..."); // do this to enable user creation 66 | await patchMongo(__dirname + '/node_modules/mongojs/index.js'); 67 | 68 | console.info("initializing user..."); 69 | await createUser(); 70 | 71 | console.info("prefilling database..."); 72 | // the following import will fail before patch 73 | var restoreclear = require('./libs/restoreclear.js'); 74 | restoreclear.restoreFactorySettings(); 75 | 76 | console.info("setup completed."); 77 | } 78 | 79 | setup(); 80 | -------------------------------------------------------------------------------- /server/views/reset.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BierBot Reset 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 | 30 | <% 31 | setTimeout(function() { 32 | step = 1; 33 | }, timeout); 34 | %> 35 | 36 |

Reset

37 | 38 | 39 |
<%= warn %>
40 | 41 | 42 | <% if (step == 1) { %> 43 |

<%= manual %>

44 |
    45 |
  1. <%= step1 %>
  2. 46 |
  3. <%= step2 %>
  4. 47 |
  5. <%= step3 %>
  6. 48 |
49 | 50 |
51 | 52 |
53 | <% } %> 54 | 55 | 56 | <% if (step == 2) { %> 57 |

<%= s2Desc %>

58 | 59 | 64 | <% } %> 65 | 66 | 67 | <% if (step == 3) { %> 68 |
69 |
70 | 71 | 72 |
73 | 74 |
75 | 76 | 81 | <% } %> 82 | 83 | 84 | <% if (warnMsg != null) { %> 85 |

<%= warnMsg %>

86 | 91 | <% } %> 92 |
93 | 94 |
95 |
96 | 97 | 98 | -------------------------------------------------------------------------------- /server/libs/brewlog.js: -------------------------------------------------------------------------------- 1 | // brewlog.js 2 | var winston = require('winston'); 3 | 4 | // winston.add(winston.transports.Console); // is added by default 5 | winston.remove(winston.transports.Console); 6 | // we treat the console logging on our own 7 | // winston.add(winston.transports.Console, { 8 | // level: 'info' 9 | // }) 10 | winston.add(winston.transports.DailyRotateFile, { 11 | level: 'info', 12 | filename: __dirname + '/../../logs/bierbot.log', 13 | datePattern: '.yyyy-MM-dd' 14 | }); 15 | 16 | 17 | var exports = module.exports = {}; 18 | 19 | var curentLogFilePath = null; 20 | 21 | var brewdate = null; 22 | 23 | // returns date in ms 24 | var getNowDate = function() { 25 | var now = new Date(); 26 | if (brewdate != null) 27 | now = brewdate.gcd(); 28 | 29 | return new Date(now.getTime() + now.getTimezoneOffset()); 30 | } 31 | 32 | // logs a string to the console 33 | exports.log = function(str) { 34 | winston.info(str); 35 | var currentdate = getNowDate(); 36 | 37 | var datestr = ("0" + (currentdate.getDate())).slice(-2) + "." + 38 | ("0" + (currentdate.getMonth() + 1)).slice(-2) + "." + 39 | currentdate.getFullYear() + ", " + 40 | ("0" + currentdate.getHours()).slice(-2) + ":" + 41 | ("0" + currentdate.getMinutes()).slice(-2) + ":" + 42 | ("0" + currentdate.getSeconds()).slice(-2); 43 | 44 | console.log(datestr + ' - ' + str); 45 | }; 46 | 47 | exports.debug = function(str) { 48 | winston.info(str); 49 | var currentdate = getNowDate(); 50 | 51 | var datestr = ("0" + (currentdate.getDate())).slice(-2) + "." + 52 | ("0" + (currentdate.getMonth() + 1)).slice(-2) + "." + 53 | currentdate.getFullYear() + ", " + 54 | ("0" + currentdate.getHours()).slice(-2) + ":" + 55 | ("0" + currentdate.getMinutes()).slice(-2) + ":" + 56 | ("0" + currentdate.getSeconds()).slice(-2); 57 | 58 | console.log(datestr + ' - ' + str); 59 | } 60 | 61 | exports.getPathToCurrentLogfile = function(subtractDays) { 62 | var now = getNowDate(); 63 | 64 | now.setDate(now.getDate() - subtractDays); 65 | 66 | var str = now.getFullYear() + "-" + 67 | ("0" + (now.getMonth() + 1)).slice(-2) + "-" + 68 | ("0" + (now.getDate())).slice(-2); 69 | return __dirname + '/../../logs/bierbot.log.' + str; 70 | } 71 | 72 | exports.setBrewDateAsync = function(bd) { 73 | // yeah I know... much hazzle for one but it was late... 74 | str = 'setting brewdate in brewlog module...'; 75 | winston.info(str); 76 | var currentdate = getNowDate(); 77 | 78 | var datestr = ("0" + (currentdate.getDate())).slice(-2) + "." + 79 | ("0" + (currentdate.getMonth() + 1)).slice(-2) + "." + 80 | currentdate.getFullYear() + ", " + 81 | ("0" + currentdate.getHours()).slice(-2) + ":" + 82 | ("0" + currentdate.getMinutes()).slice(-2) + ":" + 83 | ("0" + currentdate.getSeconds()).slice(-2); 84 | 85 | console.log(datestr + ' - ' + str); 86 | 87 | brewdate = bd; 88 | } 89 | -------------------------------------------------------------------------------- /client/js/angular-hammer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * angular-hammer v1.2.4 3 | * (c) 2013 Monospaced http://monospaced.com 4 | * License: MIT 5 | */ 6 | 7 | (function(window, angular, Hammer){ 8 | 9 | var hmTouchEvents = angular.module('hmTouchEvents', []), 10 | hmGestures = ['hmHold:hold', 11 | 'hmTap:tap', 12 | 'hmPress:press', 13 | 'hmDoubletap:doubletap', 14 | 'hmDrag:drag', 15 | 'hmDragstart:dragstart', 16 | 'hmDragend:dragend', 17 | 'hmDragup:dragup', 18 | 'hmDragdown:dragdown', 19 | 'hmDragleft:dragleft', 20 | 'hmDragright:dragright', 21 | 'hmSwipe:swipe', 22 | 'hmSwipeup:swipeup', 23 | 'hmSwipedown:swipedown', 24 | 'hmSwipeleft:swipeleft', 25 | 'hmSwiperight:swiperight', 26 | 'hmTransform:transform', 27 | 'hmTransformstart:transformstart', 28 | 'hmTransformend:transformend', 29 | 'hmRotate:rotate', 30 | 'hmPinch:pinch', 31 | 'hmPinchin:pinchin', 32 | 'hmPinchout:pinchout', 33 | 'hmTouch:touch', 34 | 'hmRelease:release']; 35 | 36 | angular.forEach(hmGestures, function(name){ 37 | var directive = name.split(':'), 38 | directiveName = directive[0], 39 | eventName = directive[1]; 40 | 41 | hmTouchEvents.directive(directiveName, ['$parse', '$window', function($parse, $window){ 42 | return { 43 | restrict: 'A, C', 44 | link: function(scope, element, attr) { 45 | var expr = $parse(attr[directiveName]), 46 | fn = function(event){ 47 | scope.$apply(function() { 48 | expr(scope, {$event: event}); 49 | }); 50 | }, 51 | opts = $parse(attr['hmOptions'])(scope, {}), 52 | hammer; 53 | 54 | if (typeof Hammer === 'undefined' || !$window.addEventListener) { 55 | // fallback to mouse events where appropriate 56 | if (directiveName === 'hmTap') { 57 | element.bind('click', fn); 58 | } 59 | if (directiveName === 'hmDoubletap') { 60 | element.bind('dblclick', fn); 61 | } 62 | return; 63 | } 64 | 65 | // don't create multiple Hammer instances per element 66 | if (!(hammer = element.data('hammer'))) { 67 | hammer = Hammer(element[0], opts); 68 | element.data('hammer', hammer); 69 | } 70 | 71 | // bind Hammer touch event 72 | hammer.on(eventName, fn); 73 | 74 | // unbind Hammer touch event 75 | scope.$on('$destroy', function(){ 76 | hammer.off(eventName, fn); 77 | }); 78 | 79 | } 80 | }; 81 | }]); 82 | }); 83 | 84 | })(window, window.angular, window.Hammer); 85 | -------------------------------------------------------------------------------- /client/partials/directives/recipe.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{recipe.name}} 5 | 6 | 7 |

8 | 11 |
12 | 13 |
14 |

{{recipe.description}}

15 | 16 |
17 |
18 | 19 | {{'BREW' | translate}} 20 | 21 | 24 | 27 | 30 | 31 | 32 | 33 | {{'EXPORT' | translate}} 34 | 35 | 38 |
39 |
40 | 41 |
    42 |
  • 43 | 44 |
  • 45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /doc/SETUP_MANUAL.MD: -------------------------------------------------------------------------------- 1 | ## Manual setup 2 | 3 | This information is not kept up-to-date since it was replaced by the automated 4 | install-script. 5 | 6 | 1. Get yourself an SSH client (such as [MobaXXterm](https://mobaxterm.mobatek.net/)) 7 | and connect to your RaspberryPi using the IP-Address as shown by your router 8 | (in my case e.g. `192.168.178.194`). 9 | - Update it to most current version by running `sudo apt-get update` and `sudo apt-get dist-upgrade`. 10 | - In the following process we'll be using `nano` quite often, some basic commands 11 | are: ctrl+c to exit, and `Y` for saving 12 | 13 | - First, we'll install MongoDB which is used to store logs, recipes, etc.: 14 | `sudo apt-get install mongodb-server` (confirm all that pops up with `Y`). 15 | - To start mongo db as a service (which is required), type 16 | `sudo service mongodb start`. 17 | - To install NodeJS, run `curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -` (yep, the dash belongs there) 18 | followed by `sudo apt-get install -y nodejs`. 19 | - Check your install with `node -v`, this should yield something like `v10.XX.X` (e.g. `v10.19.0`). 20 | - npm should already be installed, check with `npm --version`, which shoud yield 21 | something like `6.X.X` (e.g. `6.4.1` or `6.13.4`) 22 | - Install git `sudo apt-get install git -y` (the lite version of Raspbian is lite, indeed). 23 | - install `pigpio` using `sudo apt-get install pigpio`. 24 | - clone the BierBot github repository to your home directory 25 | ``` 26 | cd ~ 27 | git clone https://github.com/BernhardSchlegel/BierBot.git 28 | ``` 29 | - update / chmod rights of wireless tools 30 | ``` 31 | sudo chmod -R u+x ~/BierBot/sys 32 | sudo chown -R 1000:0 ~/BierBot/ 33 | sudo chmod -R u+w ~/BierBot/ 34 | ``` 35 | - start mongo bash `mongo` 36 | - in mongo bash paste commands to create database users 37 | ``` 38 | use admin 39 | db.addUser({ user: "mongoadmin", pwd: "bierbotmongo123a", roles: ["userAdminAnyDatabase"] }) 40 | use admin 41 | db.auth("mongoadmin", "bierbotmongo123a") 42 | use brewdb 43 | db.addUser({ user: "nodejsbierbot", pwd: "bierbotmongo123", roles: ["readWrite"] }) 44 | exit 45 | ``` 46 | - edit the config from mongo db with nano editor `sudo nano /etc/mongodb.conf` and 47 | change the lines (to make the MongoDB accessible over Ethernet). **Warning** 48 | the combination of bind_ip and the default password from above is not 49 | recommended as long term solution. Also: Do not include the brackets and 50 | anything in between. 51 | ``` 52 | # bind_ip = 127.0.0.1 (add the #) 53 | port = 27017 (remove the #) 54 | auth = true (remove the #) 55 | ``` 56 | - Restart MongoDB `sudo service mongodb restart`. 57 | - Add autostart for BierBot NodeJS server, run `sudo nano /etc/init.d/beernode` 58 | and paste the contents of the file `BierBot/utils/beernode` 59 | - Set rights for file `sudo chmod +x /etc/init.d/beernode` 60 | - Update the autostart configuration `sudo update-rc.d beernode defaults` 61 | - This is only necessary, if you did not use `raspi-config` 62 | - Enable w1 temperature sensor, open config.txt `sudo nano /boot/config.txt` 63 | - add the following line to the end of file `dtoverlay=w1-gpio,gpiopin=4` 64 | - for enabling wifi, open the interface configuration file `sudo nano /etc/network/interfaces` 65 | - paste contents of `BierBot/utils/interfaces`, comment everything else (`#` 66 | at the beginning of the line). 67 | - navigate into the `/server` (most likely `cd /home/pi/BierBot/server/`) 68 | directory and run `sudo npm install` to install all dependencies 69 | - install bower package manager `sudo npm install -g bower` (folder you're in 70 | does not matter). 71 | - navigate to the `/client` directory and run `bower install`. 72 | -------------------------------------------------------------------------------- /server/libs/common.js: -------------------------------------------------------------------------------- 1 | var brewlog = require('./brewlog'); 2 | var brewdate = require('./brewdate'); 3 | var hostname = require('./hostname'); 4 | var configDB = require('../config/database.js'); 5 | var mongoose = require('mongoose'); 6 | mongoose.connect(configDB.url); // connect to our database 7 | var Hardware = require('../app/models/hardware'); 8 | var Setting = require('../app/models/appsetting'); 9 | var collections = ["recipes"] 10 | const mongojs = require("mongojs"); 11 | var db = mongojs(configDB.url, collections); 12 | 13 | var addRecipe = function(recipe, callback) { 14 | db.recipes.save(recipe, function(err, saveRecipe) { 15 | if (err || !saveRecipe) { 16 | brewlog.log('recipe not inserted, error: ' + err); 17 | callback('recipe not inserted, error: ' + err, null); 18 | } else { 19 | brewlog.log('recipe ' + saveRecipe.name + ' (id: ' + saveRecipe._id + ') saved'); 20 | callback(null, saveRecipe); // validate, is there a _id? 21 | } 22 | }); 23 | } 24 | 25 | var updateAppSettings = function(settings, callback) { 26 | // create object if it is a schema 27 | if (settings.toObject) { 28 | settings = settings.toObject(); 29 | } 30 | 31 | // get the _id since _id cannot be replaced in mongoDB 32 | var id = settings._id 33 | 34 | // remove from old object 35 | delete settings._id; 36 | delete settings.__v; 37 | 38 | // Do the upsert, which works like this: If no Contact document exists with 39 | // _id = contact.id, then create a new doc using upsertData. 40 | // Otherwise, update the existing doc with upsertData 41 | Setting.update({ 42 | _id: id 43 | }, settings, { 44 | upsert: true 45 | }, function(err, report) { 46 | // report looks like {"ok":1,"n":1,"nModified":1} 47 | if (err) { 48 | brewlog.log('failed to update settings: ' + err); 49 | callback(err); 50 | } else { 51 | hostname.changeHostname('BierBot ' + settings.bierBotName); 52 | 53 | callback(null, settings); 54 | } 55 | }); 56 | 57 | }; 58 | 59 | // callback (err, hardware) 60 | var upsertHardware = function(hardware, callback) { 61 | var upsertVal = false; 62 | if (hardware._id == null) { 63 | brewlog.log('generating new id for (' + hardware.name + ')'); 64 | // if there is no ID, reassign it 65 | var newHardware = new Hardware({}); 66 | // assign hardware has the _v and _id fields... 67 | for (var k in hardware) newHardware[k] = hardware[k]; 68 | // has to be done this way cause methods are not copied 69 | hardware = newHardware.toObject(); 70 | 71 | upsertVal = true; 72 | } 73 | 74 | // get the _id since _id cannot be replaced in mongoDB 75 | var id = hardware._id; 76 | 77 | // remove from old object 78 | delete hardware._id; 79 | 80 | // Do the upsert, which works like this: If no Contact document exists with 81 | // _id = contact.id, then create a new doc using upsertData. 82 | // Otherwise, update the existing doc with upsertData 83 | Hardware.update({ 84 | _id: id 85 | }, hardware, { 86 | upsert: upsertVal 87 | }, function(err, numberAffected) { 88 | if (err) { 89 | brewlog.log('failed to update hardware: ' + err); 90 | callback(err); 91 | } else { 92 | hardware._id = id; // reassign 93 | brewlog.log('updated hardware (' + hardware.name + ')'); 94 | callback(null, hardware); 95 | } 96 | }); 97 | } 98 | 99 | exports.upsertHardware = upsertHardware; 100 | exports.updateAppSettings = updateAppSettings; 101 | exports.addRecipe = addRecipe; 102 | -------------------------------------------------------------------------------- /client/partials/directives/log-overview.html: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 |
26 | {{'SUDNUMBER' | translate}}{{log.sudNumber}}
31 | {{'LOGS_AMOUNT' | translate}} 32 | {{log.sudSizeLitres + 'l'}} 34 |
{{'LOGS_FINISHED' | translate}}{{log.finished | date : ' dd.MM.yyyy, HH:mm'}}
{{'LOGS_DURATION' | translate}}{{duration}}
{{'LOGS_MODE' | translate}} 47 |
48 |
49 |
53 | 54 | 55 | 56 | 57 | 74 | -------------------------------------------------------------------------------- /client/partials/logdetail.html: -------------------------------------------------------------------------------- 1 |
2 | 10 | 11 |

{{log.started | date : ' (dd.MM.yyyy)'}}

12 |

{{'LOGDETAIL_OVERVIEW' | translate}}

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 |
{{'SUDNUMBER' | translate}}{{log.sudNumber}}
{{'LOGDETAIL_PROCESSSTARTED' | translate}}{{log.started | date : 'dd.MM.yyyy HH:mm:ss' }}
{{'LOGDETAIL_PROCESSFINISHED' | translate}}{{log.finished | date : 'dd.MM.yyyy HH:mm:ss' }}
{{'LOGDETAIL_DURATION' | translate}}{{duration}}
{{'LOGDETAIL_SUDSIZE' | translate}}{{log.sudSizeLitres}}l
{{'' | translate}}Modus 40 |
41 |
42 |
46 | 47 |

{{'TEMPERATUREHISTORY' | translate}}

48 |
49 | 50 | 51 |
52 | 53 |

{{'LOGDETAIL_RECIPESTEPS' | translate}}

54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
#{{'LOGDETAIL_NAME' | translate}}{{'LOGDETAIL_START' | translate}}{{'LOGDETAIL_TEMPREACHED' | translate}}{{'LOGDETAIL_DURATIONREACH' | translate}}{{'LOGDETAIL_DURATIONHOLD' | translate}}ΔT (C/min)Ziel (°C)Min (°C)Max (°C)
{{$index}}{{step.name}}{{step.started | date : 'HH:mm:ss'}}{{step.tempReached | date : 'HH:mm:ss'}}{{getStepDuration($index,'change')}}{{getStepDuration($index,'hold')}}{{getDeltaT($index) | number : 3}}{{getStepTargetTemp($index) | number : 1}}{{getStepMinTemp($index) | number : 1}}{{getStepMaxTemp($index) | number : 1}}{{getStepAvgTemp($index) | number : 1}}
86 | 87 |

{{'LOGDETAIL_COMMENTS' | translate}}

88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /client/partials/manual.html: -------------------------------------------------------------------------------- 1 |

{{'NAV_MANUAL' | translate}}

2 | 3 |
4 | 5 |
6 | {{ 'MANUAL_ERROR_1' | translate}} 7 | Automatik 8 | {{ 'MANUAL_ERROR_2' | translate}} 9 |
10 | 11 |
12 |
13 |
14 |
15 |

16 | {{ 'MANUAL_CONTROLS' | translate }} 17 |

18 |
19 |
20 |
21 |
{{ 'MODE' | translate}}
22 |
23 |
24 | 27 | 30 |
31 |
32 |
33 |
34 |
{{ 'STIRR' | translate}}
35 |
36 |
37 | 40 | 43 |
44 | 45 | 46 | 47 |
48 |
49 |
50 |
{{ 'TTEMP' | translate}}
51 |
52 |
53 |
54 | 55 | °C 56 | 57 | 58 | 59 |
60 | 61 |
62 |
63 |
64 |
{{ 'MANUAL_STOPSWITCH' | translate}}
65 |
66 | 69 |
70 |
71 |
72 |
73 |
74 |
75 | 76 | 77 |
78 |
79 |
80 |
81 |

82 | {{ 'TEMPERATUREHISTORY' | translate }} 83 |

84 |
85 |
86 |
87 | 88 | 89 |
90 |
91 |
92 |
93 |
94 |
95 | -------------------------------------------------------------------------------- /client/modules/authentication/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('Authentication') 4 | 5 | .factory('AuthenticationService', 6 | ['Base64', '$http', '$cookieStore', '$rootScope', '$timeout', 7 | function (Base64, $http, $cookieStore, $rootScope, $timeout) { 8 | var service = {}; 9 | 10 | service.Login = function (username, password, callback) { 11 | 12 | /* Dummy authentication for testing, uses $timeout to simulate api call 13 | ----------------------------------------------*/ 14 | $timeout(function () { 15 | var response = { success: username === 'test' && password === 'test' }; 16 | if (!response.success) { 17 | response.message = 'Username or password is incorrect'; 18 | } 19 | callback(response); 20 | }, 1000); 21 | 22 | 23 | /* Use this for real authentication 24 | ----------------------------------------------*/ 25 | //$http.post('/api/authenticate', { username: username, password: password }) 26 | // .success(function (response) { 27 | // callback(response); 28 | // }); 29 | 30 | }; 31 | 32 | service.SetCredentials = function (username, password) { 33 | var authdata = Base64.encode(username + ':' + password); 34 | 35 | $rootScope.globals = { 36 | currentUser: { 37 | username: username, 38 | authdata: authdata 39 | } 40 | }; 41 | 42 | $http.defaults.headers.common['Authorization'] = 'Basic ' + authdata; // jshint ignore:line 43 | $cookieStore.put('globals', $rootScope.globals); 44 | }; 45 | 46 | service.ClearCredentials = function () { 47 | $rootScope.globals = {}; 48 | $cookieStore.remove('globals'); 49 | $http.defaults.headers.common.Authorization = 'Basic '; 50 | }; 51 | 52 | return service; 53 | }]) 54 | 55 | .factory('Base64', function () { 56 | /* jshint ignore:start */ 57 | 58 | var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 59 | 60 | return { 61 | encode: function (input) { 62 | var output = ""; 63 | var chr1, chr2, chr3 = ""; 64 | var enc1, enc2, enc3, enc4 = ""; 65 | var i = 0; 66 | 67 | do { 68 | chr1 = input.charCodeAt(i++); 69 | chr2 = input.charCodeAt(i++); 70 | chr3 = input.charCodeAt(i++); 71 | 72 | enc1 = chr1 >> 2; 73 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 74 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 75 | enc4 = chr3 & 63; 76 | 77 | if (isNaN(chr2)) { 78 | enc3 = enc4 = 64; 79 | } else if (isNaN(chr3)) { 80 | enc4 = 64; 81 | } 82 | 83 | output = output + 84 | keyStr.charAt(enc1) + 85 | keyStr.charAt(enc2) + 86 | keyStr.charAt(enc3) + 87 | keyStr.charAt(enc4); 88 | chr1 = chr2 = chr3 = ""; 89 | enc1 = enc2 = enc3 = enc4 = ""; 90 | } while (i < input.length); 91 | 92 | return output; 93 | }, 94 | 95 | decode: function (input) { 96 | var output = ""; 97 | var chr1, chr2, chr3 = ""; 98 | var enc1, enc2, enc3, enc4 = ""; 99 | var i = 0; 100 | 101 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 102 | var base64test = /[^A-Za-z0-9\+\/\=]/g; 103 | if (base64test.exec(input)) { 104 | window.alert("There were invalid base64 characters in the input text.\n" + 105 | "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + 106 | "Expect errors in decoding."); 107 | } 108 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 109 | 110 | do { 111 | enc1 = keyStr.indexOf(input.charAt(i++)); 112 | enc2 = keyStr.indexOf(input.charAt(i++)); 113 | enc3 = keyStr.indexOf(input.charAt(i++)); 114 | enc4 = keyStr.indexOf(input.charAt(i++)); 115 | 116 | chr1 = (enc1 << 2) | (enc2 >> 4); 117 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 118 | chr3 = ((enc3 & 3) << 6) | enc4; 119 | 120 | output = output + String.fromCharCode(chr1); 121 | 122 | if (enc3 != 64) { 123 | output = output + String.fromCharCode(chr2); 124 | } 125 | if (enc4 != 64) { 126 | output = output + String.fromCharCode(chr3); 127 | } 128 | 129 | chr1 = chr2 = chr3 = ""; 130 | enc1 = enc2 = enc3 = enc4 = ""; 131 | 132 | } while (i < input.length); 133 | 134 | return output; 135 | } 136 | }; 137 | 138 | /* jshint ignore:end */ 139 | }); -------------------------------------------------------------------------------- /client/js/countdown.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | countdown.js v2.3.4 http://countdownjs.org 3 | Copyright (c)2006-2012 Stephen M. McKamey. 4 | Licensed under The MIT License. 5 | */ 6 | var module,countdown=function(r){function v(a,b){var c=a.getTime();a.setUTCMonth(a.getUTCMonth()+b);return Math.round((a.getTime()-c)/864E5)}function t(a){var b=a.getTime(),c=new Date(b);c.setUTCMonth(a.getUTCMonth()+1);return Math.round((c.getTime()-b)/864E5)}function f(a,b){return a+" "+(1===a?p[b]:q[b])}function n(){}function l(a,b,c,g,x,d){0<=a[c]&&(b+=a[c],delete a[c]);b/=x;if(1>=b+1)return 0;if(0<=a[g]){a[g]=+(a[g]+b).toFixed(d);switch(g){case "seconds":if(60!==a.seconds||isNaN(a.minutes))break; 7 | a.minutes++;a.seconds=0;case "minutes":if(60!==a.minutes||isNaN(a.hours))break;a.hours++;a.minutes=0;case "hours":if(24!==a.hours||isNaN(a.days))break;a.days++;a.hours=0;case "days":if(7!==a.days||isNaN(a.weeks))break;a.weeks++;a.days=0;case "weeks":if(a.weeks!==t(a.refMonth)/7||isNaN(a.months))break;a.months++;a.weeks=0;case "months":if(12!==a.months||isNaN(a.years))break;a.years++;a.months=0;case "years":if(10!==a.years||isNaN(a.decades))break;a.decades++;a.years=0;case "decades":if(10!==a.decades|| 8 | isNaN(a.centuries))break;a.centuries++;a.decades=0;case "centuries":if(10!==a.centuries||isNaN(a.millennia))break;a.millennia++;a.centuries=0}return 0}return b}function w(a,b,c,g,d,k){a.start=b;a.end=c;a.units=g;a.value=c.getTime()-b.getTime();if(0>a.value){var f=c;c=b;b=f}a.refMonth=new Date(b.getFullYear(),b.getMonth(),15);try{a.millennia=0;a.centuries=0;a.decades=0;a.years=c.getUTCFullYear()-b.getUTCFullYear();a.months=c.getUTCMonth()-b.getUTCMonth();a.weeks=0;a.days=c.getUTCDate()-b.getUTCDate(); 9 | a.hours=c.getUTCHours()-b.getUTCHours();a.minutes=c.getUTCMinutes()-b.getUTCMinutes();a.seconds=c.getUTCSeconds()-b.getUTCSeconds();a.milliseconds=c.getUTCMilliseconds()-b.getUTCMilliseconds();var h;0>a.milliseconds?(h=s(-a.milliseconds/1E3),a.seconds-=h,a.milliseconds+=1E3*h):1E3<=a.milliseconds&&(a.seconds+=m(a.milliseconds/1E3),a.milliseconds%=1E3);0>a.seconds?(h=s(-a.seconds/60),a.minutes-=h,a.seconds+=60*h):60<=a.seconds&&(a.minutes+=m(a.seconds/60),a.seconds%=60);0>a.minutes?(h=s(-a.minutes/ 10 | 60),a.hours-=h,a.minutes+=60*h):60<=a.minutes&&(a.hours+=m(a.minutes/60),a.minutes%=60);0>a.hours?(h=s(-a.hours/24),a.days-=h,a.hours+=24*h):24<=a.hours&&(a.days+=m(a.hours/24),a.hours%=24);for(;0>a.days;)a.months--,a.days+=v(a.refMonth,1);7<=a.days&&(a.weeks+=m(a.days/7),a.days%=7);0>a.months?(h=s(-a.months/12),a.years-=h,a.months+=12*h):12<=a.months&&(a.years+=m(a.months/12),a.months%=12);10<=a.years&&(a.decades+=m(a.years/10),a.years%=10,10<=a.decades&&(a.centuries+=m(a.decades/10),a.decades%= 11 | 10,10<=a.centuries&&(a.millennia+=m(a.centuries/10),a.centuries%=10)));b=0;!(g&1024)||b>=d?(a.centuries+=10*a.millennia,delete a.millennia):a.millennia&&b++;!(g&512)||b>=d?(a.decades+=10*a.centuries,delete a.centuries):a.centuries&&b++;!(g&256)||b>=d?(a.years+=10*a.decades,delete a.decades):a.decades&&b++;!(g&128)||b>=d?(a.months+=12*a.years,delete a.years):a.years&&b++;!(g&64)||b>=d?(a.months&&(a.days+=v(a.refMonth,a.months)),delete a.months,7<=a.days&&(a.weeks+=m(a.days/7),a.days%=7)):a.months&& 12 | b++;!(g&32)||b>=d?(a.days+=7*a.weeks,delete a.weeks):a.weeks&&b++;!(g&16)||b>=d?(a.hours+=24*a.days,delete a.days):a.days&&b++;!(g&8)||b>=d?(a.minutes+=60*a.hours,delete a.hours):a.hours&&b++;!(g&4)||b>=d?(a.seconds+=60*a.minutes,delete a.minutes):a.minutes&&b++;!(g&2)||b>=d?(a.milliseconds+=1E3*a.seconds,delete a.seconds):a.seconds&&b++;if(!(g&1)||b>=d){var e=l(a,0,"milliseconds","seconds",1E3,k);if(e&&(e=l(a,e,"seconds","minutes",60,k))&&(e=l(a,e,"minutes","hours",60,k))&&(e=l(a,e,"hours","days", 13 | 24,k))&&(e=l(a,e,"days","weeks",7,k))&&(e=l(a,e,"weeks","months",t(a.refMonth)/7,k))){g=e;var n,p=a.refMonth,q=p.getTime(),r=new Date(q);r.setUTCFullYear(p.getUTCFullYear()+1);n=Math.round((r.getTime()-q)/864E5);if(e=l(a,g,"months","years",n/t(a.refMonth),k))if(e=l(a,e,"years","decades",10,k))if(e=l(a,e,"decades","centuries",10,k))if(e=l(a,e,"centuries","millennia",10,k))throw Error("Fractional unit overflow");}}}finally{delete a.refMonth}return a}function d(a,b,c,d,f){var k;c=+c||222;d=0f?Math.round(f):20:0;"function"===typeof a?(k=a,a=null):a instanceof Date||(a=null!==a&&isFinite(a)?new Date(a):null);"function"===typeof b?(k=b,b=null):b instanceof Date||(b=null!==b&&isFinite(b)?new Date(b):null);if(!a&&!b)return new n;if(!k)return w(new n,a||new Date,b||new Date,c,d,f);var l=c&1?1E3/30:c&2?1E3:c&4?6E4:c&8?36E5:c&16?864E5:6048E5,h,e=function(){k(w(new n,a||new Date,b||new Date,c,d,f),h)};e();return h=setInterval(e,l)}var s=Math.ceil,m=Math.floor,p,q,u;n.prototype.toString= 15 | function(){var a=u(this),b=a.length;if(!b)return"";1=c;c++)p[c]=a[c]||p[c],q[c]=b[c]||q[c]};(d.resetLabels=function(){p="millisecond second minute hour day week month year decade century millennium".split(" "); 17 | q="milliseconds seconds minutes hours days weeks months years decades centuries millennia".split(" ")})();r&&r.exports?r.exports=d:"function"===typeof window.define&&window.define.amd&&window.define("countdown",[],function(){return d});return d}(module); -------------------------------------------------------------------------------- /client/css/brew.css: -------------------------------------------------------------------------------- 1 | nav.navbar { 2 | border-radius: 0 !important; 3 | -moz-border-radius: 0 !important; 4 | } 5 | 6 | .vcenter { 7 | display: inline-block; 8 | vertical-align: middle; 9 | float: none; 10 | } 11 | 12 | .ng-invalid.ng-dirty { 13 | border-color: #FA787E; 14 | } 15 | 16 | .ng-valid.ng-dirty { 17 | border-color: #78FA89; 18 | } 19 | 20 | .btn.no-border { 21 | border: 0px solid transparent; 22 | } 23 | 24 | .btn.transparent { 25 | background-color: transparent; 26 | } 27 | 28 | div.all-space-inner { 29 | padding: 10px; 30 | } 31 | 32 | div.all-space-inner-tb { 33 | padding-top: 15px; 34 | padding-bottom: 15px; 35 | } 36 | 37 | div.all-space-outer-b { 38 | margin-bottom: 15px; 39 | } 40 | 41 | div.chart-spacer { 42 | margin-top:-40px; 43 | margin-bottom:-35px; 44 | height: 250px; 45 | } 46 | 47 | div.chart-spacer-detail { 48 | margin-top:-40px; 49 | margin-bottom:-35px; 50 | height: 300px; 51 | } 52 | 53 | div.recipe.edit-mode { 54 | margin-right:70px; 55 | } 56 | 57 | div.main-content { 58 | max-width:500px; 59 | } 60 | 61 | span.title-temp { 62 | font-size: 12px; 63 | font-color: #EEEEEE !important; 64 | } 65 | 66 | label.brew { 67 | font-weight: normal !important; 68 | } 69 | 70 | li.current-step { 71 | border: 10px solid black; 72 | } 73 | 74 | div.bottom-space { 75 | margin-bottom: 10px; 76 | } 77 | 78 | img.header-icon { 79 | margin-left: -3px; 80 | margin-top: -3px; 81 | padding-right: 2px; 82 | } 83 | 84 | a.navbar-brand:hover > img.header-icon { 85 | -webkit-filter: brightness(115%) 86 | } 87 | 88 | div.fullArea { 89 | z-index: 8500; 90 | background-color: white; 91 | position: absolute; 92 | top: 0; 93 | bottom: 0; 94 | right: 0; 95 | left: 0; 96 | padding: 10px; 97 | } 98 | 99 | div.autoMode-label { 100 | width: 200px; 101 | float:left; 102 | padding-left: 15px; 103 | } 104 | 105 | div.autoMode-value { 106 | } 107 | 108 | /* RECIPE STEP */ 109 | div.recipe-step { 110 | } 111 | 112 | div.recipe-step-outer { 113 | width: 100%; 114 | position: absolute: 115 | } 116 | 117 | div.recipe-step-temp { 118 | position: absolute; 119 | left: 5px; 120 | top: 0; 121 | height: 100%; 122 | } 123 | 124 | div.recipe-step-temp-inner { 125 | position: absolute; 126 | top: 50%; 127 | margin-top: -13px; 128 | margin-left: 5px; 129 | width: 75px; 130 | height: 26px; 131 | text-align: center; 132 | } 133 | 134 | div.recipe-step-details { 135 | margin-left: 78px; 136 | } 137 | 138 | div.recipe-step-details-spaced { 139 | margin-right: 98px; 140 | } 141 | 142 | div.recipe-step-name { 143 | white-space: nowrap; 144 | overflow: hidden; 145 | } 146 | 147 | div.recipe-step-symbols { 148 | overflow: hidden; 149 | } 150 | 151 | div.recipe-step-edit { 152 | position: absolute; 153 | right: 8px; 154 | top: 0; 155 | height: 100%; 156 | } 157 | 158 | div.recipe-step-edit-controls { 159 | top: 50%; 160 | margin-top: -16px; 161 | width: 102px; 162 | height: 32px; 163 | } 164 | 165 | div.recipe-step-temp h3{ 166 | } 167 | 168 | div.recipe-step-symbols { 169 | } 170 | 171 | div.recipe-step-symbols span.label-primary, 172 | div.recipe-step-symbols span.label-success, 173 | div.recipe-step-symbols span.label-default { 174 | margin-bottom: 5px; 175 | margin-right: 5px; 176 | } 177 | 178 | div.recipe-step h3 { 179 | margin: 0; 180 | } 181 | 182 | /* logsection */ 183 | a.no-hover { 184 | color: inherit; 185 | } 186 | 187 | a.no-hover:hover { 188 | text-decoration: none; 189 | color: inherit; 190 | } 191 | table.log-overview { 192 | margin-bottom: 0; 193 | } 194 | div.logdetail { 195 | } 196 | 197 | div.logdetail-name { 198 | 199 | } 200 | 201 | div.logdetail-field { 202 | } 203 | 204 | div.logdetail-value { 205 | 206 | } 207 | 208 | div.csv-button { 209 | text-align: center; 210 | margin-bottom: 9px; 211 | } 212 | 213 | div.spacer-rl { 214 | margin-right: 15px; 215 | margin-left: 15px; 216 | } 217 | 218 | td.table-comment-label { 219 | width: 150px; 220 | } 221 | 222 | td.table-comment-value { 223 | 224 | } 225 | 226 | /* update */ 227 | div.update-available { 228 | z-index: 8000; 229 | bottom: 15px; 230 | left: 0px; 231 | position: fixed; 232 | text-align: right; 233 | } 234 | 235 | button.update-available { 236 | border-bottom-left-radius: 0px; 237 | border-top-left-radius: 0px; 238 | } 239 | 240 | /* feedback */ 241 | div.feedback { 242 | z-index: 8000; 243 | bottom: 15px; 244 | right: 0px; 245 | position: fixed; 246 | text-align: right; 247 | } 248 | 249 | #modalFeedback { 250 | z-index: 9000; 251 | } 252 | 253 | button.feedback { 254 | width: 40px; 255 | border-bottom-right-radius: 0px; 256 | border-top-right-radius: 0px; 257 | } 258 | 259 | button.feedback:hover { 260 | width: 100px; 261 | } 262 | 263 | span.feedback-text { 264 | visibility: hidden; 265 | } 266 | 267 | button.feedback:hover span.feedback-text { 268 | visibility: visible; 269 | } 270 | 271 | /* ALERTS */ 272 | div.alert-canvas { 273 | position: fixed; 274 | z-index: 9999; 275 | bottom: 0px; 276 | text-align: center; 277 | width: 100%; 278 | padding-left: 15%; 279 | padding-right: 15%; 280 | } 281 | 282 | div.alert-canvas-content { 283 | } 284 | 285 | /* DRAG N DROP */ 286 | .span-3 { 287 | width: 277px; 288 | } 289 | 290 | .form-group.btn { 291 | border: 1px solid #cccccc; 292 | border-top-radius: 4px; 293 | border-bottom-radius: 0; 294 | width: 208px !important; 295 | } 296 | 297 | .form-group, { 298 | float: left; 299 | clear: left; 300 | width: 240px; 301 | } 302 | 303 | input.form-control { 304 | margin-bottom: 0; 305 | border-radius: 0; 306 | border: 1px solid #cccccc; 307 | padding-right: 20px; 308 | } 309 | .row .form-group:first-child input.form-control { 310 | border-top: 1px solid #cccccc; 311 | border-top-radius: 4px; 312 | } 313 | .row .form-group:last-child input.form-control { 314 | border-bottom-radius: 4px; 315 | } 316 | .ng-repeat-reorder-parent, [ng-repeat-reorder]{ 317 | z-index: 10; 318 | position: relative; 319 | } 320 | [ng-repeat-reorder].dragging{ 321 | z-index: 11; 322 | position: absolute; 323 | -webkit-box-shadow: 0px 0px 25px 2px rgba(0,0,0,0.75); 324 | -moz-box-shadow: 0px 0px 25px 2px rgba(0,0,0,0.75); 325 | box-shadow: 0px 0px 25px 2px rgba(0,0,0,0.75); 326 | } 327 | .form-group.dragging input { 328 | border: 1px solid #cccccc; 329 | } 330 | .form-group.dragging-after input { 331 | border-top: 1px solid #cccccc; 332 | } 333 | .form-group.dragging-before input { 334 | } 335 | 336 | .active-drag-below { 337 | -moz-user-select: none; 338 | -khtml-user-select: none; 339 | -webkit-user-select: none; 340 | user-select: none; 341 | } 342 | 343 | /* Wifi */ 344 | 345 | span.wifi-bars { 346 | overflow : hidden; 347 | } 348 | 349 | /* loading spinner */ 350 | .glyphicon-refresh-animate { 351 | -animation: spin .7s infinite linear; 352 | -webkit-animation: spin2 .7s infinite linear; 353 | } 354 | 355 | @-webkit-keyframes spin2 { 356 | from { -webkit-transform: rotate(0deg);} 357 | to { -webkit-transform: rotate(360deg);} 358 | } 359 | 360 | @keyframes spin { 361 | from { transform: scale(1) rotate(0deg);} 362 | to { transform: scale(1) rotate(360deg);} 363 | } 364 | 365 | /* status bar */ 366 | .glyphicon-small { 367 | font-size: 12px; 368 | } 369 | 370 | .glyphicon-disabled { 371 | opacity: 0.4; 372 | } -------------------------------------------------------------------------------- /client/js/xml2json.js: -------------------------------------------------------------------------------- 1 | /* This work is licensed under Creative Commons GNU LGPL License. 2 | 3 | License: http://creativecommons.org/licenses/LGPL/2.1/ 4 | Version: 0.9 5 | Author: Stefan Goessner/2006 6 | Web: http://goessner.net/ 7 | */ 8 | function xmlToJson(xml) { 9 | 10 | // Create the return object 11 | var obj = {}; 12 | 13 | if (xml.nodeType == 1) { // element 14 | // do attributes 15 | if (xml.attributes.length > 0) { 16 | obj["@attributes"] = {}; 17 | for (var j = 0; j < xml.attributes.length; j++) { 18 | var attribute = xml.attributes.item(j); 19 | obj["@attributes"][attribute.nodeName] = attribute.nodeValue; 20 | } 21 | } 22 | } else if (xml.nodeType == 3) { // text 23 | obj = xml.nodeValue; 24 | } 25 | 26 | // do children 27 | if (xml.hasChildNodes()) { 28 | for(var i = 0; i < xml.childNodes.length; i++) { 29 | var item = xml.childNodes.item(i); 30 | var nodeName = item.nodeName; 31 | if (typeof(obj[nodeName]) == "undefined") { 32 | obj[nodeName] = xmlToJson(item); 33 | } else { 34 | if (typeof(obj[nodeName].push) == "undefined") { 35 | var old = obj[nodeName]; 36 | obj[nodeName] = []; 37 | obj[nodeName].push(old); 38 | } 39 | obj[nodeName].push(xmlToJson(item)); 40 | } 41 | } 42 | } 43 | return obj; 44 | }; 45 | 46 | function xml2json(xml, tab) { 47 | var X = { 48 | toObj: function(xml) { 49 | var o = {}; 50 | if (xml.nodeType==1) { // element node .. 51 | if (xml.attributes.length) // element with attributes .. 52 | for (var i=0; i 1) 94 | o = X.escape(X.innerXml(xml)); 95 | else 96 | for (var n=xml.firstChild; n; n=n.nextSibling) 97 | o["#cdata"] = X.escape(n.nodeValue); 98 | } 99 | } 100 | if (!xml.attributes.length && !xml.firstChild) o = null; 101 | } 102 | else if (xml.nodeType==9) { // document.node 103 | o = X.toObj(xml.documentElement); 104 | } 105 | else 106 | alert("unhandled node type: " + xml.nodeType); 107 | return o; 108 | }, 109 | toJson: function(o, name, ind) { 110 | var json = name ? ("\""+name+"\"") : ""; 111 | if (o instanceof Array) { 112 | for (var i=0,n=o.length; i 1 ? ("\n"+ind+"\t"+o.join(",\n"+ind+"\t")+"\n"+ind) : o.join("")) + "]"; 115 | } 116 | else if (o == null) 117 | json += (name&&":") + "null"; 118 | else if (typeof(o) == "object") { 119 | var arr = []; 120 | for (var m in o) 121 | arr[arr.length] = X.toJson(o[m], m, ind+"\t"); 122 | json += (name?":{":"{") + (arr.length > 1 ? ("\n"+ind+"\t"+arr.join(",\n"+ind+"\t")+"\n"+ind) : arr.join("")) + "}"; 123 | } 124 | else if (typeof(o) == "string") 125 | json += (name&&":") + "\"" + o.toString() + "\""; 126 | else 127 | json += (name&&":") + o.toString(); 128 | return json; 129 | }, 130 | innerXml: function(node) { 131 | var s = "" 132 | if ("innerHTML" in node) 133 | s = node.innerHTML; 134 | else { 135 | var asXml = function(n) { 136 | var s = ""; 137 | if (n.nodeType == 1) { 138 | s += "<" + n.nodeName; 139 | for (var i=0; i"; 146 | } 147 | else 148 | s += "/>"; 149 | } 150 | else if (n.nodeType == 3) 151 | s += n.nodeValue; 152 | else if (n.nodeType == 4) 153 | s += ""; 154 | return s; 155 | }; 156 | for (var c=node.firstChild; c; c=c.nextSibling) 157 | s += asXml(c); 158 | } 159 | return s; 160 | }, 161 | escape: function(txt) { 162 | return txt.replace(/[\\]/g, "\\\\") 163 | .replace(/[\"]/g, '\\"') 164 | .replace(/[\n]/g, '\\n') 165 | .replace(/[\r]/g, '\\r'); 166 | }, 167 | removeWhite: function(e) { 168 | e.normalize(); 169 | for (var n = e.firstChild; n; ) { 170 | if (n.nodeType == 3) { // text node 171 | if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) { // pure whitespace text node 172 | var nxt = n.nextSibling; 173 | e.removeChild(n); 174 | n = nxt; 175 | } 176 | else 177 | n = n.nextSibling; 178 | } 179 | else if (n.nodeType == 1) { // element node 180 | X.removeWhite(n); 181 | n = n.nextSibling; 182 | } 183 | else // any other node 184 | n = n.nextSibling; 185 | } 186 | return e; 187 | } 188 | }; 189 | if (xml.nodeType == 9) // document node 190 | xml = xml.documentElement; 191 | var json = X.toJson(X.toObj(X.removeWhite(xml)), xml.nodeName, "\t"); 192 | return "{\n" + tab + (tab ? json.replace(/\t/g, tab) : json.replace(/\t|\n/g, "")) + "\n}"; 193 | } 194 | -------------------------------------------------------------------------------- /doc/README_DE.MD: -------------------------------------------------------------------------------- 1 |

2 | BierBot Logo 3 |

Brau-Software für den Raspberry Pi

4 |

5 | 6 | This README is also available in [English ![English](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/us.png "English")](../README.MD) 7 | 8 | ## Bitte aktuelle Version benutzen ⚠ 9 | 10 | Diese Software wird nicht mehr aktiv weiterentwickelt. Bitte benutze statt dessen den "BierBot Bricks Raspberry Pi" - [kostenlos erhältlich hier](https://github.com/BernhardSchlegel/BierBot-Bricks-RaspberryPi). Wenn du maintainer dieses repos werden willst, erstelle bitte ein Issue und melde dich. 11 | 12 | ## Funktionen 13 | 14 | - Läuft auf dem Raspberry Pi ([kompatibile Pis](#kompatiblität)) 15 | - Komplett freie, Open Source Lizenz (MIT) 16 | - Einfache Installation (Einzeiler) 17 | - Simpel: Ein Sensor, ein Relais 18 | - PD-Controller, der auch mit einfachen, günstigen Standard-Relais funktioniert 19 | und dennoch effektiv das Überschiessen der Temperatur vermeidet 20 | - Modi für Maischen und Gärung 21 | - Multilingual (Deutsch, Englisch, Pull-Requests für weitere Sprachen sind 22 | willkommen) 23 | - Integrierte Rezeptverwaltung: Rastdauer, -temperatur und Rührwerkskonfiguration 24 | - Integriertes Logging: Der Temperaturverlauf wird während dem Brauen angezeigt, 25 | nach dem Brauvorgang gibt's einen Braubericht. 26 | - Passwortschutz (beta) 27 | - Kann während des Brauvorgangs Nachrichten an Telegram verschicken 28 | - Läuft auf jedem Webbrowser 29 | 30 | ## Updates 31 | 32 | - v.2.1.0 (23.01.2021) 33 | - Telegram Support, danke an [@jonathanschneider](https://github.com/jonathanschneider) für den PR 34 | - BeerXML import funktioniert nun (wieder), getestet mit Rezepten von [www.brewersfriend](https://www.brewersfriend.com/) 35 | ## Screenshots 36 | 37 |

38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |

48 | 49 | ## Software-Installation 50 | 51 | ### Prepare SD-Card and Raspberry Pi 52 | 53 | 1. Download RASPBIAN BUSTER LITE von der [offiziellen Website](https://www.raspberrypi.org/downloads/raspbian/). Die empfohlene Version ist vom 54 | Februar 2020 (Release Datum 2002-02-13), mit kernel version 4.19. 55 | 2. Image auf die SD-Karte flashen gemäß [dieser Seite](https://www.raspberrypi.org/documentation/installation/installing-images/) 56 | 3. Anschließen des (micro) HDMI Kabels (Monitor), USB-Keyboards and microUSBs 57 | (USB-C) für Strom an den RaspberryPi (4). Warten bis er zwei mal gebootet hat. 58 | 4. Login mit User `pi` und Passwort `raspberry` (:warning: US-Keyboard, y-z 59 | sind getauscht). 60 | 5. Ausführen (=tippen) `sudo raspi-config`, 61 | 1. Dann `5 Interfacing Options` > `P2 SSH` und mit `` bestätigen. 62 | 2. `5 Interfacing Options` > `P7 1-Wire` > `` 63 | 3. _Optional_: Falls ihr direkt WLAN verbinden wollt: Jetzt ist die Zeit: 64 | Wählt `2 Network options` > `N2 Wifi`. Das wird aber auch später über 65 | die Oberfläche des BierBots gehen. 66 | 4. Ebenfalls _optional_ aber **empfohlen**: `1 Change user password`. 67 | 6. Jetzt kannst du den Monitor abstöpseln - der Rest läuft über SSH, also 68 | einen anderen PC - macht das einfügen des Links (s.u.) leichter. 69 | 7. Verbinde dich mit deiner Router-Oberfläche um die IP des Raspberies 70 | rauszufinden. 71 | 72 | ### Installieren der BierBot software 73 | 74 | Jetzt kannst du die BierBot Software installieren. Hier, wie versprochen, der 75 | (etwas lange :wink: ) **Einzeiler**: 76 | 77 | ```bash 78 | cd ~ && wget https://raw.githubusercontent.com/BernhardSchlegel/BierBot/master/bierbot-setup.sh && chmod +x bierbot-setup.sh && sudo ./bierbot-setup.sh 79 | ``` 80 | 81 | **Glückwunsch**, das wars. Dein BierBot ist jetzt unter http://BierBotBen erreichbar (du kannst den Namen deines BierBots auf der Konfigurationsseite ändern) 82 | 83 | ## Kompatiblität 84 | 85 | Die folgende Tabelle gibt eine Übersicht auf welchen Raspberry Pis die 86 | BierBot Software funktioniert ([source](https://de.wikipedia.org/wiki/Raspberry_Pi)): 87 | 88 |
{{comment[0]| date : 'dd.MM.yyyy HH:mm:ss' }}{{comment[1]}}
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 |
Raspberry Pi1234
TypeAA+BB+BB v1.2A+B v.1.2B+B
ARMARMv6 (32 Bit)ARMv7 (32Bit)ARMv8 (64 Bit)
Power to beep1m 31s1m 36s1m 33s
setupscript:heavy_check_mark::heavy_check_mark::heavy_check_mark:
software:heavy_check_mark::heavy_check_mark::heavy_check_mark:
159 | 160 | **TL;DR**: Holt euch den billigsten - es sind alle gleich schnell. 161 | 162 | Wenn ihr Werte für leere Zeilen habt, schickt mir gerne eine Nachricht! 163 | 164 | ## Hardware-Setup 165 | 166 | Standardmäßig muss der BierBot wie folgt verkabel werden (ich werde mich an denied `RPi# (pigpio#)` Syntax halten): 167 | 168 | ![BierBot Wiring](/doc/img/wiring.png) 169 | 170 | - Der [DS18B20](https://amzn.to/2TQrvxQ) Sensor muss mit 3,3V, GND und Pin 171 | Nummer RPi 7 (pi-gpio 7) verbunden werden. Das ist der Datenkanal des 172 | Temperatur-Sensors. 173 | - Der Daten-Pin deines Relais muss mit Pin 11 (17) verbunden werden. Damit wird 174 | deine Heizung / Kühlung an- bzw. ausgeschaltet. 175 | - Deinen Motor (z.B. um die Maische zu rühren) muss mit Pin 12 (18) verbunden 176 | werden. Das ist ein PWM-Pin - was auch benutzt werden wird. Die Motorspannung 177 | (und damit die Drehgeschwindigkeit) kannst du in der BierBot-Oberfläche 178 | (Einstellungen) setzen. 179 | - Pin 13 (27) is *optional*: Wenn der Temperatursensor nach dem Sart des 180 | BierBots ein- und ausgesteckt werden können soll, musst du diesen Pin verbinden. 181 | So wird eine ISR getriggert, die auf beiden Flanken (steigend und fallend) nach 182 | neuen Temperatursensoren sucht. Intern ist dieser Pin mit einem 183 | Pulldown-Widerstand auf ground gezogen. 184 | - Pin 15 (22) ist ebenfalls *optional*: Hier kannst du einen [piezzo buzzer](https://amzn.to/2vDWe9D) anschließen. Der BierBot signalisiert dann mit einer 185 | Pieps-Symphonie, wenn der nächste Schritt erreicht wird, oder der Brauvorgang 186 | beendet ist. 187 | 188 | ## Mitachen! 189 | 190 | Wenn du bei der Entwicklung des BierBots helfen möchtest, freue ich mich über 191 | jeden Pull-Request oder Übersetzung. Für Entwickler gibt's eine extra Seite mit 192 | wichtigen Infos: [Infos für Entwickler](/doc/CONTRIBUTING.MD)! 193 | 194 | ## Lizenz 195 | 196 | - Mein Code: [MIT](http://opensource.org/licenses/MIT). 197 | - Das oragene Maischpfannen-Logo: (c) by Bernhard Schlegel, all rights reserved. 198 | 199 | Copyright (c) 2014-present, Bernhard Schlegel 200 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 |

2 | BierBot Logo 3 |

Raspberry Pi powered Brewing Software

4 |

5 | 6 | This README is also available in [German ![Germany](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/de.png "Germany")](./doc/README_DE.MD) 7 | 8 | ## Deprecation Notice ⚠ 9 | 10 | This repository is no longer actively developed. Please use the "BierBot Bricks Raspberry Pi"-client instead. Available for [free here](https://github.com/BernhardSchlegel/BierBot-Bricks-RaspberryPi). If you want to be maintainer, please create an issue and reach out! 11 | 12 | ## Key features 13 | 14 | - Runs on Raspberry Pi ([compatible Pis](#compatibility)) 15 | - Free, do-the-fuck-you-want Open Source license (MIT) 16 | - Easy to setup (one liner) 17 | - Simple: one sensor, one relay 18 | - PD controller that works with a simple and cheap non PWM/SSR relay and yet eliminates temperature overshoots 19 | - Modes for mashing and fermentation 20 | - Multi-language (German, English, pull-requests welcome) 21 | - Integrated recipes: Set the temperature, the time, etc. 22 | - Integrated logging: See your temperature log during the mash and export a PDF after brewing 23 | - User authentication 24 | - Can send messages to Telegram during brewing 25 | - Works perfectly on any web browser 26 | 27 | ## Updates 28 | 29 | To run a manual update: 30 | 31 | 1. Connect to your BierBot using SSH (type `ssh pi@192.168.2.42` in your favorite Terminal, with the IP being the IP of your BierBot). 32 | 2. Change into the BierBot folder `cd cd /home/pi/BierBot` 33 | 3. Discard any local changes to your BierBot files `git reset --hard` 34 | 4. Pull the newest version from GitHub `git pull` (depending on your install, you might get permission errors, run `sudo git pull`) 35 | 36 | - v.2.1.0 (23.01.2021) 37 | - Added telegram Bot support, thanks to [@jonathanschneider](https://github.com/jonathanschneider) for the PR 38 | - Bugfix BeerXML import now working (again), tested with recipes from [www.brewersfriend](https://www.brewersfriend.com/) 39 | 40 | ## Screenshots 41 | 42 |

43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |

53 | 54 | ## Software-Installation 55 | 56 | ### Prepare SD-Card and Raspberry Pi 57 | 58 | 1. Download RASPBIAN BUSTER LITE from the [official website](https://www.raspberrypi.org/downloads/raspbian/). 59 | Recommended version is from February 2020 (Release Date 2020-02-13), with kernel version 4.19. 60 | 1. Flash it to the SD card as described on the [setup page](https://www.raspberrypi.org/documentation/installation/installing-images/) 61 | 1. Connect at least an (micro) HDMI cable (monitor), USB keyboard and microUSB 62 | (USB-C) for power to your Raspberry Pi (4). Wait for it to boot twice. 63 | 1. Login using user `pi` and password `raspberry` 64 | 1. Run (=type) `sudo raspi-config`, 65 | 1. select `5 Interfacing Options` > `P2 SSH` and confirm with `` 66 | 1. _Optional_: If you want to connect to your WiFi: now is your time: 67 | select `2 Network options` > `N2 Wifi`. This is also possible via the 68 | BierBot Software later. 69 | 1. Also _optional_ but **recommended**: `1 Change user password`. 70 | 1. You may now unplug your HDMI and keyboard and operate the Raspberry Pi soley over Ethernet / WiFi. 71 | 1. Connect to your router to figure out the IP of your Raspberry Pi. 72 | 73 | ### Install the BierBot software 74 | 75 | Now, we are ready to update the Raspberry Pi and install all requirements, such as NodeJS and the database which is the promised **one-liner**. 76 | 77 | ```bash 78 | cd ~ && wget https://raw.githubusercontent.com/BernhardSchlegel/BierBot/master/bierbot-setup.sh && chmod +x bierbot-setup.sh && sudo ./bierbot-setup.sh 79 | ``` 80 | 81 | **Congrats**, you're done. Your BierBot is now accessible at http://BierBotBen (you can change the name of your BierBot on the configuration page) 82 | 83 | If you want to know what the script does in the background, check out the (potentially outdated) explanation [here](/doc/SETUP_MANUAL.MD). 84 | 85 | 86 | ## Compatibility 87 | 88 | The BierBot install script and Software has been tested on the following 89 | Raspberry Pis ([source](https://de.wikipedia.org/wiki/Raspberry_Pi)): 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 |
Raspberry Pi1234
TypeAA+BB+BB v1.2A+B v.1.2B+B
ARMARMv6 (32 Bit)ARMv7 (32Bit)ARMv8 (64 Bit)
Power to beep1m 31s1m 36s1m 33s
setupscript:heavy_check_mark::heavy_check_mark::heavy_check_mark:
software:heavy_check_mark::heavy_check_mark::heavy_check_mark:
162 | 163 | **TL;DR**: Get the cheapest one, in terms of startup speed: No difference. 164 | 165 | If you happen to own one of the models with a blank cell, shoot me a message! 166 | 167 | ## Hardware-Setup 168 | 169 | The default BierBot wiring is as follows (I'll use the `RPi# (pigpio#)` syntax): 170 | 171 | ![BierBot Wiring](doc/img/wiring.png) 172 | 173 | - The [DS18B20](https://amzn.to/2TQrvxQ) sensor is connected to 3,3V, GND, and 174 | Pin Number RPi 7 (pi-gpio 7). With the latter being the data-channel of your sensor. 175 | - The control channel of your Relais is connected to 11 (17). This will be used 176 | to turn on/off your heating / cooling device. 177 | - Your motor (for stirring your mash) is connected to Pin 12 (18). This is a PWM 178 | Pin - which will be used. You can set your Motor Voltage on the BierBot 179 | settings page. 180 | - Pin 13 (27) is _optional_: If you want your temperature sensor to be hot pluggable 181 | connect this pin. It will trigger an ISR on either edge (falling / rising) and 182 | is pulled via an internal resistor to ground. 183 | - Pin 15 (22) is also _optional_: If you connect a [piezzo buzzer](https://amzn.to/2vDWe9D) the BierBot tells you with a beeping symphony when the next step is 184 | reached or the brew is finished. 185 | 186 | ## TODOS 187 | 188 | - migrate client package management from `bower` to `yarn`. 189 | 190 | ## Contributing 191 | 192 | If you want to contribute, your Pull-Request or translation is very welcome! 193 | Please see the [notes for developers](/doc/CONTRIBUTING.MD) to get you started! 194 | 195 | ## License 196 | 197 | - My Code: [MIT](http://opensource.org/licenses/MIT) 198 | - Orange Mash BierBot Logo: (c) by Bernhard Schlegel, all rights reserved. 199 | 200 | Copyright (c) 2014-present, Bernhard Schlegel 201 | -------------------------------------------------------------------------------- /doc/CONTRIBUTING.MD: -------------------------------------------------------------------------------- 1 | ## Notes for developers 2 | 3 | General, short hints to get you started: 4 | 5 | - To stop the "production" instance of the BierBot, run `sudo systemctl stop bierbot.service` 6 | - Start the BierBot by `sudo node /home/pi/BierBot/server/server.js` (if node is not found, try `/usr/bin/node`) 7 | - Attach to nodeJS service `sudo node --inspect=0.0.0.0:9229 /home/pi/BierBot/server/server.js` 8 | - To view the service logs (might be helpful when the BierBot crashed) `journalctl -e -u bierbot.service` 9 | or `journalctl -u bierbot.service -f` to follow along. 10 | - To ease app for minification, annotation may be necessary. install ng-annotate (just for developement) 11 | `sudo npm install -g ng-annotate`. Use it: `ng-annotate -o app_annotated.js --add app.js` (run it from 12 | `./BierBot/client)`. 13 | 14 | ### Setting up Samba Fileshare 15 | 16 | Setting up Samba Fileshare may ease the development process, since you can 17 | launch the IDE of your choice on your dev machine an access essentially the 18 | RaspberryPis filesystem: 19 | 20 | - To setup a Windows (and Mac) compatible file share, run `sudo apt-get install samba -y` 21 | (confirm with yes) followed by `sudo nano /etc/samba/smb.conf` and paste the following 22 | contents at the end of the file ([credits](https://thisdavej.com/beginners-guide-to-installing-node-js-on-a-raspberry-pi/)) 23 | 24 | [BierBotShare] 25 | comment=Raspi Share 26 | path=/home/pi 27 | browseable=Yes 28 | writeable=Yes 29 | only guest=No 30 | create mask=0740 31 | directory mask=0750 32 | public=no 33 | 34 | exit and save. 35 | 36 | - Now, create a samba fileshare user by `sudo smbpasswd -a pi` and assign him a 37 | password when prompted. 38 | 39 | ### Handy Stuff 40 | 41 | To delete the current brew start the mongo console (`pi$ mongo`) 42 | 43 | ``` 44 | use brewdb 45 | db.logs.remove({ currentBrew : true }) 46 | ``` 47 | 48 | To replace a document (e.g. settings): 49 | 50 | ``` 51 | use brewdb 52 | db.settings.remove({}) 53 | db.settings.insert( 54 | { 55 | "addToSensorVal" : 0, 56 | "bierBotName" : "Ben", 57 | "boilingTempC" : 95, 58 | "defaultSudSize" : 42, 59 | ... 60 | }) 61 | ``` 62 | 63 | ### Debugging the database 64 | 65 | First, open your database for external access: Edit the config from mongo db 66 | with the Nano / or vim editor `sudo nano /etc/mongodb.conf` and change the lines 67 | (to make the MongoDB accessible over Ethernet). 68 | 69 | > :warning: **Warning** the combination of bind_ip and the default password is not 70 | > recommended as long term solution. Also: Do not include the brackets and 71 | > anything in between. 72 | 73 | # bind_ip = 127.0.0.1 (add the #) 74 | port = 27017 (remove the #) 75 | auth = true (remove the #) 76 | 77 | Restart MongoDB `sudo service mongodb restart` to apply your changes. 78 | 79 | For debugging db issues I recommend [Robo 3T](https://robomongo.org/download) 80 | the following settings will work to authenticate: 81 | 82 | - Address: The Ip 83 | - Port: 27017 84 | - Perform authentication: Check (yes) 85 | - Database: brewdb 86 | - User name: nodejsbierbot 87 | - Password: bierbot123 88 | 89 | ### Creating a new image 90 | 91 | - Always start with a vanilla system 92 | - If you setup a WiFi network delete it from `/etc/wpa_supplicant/wpa_supplicant.conf` 93 | - replace `version.js` by git pull (`updateFinished` needs to be false) 94 | 95 | ### Liscense 96 | 97 | **TL;DR** If you contribute, your contribution is also under [MIT](.LICENSE). 98 | 99 | **Long version**: By contributing, you certify to the following conditions: 100 | 101 | Definitions. 102 | 103 | "You" (or "Your") shall mean the copyright owner or legal entity authorized by 104 | the copyright owner that is making this Agreement with Bernhard Schlegel. For 105 | legal entities, the entity making a Contribution and all other entities that 106 | control, are controlled by, or are under common control with that entity are 107 | considered to be a single Contributor. For the purposes of this definition, 108 | "control" means (i) the power, direct or indirect, to cause the direction or 109 | management of such entity, whether by contract or otherwise, or (ii) ownership 110 | of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial 111 | ownership of such entity. 112 | 113 | Grant of Copyright License. Subject to the terms and conditions of this 114 | Agreement, You hereby grant to Bernhard Schlegel and to recipients of software 115 | distributed by Bernhard Schlegel a perpetual, worldwide, non-exclusive, no-charge, 116 | royalty-free, irrevocable copyright license to reproduce, prepare derivative works 117 | of, publicly display, publicly perform, sublicense, and distribute Your Contributions 118 | and such derivative works. 119 | 120 | Grant of Patent License. Subject to the terms and conditions of this Agreement, 121 | You hereby grant to Bernhard Schlegel and to recipients of software distributed 122 | by Bernhard Schlegel a perpetual, worldwide, non-exclusive, no-charge, 123 | royalty-free, irrevocable (except as stated in this section) patent license to 124 | make, have made, use, offer to sell, sell, import, and otherwise transfer the 125 | Work, where such license applies only to those patent claims licensable by You 126 | that are necessarily infringed by Your Contribution(s) alone or by combination 127 | of Your Contribution(s) with the Work to which such Contribution(s) was 128 | submitted. If any entity institutes patent litigation against You or any other 129 | entity (including a cross-claim or counterclaim in a lawsuit) alleging that your 130 | Contribution, or the Work to which you have contributed, constitutes direct or 131 | contributory patent infringement, then any patent licenses granted to that 132 | entity under this Agreement for that Contribution or Work shall terminate as 133 | of the date such litigation is filed. 134 | 135 | You represent that You are legally entitled to grant the above license. You 136 | represent further that each employee of the Corporation designated by You is 137 | authorized to submit Contributions on behalf of the Corporation. 138 | 139 | You represent that each of Your Contributions is Your original creation (see 140 | section 7 for submissions on behalf of others). 141 | 142 | You are not expected to provide support for Your Contributions, except to the 143 | extent You desire to provide support. You may provide support for free, for a 144 | fee, or not at all. Unless required by applicable law or agreed to in writing, 145 | You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR 146 | CONDITIONS OF ANY KIND, either express or implied, including, without limitation, 147 | any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or 148 | FITNESS FOR A PARTICULAR PURPOSE. 149 | 150 | Should You wish to submit work that is not Your original creation, You may submit 151 | it to Bernhard Schlegel separately from any Contribution, identifying the 152 | complete details of its source and of any license or other restriction 153 | (including, but not limited to, related patents, trademarks, and license 154 | agreements) of which you are personally aware, and conspicuously marking 155 | the work as "Submitted on behalf of a third-party: [named here]". 156 | 157 | It is your responsibility to notify Bernhard Schlegel when any change is 158 | required to the list of designated employees authorized to submit Contributions 159 | on behalf of the Corporation, or to the Corporation's Point of Contact with 160 | Bernhard Schlegel. 161 | 162 | Also, you certify that: 163 | 164 | (a) The contribution was created in whole or in part by you and you have the 165 | right to submit it under the open source license indicated in the file; or 166 | 167 | (b) The contribution is based upon previous work that, to the best of your 168 | knowledge, is covered under an appropriate open source license and you have 169 | the right under that license to submit that work with modifications, whether 170 | created in whole or in part by you, under the same open source license (unless 171 | you are permitted to submit under a different license), as indicated in the 172 | file; or 173 | 174 | (c) The contribution was provided directly to you by some other person who 175 | certified (a), (b) or (c) and I have not modified it. 176 | 177 | (d) I understand and agree that this project and the contribution are public 178 | and that a record of the contribution (including all personal information I 179 | submit with it, including my sign-off) is maintained indefinitely and may be 180 | redistributed consistent with this project or the open source license(s) 181 | involved 182 | -------------------------------------------------------------------------------- /server/config/passport.js: -------------------------------------------------------------------------------- 1 | // load all the things we need 2 | 3 | var brewlog = require('../libs/brewlog.js'); 4 | var LocalStrategy = require('passport-local').Strategy; 5 | 6 | // load up the user model 7 | var User = require('../app/models/user'); 8 | 9 | module.exports = function(passport) { 10 | 11 | // ========================================================================= 12 | // passport session setup ================================================== 13 | // ========================================================================= 14 | // required for persistent login sessions 15 | // passport needs ability to serialize and unserialize users out of session 16 | 17 | // used to serialize the user for the session 18 | passport.serializeUser(function(user, done) { 19 | done(null, user.id); 20 | }); 21 | 22 | // used to deserialize the user 23 | passport.deserializeUser(function(id, done) { 24 | User.findById(id, function(err, user) { 25 | done(err, user); 26 | }); 27 | }); 28 | 29 | // ========================================================================= 30 | // LOCAL LOGIN ============================================================= 31 | // ========================================================================= 32 | passport.use('local-login', new LocalStrategy({ 33 | // by default, local strategy uses username and password, we will override with username 34 | usernameField: 'username', 35 | passwordField: 'password', 36 | passReqToCallback: true // allows us to pass in the req from our route (lets us check if a user is logged in or not) 37 | }, 38 | function(req, username, password, done) { 39 | brewlog.log('authenticating user (' + username + ') ...'); 40 | 41 | if (username) 42 | username = username.toLowerCase(); // Use lower-case e-mails to avoid case-sensitive e-mail matching 43 | 44 | // asynchronous 45 | process.nextTick(function() { 46 | User.findOne({ 47 | 'local.username': username 48 | }, function(err, user) { 49 | // if there are any errors, return the error 50 | if (err) 51 | return done(err); 52 | 53 | // if no user is found, return the message 54 | if (!user) 55 | return done(null, false, req.flash('loginMessage', 'No user found.')); 56 | 57 | if (!user.validPassword(password)) 58 | return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); 59 | 60 | // all is well, return user 61 | else 62 | return done(null, user); 63 | }); 64 | }); 65 | 66 | })); 67 | 68 | // ========================================================================= 69 | // LOCAL PASSWORD CHANGE ============================================================ 70 | // ========================================================================= 71 | passport.use('local-change', new LocalStrategy({ 72 | // by default, local strategy uses username and password, we will override with username 73 | usernameField: 'username', 74 | passwordField: 'password', 75 | passReqToCallback: true // allows us to pass in the req from our route (lets us check if a user is logged in or not) 76 | }, 77 | function(req, username, password, done) { 78 | brewlog.log('changing password of ' + username + ' to ' + password); 79 | if (username) 80 | username = username.toLowerCase(); // Use lower-case e-mails to avoid case-sensitive e-mail matching 81 | 82 | // asynchronous 83 | process.nextTick(function() { 84 | // if the user is not already logged in: 85 | if (!req.user) { 86 | return done(null, false, req.flash('changeMessage', 'User not loggen in.')); 87 | // if the user is logged in but has no local account... 88 | } else if (!req.user.local.username) { 89 | return done(null, false, req.flash('changeMessage', 'User logged in, but no local account.')); 90 | } else { 91 | // user is logged in and already has a local account. 92 | User.findOne({ 93 | 'local.username': username 94 | }, function(err, user) { 95 | // if there are any errors, return the error 96 | if (err) { 97 | brewlog.log('finding user failed: ' + err); 98 | return done(err); 99 | } 100 | 101 | // check to see if theres already a user with that username 102 | if (user) { 103 | brewlog.log('user found, attempting to change passphrase'); 104 | 105 | // change the the user 106 | user.local.password = user.generateHash(password); 107 | 108 | user.save(function(err) { 109 | if (err) { 110 | brewlog.log('failed to change password: ' + err); 111 | return done(err); 112 | } 113 | 114 | brewlog.log('password changed successfully!'); 115 | return done(null, user); 116 | }); 117 | } else { 118 | return done(null, false, req.flash('changeMessage', 'User doesnt exist')); 119 | } 120 | }); 121 | } 122 | }); 123 | })); 124 | 125 | // ========================================================================= 126 | // LOCAL SIGNUP ============================================================ 127 | // ========================================================================= 128 | passport.use('local-signup', new LocalStrategy({ 129 | // by default, local strategy uses username and password, we will override with username 130 | usernameField: 'username', 131 | passwordField: 'password', 132 | passReqToCallback: true // allows us to pass in the req from our route (lets us check if a user is logged in or not) 133 | }, 134 | function(req, username, password, done) { 135 | brewlog.log('signing up user (' + username + ') ...'); 136 | if (username) 137 | username = username.toLowerCase(); // Use lower-case e-mails to avoid case-sensitive e-mail matching 138 | 139 | // asynchronous 140 | process.nextTick(function() { 141 | // if the user is not already logged in: 142 | if (!req.user) { 143 | User.findOne({ 144 | 'local.username': username 145 | }, function(err, user) { 146 | // if there are any errors, return the error 147 | if (err) 148 | return done(err); 149 | 150 | // check to see if theres already a user with that username 151 | if (user) { 152 | return done(null, false, req.flash('signupMessage', 'That username is already taken.')); 153 | } else { 154 | 155 | // create the user 156 | var newUser = new User(); 157 | 158 | newUser.local.username = username; 159 | newUser.local.password = newUser.generateHash(password); 160 | 161 | newUser.save(function(err) { 162 | if (err) 163 | return done(err); 164 | 165 | return done(null, newUser); 166 | }); 167 | } 168 | 169 | }); 170 | // if the user is logged in but has no local account... 171 | } else if (!req.user.local.username) { 172 | // ...presumably they're trying to connect a local account 173 | // BUT let's check if the email used to connect a local account is being used by another user 174 | User.findOne({ 175 | 'local.username': username 176 | }, function(err, user) { 177 | if (err) 178 | return done(err); 179 | 180 | if (user) { 181 | return done(null, false, req.flash('loginMessage', 'That username is already taken.')); 182 | // Using 'loginMessage instead of signupMessage because it's used by /connect/local' 183 | } else { 184 | var user = req.user; 185 | user.local.username = username; 186 | user.local.password = user.generateHash(password); 187 | user.save(function(err) { 188 | if (err) 189 | return done(err); 190 | 191 | return done(null, user); 192 | }); 193 | } 194 | }); 195 | } else { 196 | // user is logged in and already has a local account. Ignore signup. (You should log out before trying to create a new account, user!) 197 | return done(null, req.user); 198 | } 199 | 200 | }); 201 | 202 | })); 203 | 204 | }; 205 | -------------------------------------------------------------------------------- /server/libs/restoreclear.js: -------------------------------------------------------------------------------- 1 | var brewlog = require('./brewlog'); 2 | var common = require('./common'); 3 | var defaults = require('./defaults'); 4 | var configDB = require('../config/database.js'); 5 | var collections = ["recipes", "logs", "settings", "users", "sessions"] 6 | const mongojs = require("mongojs"); 7 | var db = mongojs(configDB.url, collections); 8 | var mongoose = require('mongoose'); 9 | mongoose.connect(configDB.url); // connect to our database 10 | var Hardware = require('../app/models/hardware'); 11 | //var ReducedLog = require('./app/models/reducedlog'); 12 | var Setting = require('../app/models/appsetting'); 13 | 14 | var exports = module.exports = {}; 15 | 16 | 17 | var clearAllSessions = function(callback) { 18 | db.sessions.remove({}, function(err, updated) { 19 | if (err || !updated) brewlog.log("sessions not deleted:" + err); 20 | else brewlog.log("all sessions cleared."); 21 | }); 22 | }; 23 | 24 | exports.restoreFactorySettings = function(str) { 25 | 26 | clearAllSessions(); 27 | brewlog.log("restoring default settings..."); 28 | 29 | // delete all recips 30 | db.recipes.remove({}, function(err) { 31 | if (err) brewlog.log("recipe not deleted:" + err); 32 | else brewlog.log("all recipes deleted"); 33 | }); 34 | 35 | var defaultHardwares = [{ 36 | "description": { 37 | "enen": "Kühlschrank für Gärung", 38 | "dede": "Frigde for fermentation" 39 | }, 40 | "name": "Kühlschrank", 41 | "pd": { 42 | "manual": false, 43 | "kp": 0.9, 44 | "kd": 2, 45 | "hysteresis": 0.3 46 | } 47 | }, 48 | { 49 | "description": { 50 | "dede": "Hendi Induktionskocher mit 3.5kW Heizleistung mit Topf", 51 | "enen": "Hendi induction plate with 3.5kW heating power with a pot" 52 | }, 53 | "name": "Hendi 3.5kW", 54 | "pd": { 55 | "hysteresis": 0.3, 56 | "kd": 1.6, 57 | "kp": 0.99, 58 | "manual": false 59 | } 60 | }, 61 | { 62 | "description": { 63 | "dede": "Weck Einkochautomat mit 2kW integrierter Heizleistung", 64 | "enen": "Weck Pot with a integrated 2kW heating" 65 | }, 66 | "name": "Weck 2kW", 67 | "pd": { 68 | "hysteresis": 0.5, 69 | "kd": 1.5, 70 | "kp": 0.99, 71 | "manual": false 72 | } 73 | } 74 | ]; 75 | 76 | var defaultRecipes = [{ 77 | "name": "Hauptgärung", 78 | "description": "Dieses Rezept ist zur Steuerung des Kühlschranks oder der Gefriertruhe bei der Gärung bestimmt.\n\nDies erfolgt durch Auswählen des \"Kühlen\"-Modus.", 79 | "lastEdited": "2015-04-12T17:48:36.194Z", 80 | "mode": "cool", 81 | "steps": [{ 82 | "name": "Gärung", 83 | "stirr": false, 84 | "targetTemperature": 10, 85 | "timeLimit": 5760, 86 | "endStepBy": "time" 87 | }, 88 | { 89 | "name": "Gärung", 90 | "stirr": false, 91 | "targetTemperature": 7, 92 | "timeLimit": 5760, 93 | "endStepBy": "time" 94 | }, 95 | { 96 | "name": "Diacetyl-Rast", 97 | "stirr": false, 98 | "targetTemperature": 15, 99 | "timeLimit": 1440, 100 | "endStepBy": "time" 101 | }, 102 | { 103 | "name": "Auslagern", 104 | "stirr": false, 105 | "targetTemperature": 0, 106 | "timeLimit": 442800, 107 | "endStepBy": "never" 108 | } 109 | ] 110 | }, 111 | { 112 | "name": "Helles", 113 | "description": "Beispielrezept für ein Helles angelehnt an http://www.maischemalzundmehr.de/index.php?id=101&inhaltmitte=recipe. Hier können zudem Kommentare wie Malz- und Hopfenmengen hinterlegt werden.\n\nGüsse für 24 Ausschlag\nHauptguss: 20 Liter\nNachguss: 13 Liter\n\nPilsner Malz: 2.5 kg (50%)\nWiener Malz: 2.2 kg (44%)\nCarahell®: 0.3 kg (6%)\nGesamtschüttung: 5 kg\n\nMagnum: 8 g, 15% α-Säure, 10min nach Start\nSpalter Select: 16 g, 5.3% α-Säure, 10 min vor Ende\n\nDies erfolgt durch Auswählen des \"Heizen\"-Modus.", 114 | "lastEdited": "2014-10-13T10:56:41.762Z", 115 | "mode": "heat", 116 | "steps": [{ 117 | "name": "Einmaischtemperatur anfahren und halten", 118 | "stirr": true, 119 | "targetTemperature": 37, 120 | "timeLimit": 0, 121 | "endStepBy": "never" 122 | }, 123 | { 124 | "name": "Maischarbeiten", 125 | "stirr": "false", 126 | "targetTemperature": 0, 127 | "timeLimit": 0, 128 | "endStepBy": "never" 129 | }, 130 | { 131 | "name": "Einmaischrast", 132 | "stirr": true, 133 | "targetTemperature": 35, 134 | "timeLimit": 20, 135 | "endStepBy": "time" 136 | }, 137 | { 138 | "name": "Eweisrast", 139 | "stirr": true, 140 | "targetTemperature": 55, 141 | "timeLimit": 15, 142 | "endStepBy": "time" 143 | }, 144 | { 145 | "name": "Beta-Rast (Maltose)", 146 | "stirr": true, 147 | "targetTemperature": 63, 148 | "timeLimit": 30, 149 | "endStepBy": "time" 150 | }, 151 | { 152 | "name": "Alpha-Rast (Verzuckerungsrast)", 153 | "stirr": true, 154 | "targetTemperature": 73, 155 | "timeLimit": 30, 156 | "endStepBy": "time" 157 | }, 158 | { 159 | "name": "Läuterrast (Halten bis Überpumpen in Läuterbottich)", 160 | "stirr": true, 161 | "targetTemperature": 78, 162 | "timeLimit": 20, 163 | "endStepBy": "time" 164 | }, 165 | { 166 | "name": "Abläutern", 167 | "stirr": true, 168 | "targetTemperature": 0, 169 | "timeLimit": 0, 170 | "endStepBy": "never" 171 | }, 172 | { 173 | "name": "Pause (Läuterarbeiten)", 174 | "stirr": false, 175 | "targetTemperature": 0, 176 | "timeLimit": 0, 177 | "endStepBy": "never" 178 | }, 179 | { 180 | "name": "Würzekochen", 181 | "stirr": false, 182 | "targetTemperature": 110, 183 | "timeLimit": 70, 184 | "endStepBy": "time" 185 | }, 186 | { 187 | "name": "Nachbereitung", 188 | "stirr": false, 189 | "targetTemperature": 0, 190 | "timeLimit": 0, 191 | "endStepBy": "never" 192 | } 193 | ] 194 | } 195 | ]; 196 | 197 | var hardwareSelected = false; 198 | Hardware.remove(function(err) { 199 | if (err) { 200 | brewlog.log("failed deleting all hardwares: " + err); 201 | } else { 202 | brewlog.log("all hardwares deleted"); 203 | defaultHardwares.forEach(function(elem, idx, array) { 204 | common.upsertHardware(elem, function(err, upsertedHardware) { 205 | if (err) { 206 | brewlog.log('failed to upsert hardware: ' + err); 207 | } else { 208 | brewlog.log("added hardware #" + (idx + 1)); 209 | 210 | if (hardwareSelected == false) { 211 | hardwareSelected = true; 212 | brewlog.log("selecting first hardware..."); 213 | // add first hardware to app settings 214 | Hardware.findOne(function(err, hw) { 215 | if (err) { 216 | brewlog.log('failed getting new hardware:' + err); 217 | } else { 218 | brewlog.log("reveived hardware from database."); 219 | var defaultSettings = defaults.defaultSettings; 220 | defaultSettings.selectedHardware = hw._id; 221 | 222 | Setting.findOne(function(err, appSettings) { 223 | if (err) { 224 | callback(err, null); 225 | } else { 226 | Hardware.find(function(err, hardwares) { 227 | if (err) { 228 | callback(err, null); 229 | } else { 230 | if (appSettings == null) { 231 | brewlog.log("no settings found, creating default..."); 232 | appSettings = defaultSettings; 233 | } 234 | for (var k in defaultSettings) { 235 | brewlog.log("replacing key=" + k); 236 | appSettings[k] = defaultSettings[k]; 237 | } 238 | 239 | common.updateAppSettings(appSettings, function(err, updatedSettings) { 240 | brewlog.log("updateAppSettings callback."); 241 | if (err) { 242 | brewlog.log('failed updating app settings:' + err); 243 | } else { 244 | brewlog.log('selected new hardware: ' + hw.name); 245 | 246 | 247 | common.addRecipe(defaultRecipes[0], function(err, recipe) { 248 | if (err) { 249 | brewlog.log("failed adding recipe: " + err); 250 | } else { 251 | common.addRecipe(defaultRecipes[1], function(err, recipe) { 252 | if (err) { 253 | brewlog.log("failed adding recipe: " + err); 254 | } else { 255 | brewlog.log("closing db"); 256 | closeDb(); 257 | process.exit(); 258 | } 259 | }); 260 | } 261 | }); 262 | 263 | } 264 | }) 265 | } 266 | }); 267 | } 268 | }); 269 | } 270 | }); 271 | } 272 | } 273 | }); 274 | }); 275 | } 276 | }); 277 | }; 278 | 279 | var closeDb = function() { 280 | mongoose.connection.close(); 281 | } 282 | 283 | exports.clearAllSessions = clearAllSessions; 284 | exports.closeDb = closeDb; 285 | -------------------------------------------------------------------------------- /client/partials/auto.html: -------------------------------------------------------------------------------- 1 |
2 |

{{'NAV_AUTO' | translate}}

3 |
4 | {{ 'AUTO_NOTLOADED_1' | translate }} {{'NAV_RECIPES' | translate}} 5 | {{ 'AUTO_NOTLOADED_2' | translate }} 6 | {{ 'AUTO_NOTLOADED_3' | translate }} 7 | 8 | {{ 'AUTO_NOTLOADED_4' | translate }} 9 |
10 |
11 |
12 |
13 | 14 |
15 | 16 | l 17 |
18 |
19 |
20 | 22 |
23 |
24 |
25 |
26 |
27 | 28 | 31 | 34 | 35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 | 46 | 49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
{{ 'SUDNUMBER' | translate }}{{recipe.sudNumber}}
{{ 'AUTO_RECIPESTARTED' | translate }}{{recipe.started | date : 'dd.MM.yyyy HH:mm:ss' }}
{{ 'AUTO_STEPSTARTED' | translate }}{{currentStep.started | date : 'HH:mm:ss' }}
{{ 'AUTO_TEMPREACHED' | translate }}{{currentStep.tempReached | date : 'HH:mm:ss' }}{{estimatedTempReached | date : 'HH:mm:ss' }} (est.)
{{ 'AUTO_STEPFINISHED' | translate }}{{stepEstFinish | date : 'HH:mm:ss' }}{{stepEstFinish | date : 'HH:mm:ss' }} (est.)
{{ 'AUTO_TIMEREMAINING' | translate }}{{remainingTime}}{{remainingTime}} (est.)
{{ 'AUTO_CURTEMP' | translate }}{{temp1 | number : 1}}°C
{{ 'TTEMP' | translate }}{{currentStep.targetTemperature | number : 1}}°C
97 |
98 |
99 |
100 | 101 |
102 |
103 |
104 |
105 |

106 | {{ 'TEMPERATUREHISTORY' | translate }} 107 |

108 |
109 |
110 |
111 | 112 | 113 |
114 |
115 |
116 |
117 |
118 | 119 |
120 |
121 | 122 |
123 |
124 | 125 |
126 |
127 |
128 |
129 |

130 | {{ 'AUTO_LOGS' | translate}} 131 |

132 |
133 |
134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
{{comment[0]| date : 'dd.MM.yyyy HH:mm:ss' }}{{comment[1]}}
143 | 146 |
147 |
148 |
149 |
150 |
151 | 152 | 153 | 154 | 171 | 172 | 173 | 174 | 209 | 210 | 211 | 236 |
237 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BierBot {{bierBotName}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 88 | 89 | 105 | 106 | 107 | 108 |
109 | 113 |
114 |
115 | 116 | 133 | 134 | 202 | 203 |
204 | 205 |
206 |
207 | 208 | 212 | 213 | 217 | 218 | 222 |
223 | 224 |
225 | 226 | 227 |
228 | 247 |
248 | 249 |
250 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /server/libs/version.js: -------------------------------------------------------------------------------- 1 | // Versioning 2 | var brewlog = require('./brewlog'); 3 | var util = require('util'); // util.inspect(updatedRecipe, false, null) 4 | var https = require('https'); 5 | var fs = require('fs'); 6 | var uidNumber = require("uid-number"); 7 | var exec = require('child_process').exec; 8 | var mkdirp = require('mkdirp'); 9 | 10 | // settings 11 | var version = '0.0.0'; // own version 12 | var settingUpdateURLs = [ 13 | "https://raw.githubusercontent.com/BernhardSchlegel/BierBot/master/server/libs/version-self.json", 14 | ]; 15 | 16 | // variables 17 | var versionFilename = __dirname + '/version-self.json'; 18 | var cpuid = ""; 19 | var newVersionCallback = null; // newVersionCallback(rarpath) 20 | var finalizeUpdateCallback = null; 21 | var updatePath = null; 22 | var rootdir = __dirname + '/../../'; // node process runs in /server - navigate one up 23 | 24 | var exports = module.exports = {}; 25 | 26 | var setUpdateFinished = function() { 27 | brewlog.log('preparing version file ...'); 28 | 29 | var versionData = { 30 | updateFinished: true, 31 | version: version 32 | } 33 | 34 | 35 | fs.writeFile(versionFilename, JSON.stringify(versionData, null, 4), function(err) { 36 | if (err) { 37 | brewlog.log(err); 38 | } else { 39 | brewlog.log("JSON saved to " + versionFilename); 40 | uidNumber("pi", function(err, uid, gid) { 41 | // gid is null because we didn't ask for a group name 42 | // uid === 24561 because that's my number. 43 | if (!err) { 44 | fs.chownSync(versionFilename, uid, gid); 45 | } 46 | }); 47 | } 48 | }); 49 | } 50 | 51 | // callback(err, true) in case it is 52 | var updateIsFinalized = function(callback) { 53 | fs.readFile(versionFilename, function(err, data) { 54 | if (err) { 55 | brewlog.log('error reading version file: ' + err); 56 | callback(err); 57 | } else { 58 | 59 | var jsonData = JSON.parse(data); 60 | callback(null, jsonData.updateFinished); 61 | } 62 | }); 63 | } 64 | 65 | //callback(err, "0.1.2") 66 | var getVersionFromFile = function(callback) { 67 | fs.readFile(versionFilename, function(err, data) { 68 | if (err) { 69 | brewlog.log('error reading version file: ' + err); 70 | callback(err); 71 | } else { 72 | var jsonData = JSON.parse(data); 73 | callback(null, jsonData.version); 74 | } 75 | }); 76 | } 77 | 78 | exports.init = function(owncpuid, newVersionCallbackInit, finalizeUpdateCallbackInit) { 79 | cpuid = owncpuid; 80 | newVersionCallback = newVersionCallbackInit; 81 | finalizeUpdateCallback = finalizeUpdateCallbackInit; 82 | 83 | // get own 84 | getVersionFromFile(function(err, versionFromFile) { 85 | if (!err) { 86 | version = versionFromFile; 87 | brewlog.log('auto update initialized (' + cpuid + ', ' + version + ')'); 88 | 89 | // if its the first start with the new version 90 | updateIsFinalized(function(err, finished) { 91 | if (finished == false) { 92 | brewlog.log('finalizing update ...'); 93 | if (finalizeUpdateCallback) { 94 | finalizeUpdateCallback(); 95 | setUpdateFinished(); 96 | } 97 | } 98 | }); 99 | 100 | // check for new version once 101 | checkForNewVersion(); 102 | 103 | // set intervall 104 | var checkVersionIntervalID = setInterval(function() { 105 | checkForNewVersion(); 106 | }, 24 * 60 * 60 * 1000); // check once a day, carefull that check isn't called twice 107 | } 108 | }); 109 | } 110 | 111 | exports.getVersion = function() { 112 | return version; 113 | } 114 | exports.dbg = function() {}; 115 | 116 | exports.chmod = function(callback) { 117 | uidNumber("pi", function(err, uid, gid) { 118 | // gid is null because we didn't ask for a group name 119 | // uid === 24561 because that's my number. 120 | if (!err) { 121 | brewlog.log('user id of \"pi\" is ' + uid + ', groupid is ' + gid); 122 | 123 | 124 | // make all files in /home/BierBot/brew/sys executable 125 | // sth like "sudo chmod -R u+x /home/pi/BierBot/sys" 126 | var chmodSysCommand = 'sudo chmod -R u+x ' + ' ' + rootdir + 'sys/'; 127 | brewlog.log("chmodding sys using \"" + chmodSysCommand + "\""); 128 | var chownProcess = exec(chmodSysCommand); 129 | chownProcess.stdout.on('data', function(data) { 130 | brewlog.log('stdout: ' + data); 131 | }); 132 | chownProcess.stderr.on('data', function(data) { 133 | brewlog.log('error chwoning sys: ' + data); 134 | }); 135 | chownProcess.on('close', function(code, signal) { 136 | 137 | // change owner to pi 138 | // sth like "sudo chown -R 1000:0 /home/pi/BierBot" 139 | var chownCommand = 'sudo chown -R ' + uid + ':' + gid + ' ' + rootdir + ''; 140 | brewlog.log("chowning using \"" + chownCommand + "\""); 141 | var chownProcess = exec(chownCommand); 142 | chownProcess.stdout.on('data', function(data) { 143 | brewlog.log('stdout: ' + data); 144 | }); 145 | chownProcess.stderr.on('data', function(data) { 146 | brewlog.log('error chwoning: ' + data); 147 | }); 148 | chownProcess.on('close', function(code, signal) { 149 | 150 | // grant write permission to pi 151 | // sth like "sudo chmod -R u+w /home/pi/BierBot/" 152 | var chmodCommand = 'sudo chmod -R u+w ' + rootdir + ''; 153 | brewlog.log("chmodding using \"" + chmodCommand + "\""); 154 | var chownProcess = exec(chmodCommand); 155 | chownProcess.stdout.on('data', function(data) { 156 | brewlog.log('stdout: ' + data); 157 | }); 158 | chownProcess.stderr.on('data', function(data) { 159 | brewlog.log('error chmodding: ' + data); 160 | }); 161 | chownProcess.on('close', function(code, signal) { 162 | 163 | if (callback != null) { 164 | callback(); 165 | } 166 | }); 167 | }); 168 | }); 169 | } else { 170 | installing = false; 171 | callback('update failed: ' + err); 172 | } 173 | }); 174 | 175 | } 176 | 177 | // callback(err) 178 | var installing = false; 179 | exports.installUpdate = function(updatePath, callback) { 180 | if (installing == false) { 181 | installing = true; 182 | if (updatePath) { 183 | brewlog.log('installing update...'); 184 | 185 | // overwrite all existing 186 | var targetFolder = __dirname + '/../../../BierBotUpdate'; 187 | brewlog.log('unzipping ' + updatePath + ' to ' + targetFolder + '...'); 188 | var process = exec('unzip -o ' + updatePath + ' -d ' + targetFolder); 189 | process.stdout.on('data', function(data) { 190 | //brewlog.log('unzipping: ' + data); 191 | }); 192 | process.stderr.on('data', function(data) { 193 | brewlog.log('error unzipping: ' + data); 194 | }); 195 | process.on('close', function(code, signal) { 196 | if (code == 0) { 197 | brewlog.log('unzipping finished with code ' + code + '...'); 198 | 199 | var zipOutputFolder = targetFolder + "/BierBot-master"; 200 | brewlog.log("moving zip output folder " + zipOutputFolder); 201 | var moveProcess = exec('rsync -a ' + zipOutputFolder + '/ /home/pi/BierBotUpdate2/'); // TODO replace 202 | 203 | moveProcess.stdout.on('data', function(data) { 204 | brewlog.log('mv: ' + data); 205 | }); 206 | moveProcess.stderr.on('data', function(data) { 207 | brewlog.log('error moving: ' + data); 208 | }); 209 | moveProcess.on('close', function(code, signal) { 210 | exports.chmod(function() { 211 | brewlog.log('update finished!'); 212 | if (finalizeUpdateCallback != null) { 213 | finalizeUpdateCallback(); 214 | } 215 | installing = false; 216 | callback(null); 217 | }); 218 | }); 219 | } else { 220 | brewlog.log('unzipping finished with code ' + code + '...'); 221 | } 222 | }); 223 | } 224 | } else { 225 | callback('update already installing'); 226 | } 227 | } 228 | 229 | function IsJsonString(str) { 230 | try { 231 | JSON.parse(str); 232 | } catch (e) { 233 | return false; 234 | } 235 | return true; 236 | } 237 | 238 | // callback(false) = fail, callback(true) = success 239 | var checkURL = function(requestURL, callback) { 240 | 241 | brewlog.log('checking for new version ... (' + requestURL + ')'); 242 | 243 | https.get(requestURL, function(res) { 244 | var body = ''; 245 | 246 | res.on('data', function(chunk) { 247 | body += chunk; 248 | }); 249 | 250 | res.on('end', function() { 251 | if (IsJsonString(body) == false) { 252 | brewlog.log('string no json string. returning ...'); 253 | callback(false); 254 | return; 255 | } 256 | callback(true); 257 | var response = JSON.parse(body); 258 | brewlog.log('got response: ' + util.inspect(response, false, null)); 259 | brewlog.log('own version: ' + version); 260 | 261 | if (firstIsHigher(extract(response.version), extract(version))) { 262 | var versionFromGithub = extract(response.version); 263 | var versionStr = versionFromGithub.major + '.' + versionFromGithub.minor + '.' + versionFromGithub.build; 264 | brewlog.log('new version (' + versionStr + ') available ... '); 265 | var downloadURL = response.url; 266 | 267 | 268 | mkdirp(__dirname + '/../../update/', function(err) { 269 | 270 | if (err) { 271 | brewlog.log("error creating update path: " + err); 272 | } else { 273 | updateTempPath = __dirname + '/../../update/' + versionStr + '_temp' + '.zip'; 274 | updatePath = __dirname + '/../../update/' + versionStr + '.zip'; 275 | fs.exists(updatePath, function(exists) { 276 | if (exists == true) { 277 | brewlog.log('file (' + updatePath + ') already existing ... skipping download') 278 | if (newVersionCallback) { 279 | newVersionCallback(updatePath, versionStr); 280 | } 281 | } else { 282 | brewlog.log('update not available on fs, starting download ...'); 283 | var file = fs.createWriteStream(updateTempPath); 284 | var request = https.get(downloadURL, function(response) { 285 | response.pipe(file); 286 | 287 | response.on('end', function() { 288 | brewlog.log('downloading update ... finished'); 289 | fs.renameSync(updateTempPath, updatePath); 290 | if (newVersionCallback) { 291 | newVersionCallback(updatePath); 292 | } 293 | }); 294 | }); 295 | } 296 | }); 297 | } 298 | 299 | }); 300 | 301 | } 302 | }); 303 | }).on('error', function(e) { 304 | brewlog.log("Got error: ", e); 305 | callback(false); 306 | }); 307 | }; 308 | 309 | var checkForNewVersion = function() { 310 | checkURL(settingUpdateURLs[0], function(value) { 311 | brewlog.log("checking url " + settingUpdateURLs[0] + " failed. total fail.") 312 | }); 313 | }; 314 | 315 | exports.checkForNewVersion = checkForNewVersion; 316 | 317 | 318 | // UTILS ================================================================== 319 | 320 | // version has to be separated styled liked 321 | // bierbottype.major.minor.build.fileending 322 | var extract = function(string) { 323 | var extracted = string.split("."); 324 | 325 | return { 326 | major: parseInt(extracted[0]), 327 | minor: parseInt(extracted[1]), 328 | build: parseInt(extracted[2]) 329 | }; 330 | }; 331 | 332 | var firstIsHigher = function(version1, version2) { 333 | if (version1.major < version2.major) { 334 | return false; 335 | } else if (version1.major == version2.major) { 336 | 337 | if (version1.minor < version2.minor) { 338 | return false; 339 | } else if (version1.minor == version2.minor) { 340 | 341 | if (version1.build < version2.build) { 342 | return false; 343 | } else if (version1.build == version2.build) { 344 | return false; 345 | } else { 346 | return true; 347 | } 348 | } else { 349 | return true; 350 | } 351 | } else { 352 | return true; 353 | } 354 | } 355 | --------------------------------------------------------------------------------