From 649e4522bb1bcc3ba913ce8cc28799877bb6df73 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 1 May 2018 18:29:55 +0200 Subject: [PATCH 01/25] Added writing to Time Series Database KairosDB --- config/logger.yaml | 19 +++++++++++ modules/app_config.py | 17 +++------- modules/core/core.py | 21 +++++++++--- modules/logs/endpoints.py | 70 ++++++++++++++++++++++++++++++++++----- 4 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 config/logger.yaml diff --git a/config/logger.yaml b/config/logger.yaml new file mode 100644 index 0000000..4b28e6f --- /dev/null +++ b/config/logger.yaml @@ -0,0 +1,19 @@ +version: 1 +formatters: + simple: + format: '%(asctime)s - %(levelname)s - %(name)s - %(message)s' +handlers: + console: + class: logging.StreamHandler + level: DEBUG + formatter: simple + stream: ext://sys.stdout + file: + class : logging.handlers.RotatingFileHandler + formatter: simple + filename: ./logs/app.log + maxBytes: 10000000 + backupCount: 3 +root: + level: DEBUG + handlers: [console, file] \ No newline at end of file diff --git a/modules/app_config.py b/modules/app_config.py index 96a5e0c..6552202 100644 --- a/modules/app_config.py +++ b/modules/app_config.py @@ -1,23 +1,16 @@ +from flask import Flask, json, g +from flask_socketio import SocketIO -import json -import sys, os -from flask import Flask, render_template, redirect, json, g - - -from flask_socketio import SocketIO, emit - -import logging - - +import yaml +import logging.config from modules.core.core import CraftBeerPi, ActorBase, SensorBase from modules.core.db import DBModel app = Flask(__name__) -FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s' +logging.config.dictConfig(yaml.load(open('./config/logger.yaml', 'r'))) -logging.basicConfig(filename='./logs/app.log',level=logging.INFO, format=FORMAT) app.config['SECRET_KEY'] = 'craftbeerpi' app.config['UPLOAD_FOLDER'] = './upload' diff --git a/modules/core/core.py b/modules/core/core.py index c4b529d..915dcfa 100644 --- a/modules/core/core.py +++ b/modules/core/core.py @@ -1,16 +1,14 @@ import inspect -import pprint -import sqlite3 from flask import make_response, g import datetime from datetime import datetime -from flask.views import MethodView -from flask_classy import FlaskView, route from time import localtime, strftime from functools import wraps, update_wrapper +import requests +import json from props import * @@ -149,13 +147,26 @@ class SensorAPI(object): self.save_to_file(id, value) def save_to_file(self, id, value, prefix="sensor"): - filename = "./logs/%s_%s.log" % (prefix, str(id)) + sensor_name = "%s_%s" % (prefix, str(id)) + filename = "./logs/%slog" % sensor_name formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) msg = str(formatted_time) + "," +str(value) + "\n" with open(filename, "a") as file: file.write(msg) + kairosdb_server = "http://192.168.178.20:5001" + + data = [ + dict(name=sensor_name, datapoints=[ + [int(round(time.time() * 1000)), value] + ], tags={ + "cbpi": prefix + }) + ] + + response = requests.post(kairosdb_server + "/api/v1/datapoints", json.dumps(data)) + def log_action(self, text): filename = "./logs/action.log" formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) diff --git a/modules/logs/endpoints.py b/modules/logs/endpoints.py index 5f8b29e..384277d 100644 --- a/modules/logs/endpoints.py +++ b/modules/logs/endpoints.py @@ -1,11 +1,16 @@ import datetime import os -from flask import Blueprint, request, send_from_directory, json +import time +import requests +import logging +from flask import request, send_from_directory, json from flask_classy import FlaskView, route from modules import cbpi class LogView(FlaskView): + def __init__(self): + self.logger = logging.getLogger(__name__) @route('/', methods=['GET']) def get_all_logfiles(self): @@ -18,18 +23,24 @@ class LogView(FlaskView): @route('/actions') def actions(self): filename = "./logs/action.log" - if os.path.isfile(filename) == False: - return + if not os.path.isfile(filename): + self.logger.warn("File does not exist [%s]", filename) + return json.dumps([]) import csv array = [] with open(filename, 'rb') as f: reader = csv.reader(f) for row in reader: try: - array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, row[1]]) + time.mktime(time.strptime(row[0], "%Y-%m-%d %H:%M:%S")) + array.append([int(time.mktime(time.strptime(row[0], "%Y-%m-%d %H:%M:%S")) * 1000), row[1]]) except: pass - return json.dumps(array) + + json_dumps = json.dumps(array) + self.logger.debug("Loaded action.log [%s]", json_dumps) + + return json_dumps @route('/', methods=["DELETE"]) def clearlog(self, file): @@ -49,7 +60,43 @@ class LogView(FlaskView): cbpi.notify("Failed to delete log", "", type="danger") return ('', 204) + def querry_tsdb(self, type, id): + kairosdb_server = "http://192.168.178.20:5001" + + data = dict(metrics=[ + { + "tags": {}, + "name": "%s_%s" % (type, id), + "aggregators": [ + { + "name": "avg", + "align_sampling": True, + "sampling": { + "value": "1", + "unit": "minutes" + }, + "align_start_time": True + } + ] + } + ], + cache_time=0, + start_relative={ + "value": "1", + "unit": "days" + }) + + response = requests.post(kairosdb_server + "/api/v1/datapoints/query", json.dumps(data)) + if response.ok: + self.logger.debug("Fetching time series for [%s_%s] took [%s]", type, id, response.elapsed) + self.logger.debug("Time series for [%s_%s] is [%s]", type, id, response.json()) + return response.json()["queries"][0]["results"][0]["values"] + else: + self.logger.warning("Failed to fetch time series for [%s_%s]. Response [%s]", type, id, response) + def read_log_as_json(self, type, id): + return self.querry_tsdb(type, id) + filename = "./logs/%s_%s.log" % (type, id) if os.path.isfile(filename) == False: return @@ -60,13 +107,18 @@ class LogView(FlaskView): reader = csv.reader(f) for row in reader: try: - array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, float(row[1])]) + array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, + 1, + 1)).total_seconds()) * 1000, + float(row[1])]) except: pass + print(array) return array def convert_chart_data_to_json(self, chart_data): - return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])} + return {"name": chart_data["name"], + "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])} @route('//', methods=["POST"]) def get_logs_as_json(self, t, id): @@ -82,7 +134,8 @@ class LogView(FlaskView): if t == "f": fermenter = cbpi.cache.get("fermenter").get(id) - result = map(self.convert_chart_data_to_json, cbpi.get_fermentation_controller(fermenter.logic).get("class").chart(fermenter)) + result = map(self.convert_chart_data_to_json, + cbpi.get_fermentation_controller(fermenter.logic).get("class").chart(fermenter)) return json.dumps(result) @@ -99,6 +152,7 @@ class LogView(FlaskView): return True if pattern.match(name) else False + @cbpi.initalizer() def init(app): """ From 093d486f8e7133a3b1eb1899e951e3a0640bfd6c Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 2 May 2018 12:29:17 +0200 Subject: [PATCH 02/25] Added writing to Time Series Database KairosDB --- .gitignore | 1 + modules/core/core.py | 33 +++++++++++++++++++-------------- modules/logs/endpoints.py | 18 ++++++++++++------ update/4_kairosdb_config.sql | 2 ++ 4 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 update/4_kairosdb_config.sql diff --git a/.gitignore b/.gitignore index 928ac45..67cd145 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ yarn.lock modules/ui/package-lock.json +logs/ diff --git a/modules/core/core.py b/modules/core/core.py index 915dcfa..368f412 100644 --- a/modules/core/core.py +++ b/modules/core/core.py @@ -148,24 +148,29 @@ class SensorAPI(object): def save_to_file(self, id, value, prefix="sensor"): sensor_name = "%s_%s" % (prefix, str(id)) - filename = "./logs/%slog" % sensor_name - formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) - msg = str(formatted_time) + "," +str(value) + "\n" + use_kairosdb = (self.cache["config"]["kairos_db"].__dict__["value"] == "YES") - with open(filename, "a") as file: - file.write(msg) + if use_kairosdb: + kairosdb_server = "http://127.0.0.1:" + self.cache["config"]["kairos_db_port"].__dict__["value"] + + data = [ + dict(name="cbpi." + sensor_name, datapoints=[ + [int(round(time.time() * 1000)), value] + ], tags={ + "cbpi": prefix + }) + ] + + response = requests.post(kairosdb_server + "/api/v1/datapoints", json.dumps(data)) + else: + filename = "./logs/%s.log" % sensor_name + formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) + msg = str(formatted_time) + "," +str(value) + "\n" - kairosdb_server = "http://192.168.178.20:5001" + with open(filename, "a") as file: + file.write(msg) - data = [ - dict(name=sensor_name, datapoints=[ - [int(round(time.time() * 1000)), value] - ], tags={ - "cbpi": prefix - }) - ] - response = requests.post(kairosdb_server + "/api/v1/datapoints", json.dumps(data)) def log_action(self, text): filename = "./logs/action.log" diff --git a/modules/logs/endpoints.py b/modules/logs/endpoints.py index 384277d..fea6865 100644 --- a/modules/logs/endpoints.py +++ b/modules/logs/endpoints.py @@ -61,12 +61,12 @@ class LogView(FlaskView): return ('', 204) def querry_tsdb(self, type, id): - kairosdb_server = "http://192.168.178.20:5001" + kairosdb_server = "http://127.0.0.1:" + cbpi.cache["config"]["kairos_db_port"].__dict__["value"] data = dict(metrics=[ { "tags": {}, - "name": "%s_%s" % (type, id), + "name": "cbpi.%s_%s" % (type, id), "aggregators": [ { "name": "avg", @@ -94,9 +94,7 @@ class LogView(FlaskView): else: self.logger.warning("Failed to fetch time series for [%s_%s]. Response [%s]", type, id, response) - def read_log_as_json(self, type, id): - return self.querry_tsdb(type, id) - + def querry_log(self, type, id): filename = "./logs/%s_%s.log" % (type, id) if os.path.isfile(filename) == False: return @@ -113,9 +111,17 @@ class LogView(FlaskView): float(row[1])]) except: pass - print(array) return array + def read_log_as_json(self, type, id): + use_kairosdb = (cbpi.cache["config"]["kairos_db"].__dict__["value"] == "YES") + + if use_kairosdb: + return self.querry_tsdb(type, id) + else: + return self.querry_log(type, id) + + def convert_chart_data_to_json(self, chart_data): return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])} diff --git a/update/4_kairosdb_config.sql b/update/4_kairosdb_config.sql new file mode 100644 index 0000000..35e0af8 --- /dev/null +++ b/update/4_kairosdb_config.sql @@ -0,0 +1,2 @@ +INSERT OR IGNORE INTO config VALUES ('kairos_db', 'NO', 'select', 'Use timeseries database KairosDB for storing sensor values. For now you have to install KairosDB by yourself.', '["YES","NO"]' ); +INSERT OR IGNORE INTO config VALUES ('kairos_db_port', 5001, 'number', 'Port for KairosDB. We assume the DB is running on your PI, so IP-Address is 127.0.0.1.', NULL ); \ No newline at end of file From 47d6e76ea4fc337bd270306fb4cf2dedde0ab9e0 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 3 May 2018 20:49:03 +0200 Subject: [PATCH 03/25] added KairosDB to installer --- install.sh | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/install.sh b/install.sh index 82193cb..a125029 100755 --- a/install.sh +++ b/install.sh @@ -20,7 +20,8 @@ show_menu () { "8" "Reset File Changes (git reset --hard)" \ "9" "Clear all logs" \ "10" "Reboot Raspberry Pi" \ - "11" "Stop CraftBeerPi, Clear logs, Start CraftBeerPi" 3>&1 1>&2 2>&3) + "11" "Stop CraftBeerPi, Clear logs, Start CraftBeerPi" \ + "12" "Install KairosDB" 3>&1 1>&2 2>&3) BUTTON=$? # Exit if user pressed cancel or escape @@ -49,7 +50,7 @@ show_menu () { apt-get -y install libpcre3-dev pip install -r requirements.txt - confirmAnswer "Would you like to add active 1-wire support at your Raspberry PI now? IMPORTANT: The 1-wire thermometer must be conneted to GPIO 4!" + confirmAnswer "Would you like to add active 1-wire support at your Raspberry PI now? IMPORTANT: The 1-wire thermometer must be connected to GPIO 4!" if [ $? = 0 ]; then #apt-get -y update; apt-get -y upgrade; echo '# CraftBeerPi 1-wire support' >> "/boot/config.txt" @@ -62,14 +63,14 @@ show_menu () { sed "s@#DIR#@${PWD}@g" config/craftbeerpiboot > /etc/init.d/craftbeerpiboot chmod 755 /etc/init.d/craftbeerpiboot; - whiptail --title "Installition Finished" --msgbox "CraftBeerPi installation finished! You must hit OK to continue." 8 78 + whiptail --title "Installation Finished" --msgbox "CraftBeerPi installation finished! You must hit OK to continue." 8 78 show_menu ;; 2) confirmAnswer "Are you sure you want to clear the CraftBeerPi. All hardware setting will be deleted" if [ $? = 0 ]; then sudo rm -f craftbeerpi.db - whiptail --title "Database Delted" --msgbox "The CraftBeerPi database was succesfully deleted. You must hit OK to continue." 8 78 + whiptail --title "Database Deleted" --msgbox "The CraftBeerPi database was successfully deleted. You must hit OK to continue." 8 78 show_menu else show_menu @@ -81,7 +82,7 @@ show_menu () { sed "s@#DIR#@${PWD}@g" config/craftbeerpiboot > /etc/init.d/craftbeerpiboot chmod 755 /etc/init.d/craftbeerpiboot; update-rc.d craftbeerpiboot defaults; - whiptail --title "Added succesfull to autostart" --msgbox "The CraftBeerPi was added to autostart succesfully. You must hit OK to continue." 8 78 + whiptail --title "Added successful to autostart" --msgbox "The CraftBeerPi was added to autostart successfully. You must hit OK to continue." 8 78 show_menu else show_menu @@ -104,7 +105,7 @@ show_menu () { ;; 6) sudo /etc/init.d/craftbeerpiboot stop - whiptail --title "CraftBeerPi stoped" --msgbox "The software is stoped" 8 78 + whiptail --title "CraftBeerPi stopped" --msgbox "The software is stopped" 8 78 show_menu ;; 7) @@ -118,7 +119,7 @@ show_menu () { fi ;; 8) - confirmAnswer "Are you sure you want to reset all file changes for this git respository (git reset --hard)?" + confirmAnswer "Are you sure you want to reset all file changes for this git repository (git reset --hard)?" if [ $? = 0 ]; then whiptail --textbox /dev/stdin 20 50 <<<"$(git reset --hard)" show_menu @@ -155,6 +156,15 @@ show_menu () { show_menu fi ;; + 12) + confirmAnswer "Are you sure you want to install KairosDB?" + if [ $? = 0 ]; then + wget https://github.com/kairosdb/kairosdb/releases/download/v1.2.1/kairosdb_1.2.1-1_all.deb + sudo dpkg -i kairosdb_1.2.1-1_all.deb + else + show_menu + fi + ;; esac fi } From 33834c68f515d326ac923c9242e5be823ecfe443 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 3 May 2018 21:05:50 +0200 Subject: [PATCH 04/25] added KairosDB to installer --- install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/install.sh b/install.sh index a125029..195e05a 100755 --- a/install.sh +++ b/install.sh @@ -161,6 +161,8 @@ show_menu () { if [ $? = 0 ]; then wget https://github.com/kairosdb/kairosdb/releases/download/v1.2.1/kairosdb_1.2.1-1_all.deb sudo dpkg -i kairosdb_1.2.1-1_all.deb + sudo service kairosdb start + show_menu else show_menu fi From ac328b843d19651c98b3c8b7ba6ee842dc6832df Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 3 May 2018 22:18:09 +0200 Subject: [PATCH 05/25] Added writing to Time Series Database KairosDB --- modules/core/core.py | 3 ++- modules/steps/__init__.py | 9 ++++++++- update/4_kairosdb_config.sql | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/core/core.py b/modules/core/core.py index 368f412..e70d04c 100644 --- a/modules/core/core.py +++ b/modules/core/core.py @@ -157,7 +157,8 @@ class SensorAPI(object): dict(name="cbpi." + sensor_name, datapoints=[ [int(round(time.time() * 1000)), value] ], tags={ - "cbpi": prefix + "cbpi": prefix, + "brew": self.cache["active_brew"] }) ] diff --git a/modules/steps/__init__.py b/modules/steps/__init__.py index 05d65fc..3b2b75f 100755 --- a/modules/steps/__init__.py +++ b/modules/steps/__init__.py @@ -1,4 +1,5 @@ import time +import datetime from flask import json, request from flask_classy import route @@ -142,9 +143,14 @@ class StepView(BaseView): # set step instance to ache cbpi.cache["active_step"] = instance - @route('/next', methods=['POST']) @route('/start', methods=['POST']) def start(self): + cbpi.cache["active_brew"] = cbpi.cache["config"]["brew_name"].__dict__["value"] + \ + "_" + datetime.datetime.now().strftime('%y-%m-%dT%H:%M') + return self.next() + + @route('/next', methods=['POST']) + def next(self): active = Step.get_by_state("A") inactive = Step.get_by_state('I') @@ -163,6 +169,7 @@ class StepView(BaseView): else: cbpi.log_action("Brewing Finished") cbpi.notify("Brewing Finished", "You are done!", timeout=None) + cbpi.cache["active_brew"] = "none" cbpi.emit("UPDATE_ALL_STEPS", Step.get_all()) return ('', 204) diff --git a/update/4_kairosdb_config.sql b/update/4_kairosdb_config.sql index 35e0af8..3415309 100644 --- a/update/4_kairosdb_config.sql +++ b/update/4_kairosdb_config.sql @@ -1,2 +1,2 @@ INSERT OR IGNORE INTO config VALUES ('kairos_db', 'NO', 'select', 'Use timeseries database KairosDB for storing sensor values. For now you have to install KairosDB by yourself.', '["YES","NO"]' ); -INSERT OR IGNORE INTO config VALUES ('kairos_db_port', 5001, 'number', 'Port for KairosDB. We assume the DB is running on your PI, so IP-Address is 127.0.0.1.', NULL ); \ No newline at end of file +INSERT OR IGNORE INTO config VALUES ('kairos_db_port', 8080, 'number', 'Port for KairosDB. We assume the DB is running on your PI, so IP-Address is 127.0.0.1.', NULL ); \ No newline at end of file From 0803e85630149d1980f3d2c86d9eb85bcded8cf4 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 4 May 2018 06:07:29 +0200 Subject: [PATCH 06/25] Added writing to Time Series Database KairosDB --- modules/logs/endpoints.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/logs/endpoints.py b/modules/logs/endpoints.py index fea6865..2505f06 100644 --- a/modules/logs/endpoints.py +++ b/modules/logs/endpoints.py @@ -63,17 +63,22 @@ class LogView(FlaskView): def querry_tsdb(self, type, id): kairosdb_server = "http://127.0.0.1:" + cbpi.cache["config"]["kairos_db_port"].__dict__["value"] + if cbpi.cache["active_brew"] != "" and cbpi.cache["active_brew"] != "none": + tag = '"brew": "%s"' % cbpi.cache["active_brew"] + else: + tag = "" + data = dict(metrics=[ { - "tags": {}, + "tags": {tag}, "name": "cbpi.%s_%s" % (type, id), "aggregators": [ { "name": "avg", "align_sampling": True, "sampling": { - "value": "1", - "unit": "minutes" + "value": "5", + "unit": "seconds" }, "align_start_time": True } @@ -86,6 +91,8 @@ class LogView(FlaskView): "unit": "days" }) + self.logger.debug("query: %s", json.dumps(data)) + response = requests.post(kairosdb_server + "/api/v1/datapoints/query", json.dumps(data)) if response.ok: self.logger.debug("Fetching time series for [%s_%s] took [%s]", type, id, response.elapsed) From c34939ff4073cf0a5e01fe809710234065d620b9 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 4 May 2018 06:12:59 +0200 Subject: [PATCH 07/25] Added writing to Time Series Database KairosDB --- modules/logs/endpoints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/logs/endpoints.py b/modules/logs/endpoints.py index 2505f06..e67ef08 100644 --- a/modules/logs/endpoints.py +++ b/modules/logs/endpoints.py @@ -63,10 +63,10 @@ class LogView(FlaskView): def querry_tsdb(self, type, id): kairosdb_server = "http://127.0.0.1:" + cbpi.cache["config"]["kairos_db_port"].__dict__["value"] + tag = "" + if cbpi.cache["active_brew"] != "" and cbpi.cache["active_brew"] != "none": tag = '"brew": "%s"' % cbpi.cache["active_brew"] - else: - tag = "" data = dict(metrics=[ { From 8e4f509e0bae1b6bf4b243cdcc3122d897107ea7 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 16 May 2018 07:48:49 +0200 Subject: [PATCH 08/25] corrected tagging of actual brew process for storing in time serires db --- modules/core/core.py | 3 ++- modules/steps/__init__.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/core/core.py b/modules/core/core.py index e70d04c..d93b9ae 100644 --- a/modules/core/core.py +++ b/modules/core/core.py @@ -222,7 +222,8 @@ class CraftBeerPi(ActorAPI, SensorAPI): "messages": [], "plugins": {}, "fermentation_controller_types": {}, - "fermenter_task": {} + "fermenter_task": {}, + "active_brew": "none" } buzzer = None eventbus = {} diff --git a/modules/steps/__init__.py b/modules/steps/__init__.py index 3b2b75f..1189d09 100755 --- a/modules/steps/__init__.py +++ b/modules/steps/__init__.py @@ -94,6 +94,7 @@ class StepView(BaseView): self.model.reset_all_steps() self.stop_step() cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all()) + cbpi.cache["active_brew"] = "none" return ('', 204) def stop_step(self): From b5c722b0aa1e18995b2c76a6425dc468be1aee0f Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 16 May 2018 07:51:46 +0200 Subject: [PATCH 09/25] corrected tagging of actual brew process for storing in time serires db --- modules/logs/endpoints.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/logs/endpoints.py b/modules/logs/endpoints.py index e67ef08..b2a1179 100644 --- a/modules/logs/endpoints.py +++ b/modules/logs/endpoints.py @@ -60,17 +60,12 @@ class LogView(FlaskView): cbpi.notify("Failed to delete log", "", type="danger") return ('', 204) - def querry_tsdb(self, type, id): + def query_tsdb(self, type, id): kairosdb_server = "http://127.0.0.1:" + cbpi.cache["config"]["kairos_db_port"].__dict__["value"] - tag = "" - - if cbpi.cache["active_brew"] != "" and cbpi.cache["active_brew"] != "none": - tag = '"brew": "%s"' % cbpi.cache["active_brew"] - data = dict(metrics=[ { - "tags": {tag}, + "tags": {}, "name": "cbpi.%s_%s" % (type, id), "aggregators": [ { @@ -91,6 +86,9 @@ class LogView(FlaskView): "unit": "days" }) + if cbpi.cache["active_brew"] != "none": + data["metrics"][0]["tags"] = {"brew": [cbpi.cache["active_brew"]]} + self.logger.debug("query: %s", json.dumps(data)) response = requests.post(kairosdb_server + "/api/v1/datapoints/query", json.dumps(data)) @@ -125,6 +123,7 @@ class LogView(FlaskView): if use_kairosdb: return self.querry_tsdb(type, id) + return self.query_tsdb(type, id) else: return self.querry_log(type, id) From 869473f09008c020de0ab48c9775a06c834d2e16 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 16 May 2018 07:52:04 +0200 Subject: [PATCH 10/25] simplified code for logging to logfile --- modules/logs/endpoints.py | 44 ++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/modules/logs/endpoints.py b/modules/logs/endpoints.py index b2a1179..d7935c3 100644 --- a/modules/logs/endpoints.py +++ b/modules/logs/endpoints.py @@ -23,19 +23,7 @@ class LogView(FlaskView): @route('/actions') def actions(self): filename = "./logs/action.log" - if not os.path.isfile(filename): - self.logger.warn("File does not exist [%s]", filename) - return json.dumps([]) - import csv - array = [] - with open(filename, 'rb') as f: - reader = csv.reader(f) - for row in reader: - try: - time.mktime(time.strptime(row[0], "%Y-%m-%d %H:%M:%S")) - array.append([int(time.mktime(time.strptime(row[0], "%Y-%m-%d %H:%M:%S")) * 1000), row[1]]) - except: - pass + array = self.query_log(filename, "string") json_dumps = json.dumps(array) self.logger.debug("Loaded action.log [%s]", json_dumps) @@ -99,22 +87,30 @@ class LogView(FlaskView): else: self.logger.warning("Failed to fetch time series for [%s_%s]. Response [%s]", type, id, response) - def querry_log(self, type, id): - filename = "./logs/%s_%s.log" % (type, id) - if os.path.isfile(filename) == False: - return + def query_log(self, filename, value_type): + array = [] + + if not os.path.isfile(filename): + self.logger.warn("File does not exist [%s]", filename) + return array import csv - array = [] + + if value_type == "float": + converter = float + else: + converter = str + with open(filename, 'rb') as f: reader = csv.reader(f) for row in reader: try: - array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, - 1, - 1)).total_seconds()) * 1000, - float(row[1])]) + point_of_time = int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") + - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000 + value = converter(row[1]) + array.append([point_of_time, value]) except: + self.logger.exception("error in reading logfile [%s]", filename) pass return array @@ -122,10 +118,10 @@ class LogView(FlaskView): use_kairosdb = (cbpi.cache["config"]["kairos_db"].__dict__["value"] == "YES") if use_kairosdb: - return self.querry_tsdb(type, id) return self.query_tsdb(type, id) else: - return self.querry_log(type, id) + filename = "./logs/%s_%s.log" % (type, id) + return self.query_log(filename, "float") def convert_chart_data_to_json(self, chart_data): From cc6559144aaeeab334bd0aa3fd81686918dd47ce Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 17 May 2018 07:37:17 +0200 Subject: [PATCH 11/25] added logging of "action" into time series database --- modules/core/core.py | 58 +++++++++++++++++++++++---------------- modules/logs/endpoints.py | 27 ++++++++---------- 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/modules/core/core.py b/modules/core/core.py index d93b9ae..e668772 100644 --- a/modules/core/core.py +++ b/modules/core/core.py @@ -16,6 +16,7 @@ from hardware import * import time import uuid +import logging class NotificationAPI(object): @@ -85,6 +86,8 @@ class SensorAPI(object): :return: ''' + self.logger = logging.getLogger(__name__) + self.app.logger.info("Init Sensors") t = self.cache.get("sensor_types") @@ -144,41 +147,48 @@ class SensorAPI(object): def receive_sensor_value(self, id, value): self.emit("SENSOR_UPDATE", self.cache.get("sensors")[id]) - self.save_to_file(id, value) + self.save_to_file(id, value, "sensor") - def save_to_file(self, id, value, prefix="sensor"): + def save_to_file(self, id, value, prefix): sensor_name = "%s_%s" % (prefix, str(id)) use_kairosdb = (self.cache["config"]["kairos_db"].__dict__["value"] == "YES") if use_kairosdb: - kairosdb_server = "http://127.0.0.1:" + self.cache["config"]["kairos_db_port"].__dict__["value"] - - data = [ - dict(name="cbpi." + sensor_name, datapoints=[ - [int(round(time.time() * 1000)), value] - ], tags={ - "cbpi": prefix, - "brew": self.cache["active_brew"] - }) - ] - - response = requests.post(kairosdb_server + "/api/v1/datapoints", json.dumps(data)) + self.write_to_tsdb(prefix, sensor_name, value) else: - filename = "./logs/%s.log" % sensor_name - formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) - msg = str(formatted_time) + "," +str(value) + "\n" + self.write_to_logfile(sensor_name, value) + + def write_to_logfile(self, sensor_name, value): + filename = "./logs/%s.log" % sensor_name + formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) + msg = str(formatted_time) + "," + str(value) + "\n" - with open(filename, "a") as file: - file.write(msg) + with open(filename, "a") as file: + file.write(msg) + + def write_to_tsdb(self, prefix, sensor_name, value): + kairosdb_server = "http://127.0.0.1:" + self.cache["config"]["kairos_db_port"].__dict__["value"] + data = [ + dict(name="cbpi." + sensor_name, datapoints=[ + [int(round(time.time() * 1000)), value] + ], tags={ + "cbpi": prefix, + "brew": self.cache["active_brew"] + }) + ] + response = requests.post(kairosdb_server + "/api/v1/datapoints", json.dumps(data)) + if not response.ok: + self.logger.warning("Failed to write time series entry for [%s]. Response [%s]", sensor_name, response) def log_action(self, text): - filename = "./logs/action.log" - formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) - with open(filename, "a") as file: - text = text.encode("utf-8") - file.write("%s,%s\n" % (formatted_time, text)) + use_kairosdb = (self.cache["config"]["kairos_db"].__dict__["value"] == "YES") + + if use_kairosdb: + self.write_to_tsdb("action", "action", text) + else: + self.write_to_logfile("action", text) def shutdown_sensor(self, id): self.cache.get("sensors")[id].stop() diff --git a/modules/logs/endpoints.py b/modules/logs/endpoints.py index d7935c3..32b3e48 100644 --- a/modules/logs/endpoints.py +++ b/modules/logs/endpoints.py @@ -1,6 +1,5 @@ import datetime import os -import time import requests import logging from flask import request, send_from_directory, json @@ -22,11 +21,8 @@ class LogView(FlaskView): @route('/actions') def actions(self): - filename = "./logs/action.log" - array = self.query_log(filename, "string") - + array = self.read_log_as_json("action") json_dumps = json.dumps(array) - self.logger.debug("Loaded action.log [%s]", json_dumps) return json_dumps @@ -48,13 +44,13 @@ class LogView(FlaskView): cbpi.notify("Failed to delete log", "", type="danger") return ('', 204) - def query_tsdb(self, type, id): + def query_tsdb(self, sensor_name): kairosdb_server = "http://127.0.0.1:" + cbpi.cache["config"]["kairos_db_port"].__dict__["value"] data = dict(metrics=[ { "tags": {}, - "name": "cbpi.%s_%s" % (type, id), + "name": "cbpi.%s" % sensor_name, "aggregators": [ { "name": "avg", @@ -81,11 +77,11 @@ class LogView(FlaskView): response = requests.post(kairosdb_server + "/api/v1/datapoints/query", json.dumps(data)) if response.ok: - self.logger.debug("Fetching time series for [%s_%s] took [%s]", type, id, response.elapsed) - self.logger.debug("Time series for [%s_%s] is [%s]", type, id, response.json()) + self.logger.debug("Fetching time series for [%s] took [%s]", sensor_name, response.elapsed) + self.logger.debug("Time series for [%s] is [%s]", sensor_name, response.json()) return response.json()["queries"][0]["results"][0]["values"] else: - self.logger.warning("Failed to fetch time series for [%s_%s]. Response [%s]", type, id, response) + self.logger.warning("Failed to fetch time series for [%s]. Response [%s]", sensor_name, response) def query_log(self, filename, value_type): array = [] @@ -114,19 +110,19 @@ class LogView(FlaskView): pass return array - def read_log_as_json(self, type, id): + def read_log_as_json(self, sensor_name): use_kairosdb = (cbpi.cache["config"]["kairos_db"].__dict__["value"] == "YES") if use_kairosdb: - return self.query_tsdb(type, id) + return self.query_tsdb(sensor_name) else: - filename = "./logs/%s_%s.log" % (type, id) + filename = "./logs/%s.log" % sensor_name return self.query_log(filename, "float") def convert_chart_data_to_json(self, chart_data): return {"name": chart_data["name"], - "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])} + "data": self.read_log_as_json(chart_data["data_type"] + "_" + str(chart_data["data_id"]))} @route('//', methods=["POST"]) def get_logs_as_json(self, t, id): @@ -134,7 +130,8 @@ class LogView(FlaskView): result = [] if t == "s": name = cbpi.cache.get("sensors").get(id).name - result.append({"name": name, "data": self.read_log_as_json("sensor", id)}) + sensor_name = "%s_%s" % ("sensor", str(id)) + result.append({"name": name, "data": self.read_log_as_json(sensor_name)}) if t == "k": kettle = cbpi.cache.get("kettle").get(id) From 37068ca4322a6931ca97e0987f2cd28c4186ade7 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 18 May 2018 06:01:33 +0200 Subject: [PATCH 12/25] Added branches to git overview on system tab --- modules/system/endpoints.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/system/endpoints.py b/modules/system/endpoints.py index bfb23b3..0a01a28 100755 --- a/modules/system/endpoints.py +++ b/modules/system/endpoints.py @@ -61,6 +61,10 @@ class SystemView(FlaskView): for t in repo.tags: tags.append({"name": t.name, "commit": str(t.commit), "date": t.commit.committed_date, "committer": t.commit.committer.name, "message": t.commit.message}) + for b in repo.branches: + tags.append({"name": b.name, "commit": str(b.commit), "date": b.commit.committed_date, + "committer": b.commit.committer.name, "message": b.commit.message}) + try: branch_name = repo.active_branch.name # test1 From b17203d44560eacab9fc4a99f54a024fe51e1bff Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 18 May 2018 06:18:47 +0200 Subject: [PATCH 13/25] changed description for KairosDB parameter --- update/4_kairosdb_config.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update/4_kairosdb_config.sql b/update/4_kairosdb_config.sql index 3415309..32fd59e 100644 --- a/update/4_kairosdb_config.sql +++ b/update/4_kairosdb_config.sql @@ -1,2 +1,2 @@ -INSERT OR IGNORE INTO config VALUES ('kairos_db', 'NO', 'select', 'Use timeseries database KairosDB for storing sensor values. For now you have to install KairosDB by yourself.', '["YES","NO"]' ); +INSERT OR IGNORE INTO config VALUES ('kairos_db', 'NO', 'select', 'Use timeseries database KairosDB for storing sensor values. You can install KairosDB with the CraftBeerPi installer.', '["YES","NO"]' ); INSERT OR IGNORE INTO config VALUES ('kairos_db_port', 8080, 'number', 'Port for KairosDB. We assume the DB is running on your PI, so IP-Address is 127.0.0.1.', NULL ); \ No newline at end of file From cdac4bca84f9fefd93f0879523cee839e60ee445 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 23 May 2018 06:36:57 +0200 Subject: [PATCH 14/25] Added additional parameters to configure timeseries database queries (kairos_db_sampling_value, kairos_db_start_relative) --- modules/logs/endpoints.py | 4 ++-- update/4_kairosdb_config.sql | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/logs/endpoints.py b/modules/logs/endpoints.py index 32b3e48..4eb7e2b 100644 --- a/modules/logs/endpoints.py +++ b/modules/logs/endpoints.py @@ -56,7 +56,7 @@ class LogView(FlaskView): "name": "avg", "align_sampling": True, "sampling": { - "value": "5", + "value": cbpi.cache["config"]["kairos_db_sampling_value"].__dict__["value"], "unit": "seconds" }, "align_start_time": True @@ -66,7 +66,7 @@ class LogView(FlaskView): ], cache_time=0, start_relative={ - "value": "1", + "value": cbpi.cache["config"]["kairos_db_start_relative"].__dict__["value"], "unit": "days" }) diff --git a/update/4_kairosdb_config.sql b/update/4_kairosdb_config.sql index 32fd59e..ce824a1 100644 --- a/update/4_kairosdb_config.sql +++ b/update/4_kairosdb_config.sql @@ -1,2 +1,4 @@ INSERT OR IGNORE INTO config VALUES ('kairos_db', 'NO', 'select', 'Use timeseries database KairosDB for storing sensor values. You can install KairosDB with the CraftBeerPi installer.', '["YES","NO"]' ); -INSERT OR IGNORE INTO config VALUES ('kairos_db_port', 8080, 'number', 'Port for KairosDB. We assume the DB is running on your PI, so IP-Address is 127.0.0.1.', NULL ); \ No newline at end of file +INSERT OR IGNORE INTO config VALUES ('kairos_db_port', 8080, 'number', 'Port for KairosDB. We assume the DB is running on your PI, so IP-Address is 127.0.0.1.', NULL ); +INSERT OR IGNORE INTO config VALUES ('kairos_db_sampling_value', 5, 'number', 'A timeseries database has the advantage to aggregate data points and therefore to reduce the transmitted data. This value sets a time span in seconds to calculate the average', NULL ); +INSERT OR IGNORE INTO config VALUES ('kairos_db_start_relative', 1, 'number', 'If you have an ongoing brew or fermentation process only values related to this process will be shown in the graph. Additionally you can define in days how far the time series should reach in the past. The earliest time of both information defines when the data series starts.', NULL ); \ No newline at end of file From 590a523d87a7d7a3040253ae1ed6ec7ea68a7977 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 5 Jan 2018 21:10:36 +0000 Subject: [PATCH 15/25] Import boil time alerts from BeerXML By reading the HOP and MISC elements, the 'please add your hop' alerts may be imported. --- modules/recipe_import/beerxml.py | 49 +++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/modules/recipe_import/beerxml.py b/modules/recipe_import/beerxml.py index e9f17e0..f8001d5 100644 --- a/modules/recipe_import/beerxml.py +++ b/modules/recipe_import/beerxml.py @@ -48,6 +48,7 @@ class BeerXMLImport(FlaskView): steps = self.getSteps(id) + boil_time_alerts = self.getBoilAlerts(id) name = self.getRecipeName(id) self.api.set_config_parameter("brew_name", name) boil_time = self.getBoilTime(id) @@ -67,8 +68,26 @@ class BeerXMLImport(FlaskView): for row in steps: Step.insert(**{"name": row.get("name"), "type": mashstep_type, "config": {"kettle": mash_kettle, "temp": float(row.get("temp")), "timer": row.get("timer")}}) Step.insert(**{"name": "ChilStep", "type": "ChilStep", "config": {"timer": 15}}) - ## Add cooking step - Step.insert(**{"name": "Boil", "type": boilstep_type, "config": {"kettle": boil_kettle, "temp": boil_temp, "timer": boil_time}}) + ## Add boiling step + Step.insert(**{ + "name": "Boil", + "type": boilstep_type, + "config": { + "kettle": boil_kettle, + "temp": boil_temp, + "timer": boil_time, + ## Beer XML defines additions as the total time spent in boiling, + ## CBP defines it as time-until-alert + + ## Also, The model supports five boil-time additions. + ## Set the rest to None to signal them being absent + "hop_1": boil_time - boil_time_alerts[0] if len(boil_time_alerts) >= 1 else None, + "hop_2": boil_time - boil_time_alerts[1] if len(boil_time_alerts) >= 2 else None, + "hop_3": boil_time - boil_time_alerts[2] if len(boil_time_alerts) >= 3 else None, + "hop_4": boil_time - boil_time_alerts[3] if len(boil_time_alerts) >= 4 else None, + "hop_5": boil_time - boil_time_alerts[4] if len(boil_time_alerts) >= 5 else None + } + }) ## Add Whirlpool step Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}}) self.api.emit("UPDATE_ALL_STEPS", Step.get_all()) @@ -87,18 +106,40 @@ class BeerXMLImport(FlaskView): e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() return float(e.find('./RECIPE[%s]/BOIL_TIME' % (str(id))).text) - def getSteps(self, id): + def getBoilAlerts(self, id): + e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() + + recipe = e.find('./RECIPE[%s]' % (str(id))) + alerts = [] + for e in recipe.findall('./HOPS/HOP'): + use = e.find('USE').text + ## Hops which are not used in the boil step should not cause alerts + if use != 'Aroma' and use != 'Boil': + continue + + alerts.append(float(e.find('TIME').text)) + + ## There might also be miscelaneous additions during boild time + for e in recipe.findall('MISCS/MISC[USE="Boil"]'): + alerts.append(float(e.find('TIME').text)) + ## Dedupe and order the additions by their time, to prevent multiple alerts at the same time + alerts = sorted(list(set(alerts))) + ## CBP should have these additions in reverse + alerts.reverse() + return alerts + + def getSteps(self, id): e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() steps = [] for e in e.findall('./RECIPE[%s]/MASH/MASH_STEPS/MASH_STEP' % (str(id))): - if self.api.get_config_parameter("unit", "C") == "C": temp = float(e.find("STEP_TEMP").text) else: temp = round(9.0 / 5.0 * float(e.find("STEP_TEMP").text) + 32, 2) + steps.append({"name": e.find("NAME").text, "temp": temp, "timer": float(e.find("STEP_TIME").text)}) return steps From e57350e6229c9155e6d03d930d5c807ba68906ee Mon Sep 17 00:00:00 2001 From: clearwaterbrewer <34481584+clearwaterbrewer@users.noreply.github.com> Date: Thu, 5 Apr 2018 10:49:35 -0400 Subject: [PATCH 16/25] Update __init__.py removed lines 37 and 38 to make hysteresis work properly. --- modules/base_plugins/hysteresis/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/base_plugins/hysteresis/__init__.py b/modules/base_plugins/hysteresis/__init__.py index ebc7419..01a726b 100644 --- a/modules/base_plugins/hysteresis/__init__.py +++ b/modules/base_plugins/hysteresis/__init__.py @@ -34,7 +34,5 @@ class Hysteresis(KettleController): self.heater_on(100) elif self.get_temp() >= self.get_target_temp() - float(self.off): self.heater_off() - else: - self.heater_off() self.sleep(1) From e0c267832fea1eb53915cbab4035b4487b064c67 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 5 Apr 2018 08:53:19 -0700 Subject: [PATCH 17/25] Remove duplicate line --- modules/core/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/core/core.py b/modules/core/core.py index e668772..2505718 100644 --- a/modules/core/core.py +++ b/modules/core/core.py @@ -39,7 +39,6 @@ class ActorAPI(object): value = self.cache.get("actors").get(int(id)) cfg = value.config.copy() cfg.update(dict(api=self, id=id, name=value.name)) - cfg.update(dict(api=self, id=id, name=value.name)) clazz = self.cache.get("actor_types").get(value.type).get("class") value.instance = clazz(**cfg) value.instance.init() From efc85d65f71d62bff37127bd47e56b3001813cef Mon Sep 17 00:00:00 2001 From: swimIan Date: Fri, 3 Aug 2018 09:12:45 -0700 Subject: [PATCH 18/25] Remove "Steps started on xml imported" bug --- modules/recipe_import/beerxml.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/recipe_import/beerxml.py b/modules/recipe_import/beerxml.py index f8001d5..5e39d11 100644 --- a/modules/recipe_import/beerxml.py +++ b/modules/recipe_import/beerxml.py @@ -90,6 +90,7 @@ class BeerXMLImport(FlaskView): }) ## Add Whirlpool step Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}}) + StepView().reset() self.api.emit("UPDATE_ALL_STEPS", Step.get_all()) self.api.notify(headline="Recipe %s loaded successfully" % name, message="") except Exception as e: From 640d9210e4ab66b00ebcbe1d65c404847b06cb28 Mon Sep 17 00:00:00 2001 From: Chris Speck Date: Mon, 13 Aug 2018 23:13:13 +1000 Subject: [PATCH 19/25] Docker files for ease of development on PCs/macs --- Dockerfile | 14 ++++++++++++++ README.md | 15 +++++++++++++++ docker-compose.yml | 9 +++++++++ 3 files changed, 38 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ff2bfe5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# Dockerfile for development on a pc/mac +FROM python:2 + +EXPOSE 5000 + +WORKDIR /usr/src/app + +COPY requirements.txt ./ + +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["python", "run.py"] \ No newline at end of file diff --git a/README.md b/README.md index 84730e1..a6bd52a 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,21 @@ http://web.craftbeerpi.com/hardware/ CraftBeerPi 3.0 is a complete rewrite. Server as well as user interface. I recommend to use a second SD card for testing. +## Docker-based development + +For developing this application or its plugins on a PC/Mac you can use the docker-compose file: + +``` shell +$ docker-compose up +... +Starting craftbeerpi3_app_1 ... done +Attaching to craftbeerpi3_app_1 +app_1 | [2018-08-13 12:54:44,264] ERROR in __init__: BUZZER not working +app_1 | (1) wsgi starting up on http://0.0.0.0:5000 +``` + +The contents of this folder will be mounted to `/usr/src/craftbeerpi3` and the server will be accesible on `localhost:3000`. + ## Donation CraftBeerPi is a free & open source project. If you like to support the project I happy about a donation: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..578f288 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +# docker-compose file for development on a pc/mac +version: '3.3' +services: + app: + build: . + ports: + - 5000:5000 + volumes: + - $PWD:/usr/src/app From f11f7d0737eec0393f02658a973e0b3db8f5fec1 Mon Sep 17 00:00:00 2001 From: Chris Speck Date: Wed, 15 Aug 2018 22:47:36 +1000 Subject: [PATCH 20/25] Fix typo which resulted in reference to incorrect port --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a6bd52a..871f6a8 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ app_1 | [2018-08-13 12:54:44,264] ERROR in __init__: BUZZER not working app_1 | (1) wsgi starting up on http://0.0.0.0:5000 ``` -The contents of this folder will be mounted to `/usr/src/craftbeerpi3` and the server will be accesible on `localhost:3000`. +The contents of this folder will be mounted to `/usr/src/craftbeerpi3` and the server will be accesible on `localhost:5000`. ## Donation From a3bbbccfc91e118fbdaed3865a139c5c23189d6f Mon Sep 17 00:00:00 2001 From: Johannes Date: Sun, 28 Oct 2018 08:57:36 +0100 Subject: [PATCH 21/25] beer.xml exports from "BrewersFriend" can contain a tag like "" which led to a float converting error. Now this will be interpreted as Step_Time = 0.0 --- modules/recipe_import/beerxml.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/recipe_import/beerxml.py b/modules/recipe_import/beerxml.py index 5e39d11..98748c0 100644 --- a/modules/recipe_import/beerxml.py +++ b/modules/recipe_import/beerxml.py @@ -141,7 +141,12 @@ class BeerXMLImport(FlaskView): else: temp = round(9.0 / 5.0 * float(e.find("STEP_TEMP").text) + 32, 2) - steps.append({"name": e.find("NAME").text, "temp": temp, "timer": float(e.find("STEP_TIME").text)}) + if e.find("STEP_TIME").text is None: + stepTime = 0.0 + else: + stepTime = float(e.find("STEP_TIME").text) + + steps.append({"name": e.find("NAME").text, "temp": temp, "timer": stepTime}) return steps From 1008613656c7e4c3933e1311d2af7e89ece12266 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 1 Nov 2018 08:06:10 +0100 Subject: [PATCH 22/25] beer.xml import differentates between mash step or mash in step, depending on timer > 0. --- modules/recipe_import/beerxml.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/modules/recipe_import/beerxml.py b/modules/recipe_import/beerxml.py index 98748c0..262b0c8 100644 --- a/modules/recipe_import/beerxml.py +++ b/modules/recipe_import/beerxml.py @@ -46,13 +46,13 @@ class BeerXMLImport(FlaskView): @route('/', methods=['POST']) def load(self, id): - steps = self.getSteps(id) boil_time_alerts = self.getBoilAlerts(id) name = self.getRecipeName(id) self.api.set_config_parameter("brew_name", name) boil_time = self.getBoilTime(id) mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep") + mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep") mash_kettle = cbpi.get_config_parameter("step_mash_kettle", None) boilstep_type = cbpi.get_config_parameter("step_boil", "BoilStep") @@ -65,10 +65,20 @@ class BeerXMLImport(FlaskView): try: + # Add mash in or mash step, depends on timer > 0 for row in steps: - Step.insert(**{"name": row.get("name"), "type": mashstep_type, "config": {"kettle": mash_kettle, "temp": float(row.get("temp")), "timer": row.get("timer")}}) + if row.get("timer") > 0: + Step.insert(**{"name": row.get("name"), "type": mashstep_type, + "config": {"kettle": mash_kettle, "temp": float(row.get("temp")), + "timer": row.get("timer")}}) + else: + Step.insert(**{"name": row.get("name"), "type": mashinstep_type, + "config": {"kettle": mash_kettle, "temp": float(row.get("temp"))}}) + + # Add chilling step Step.insert(**{"name": "ChilStep", "type": "ChilStep", "config": {"timer": 15}}) - ## Add boiling step + + # Add boiling step Step.insert(**{ "name": "Boil", "type": boilstep_type, @@ -76,11 +86,11 @@ class BeerXMLImport(FlaskView): "kettle": boil_kettle, "temp": boil_temp, "timer": boil_time, - ## Beer XML defines additions as the total time spent in boiling, - ## CBP defines it as time-until-alert + # Beer XML defines additions as the total time spent in boiling, + # CBP defines it as time-until-alert - ## Also, The model supports five boil-time additions. - ## Set the rest to None to signal them being absent + # Also, The model supports five boil-time additions. + # Set the rest to None to signal them being absent "hop_1": boil_time - boil_time_alerts[0] if len(boil_time_alerts) >= 1 else None, "hop_2": boil_time - boil_time_alerts[1] if len(boil_time_alerts) >= 2 else None, "hop_3": boil_time - boil_time_alerts[2] if len(boil_time_alerts) >= 3 else None, @@ -88,7 +98,8 @@ class BeerXMLImport(FlaskView): "hop_5": boil_time - boil_time_alerts[4] if len(boil_time_alerts) >= 5 else None } }) - ## Add Whirlpool step + + # Add Whirlpool step Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}}) StepView().reset() self.api.emit("UPDATE_ALL_STEPS", Step.get_all()) From b89dc3a4c3137c278785030dca539a7558f98cfa Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 1 Nov 2018 08:11:00 +0100 Subject: [PATCH 23/25] start event is called everytime a new step starts. So a new value for "active_brew" will only be generated if the value before was "none". The value is "none" either after initializing the app or after the process got stopped. That leaves one potentiall error: if a process is started and the app needs a restart. In this case a new value for "active_brew" would be unecessarily generated. --- modules/steps/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/steps/__init__.py b/modules/steps/__init__.py index 1189d09..7827c14 100755 --- a/modules/steps/__init__.py +++ b/modules/steps/__init__.py @@ -146,7 +146,8 @@ class StepView(BaseView): @route('/start', methods=['POST']) def start(self): - cbpi.cache["active_brew"] = cbpi.cache["config"]["brew_name"].__dict__["value"] + \ + if "none" == cbpi.cache["active_brew"]: + cbpi.cache["active_brew"] = cbpi.cache["config"]["brew_name"].__dict__["value"] + \ "_" + datetime.datetime.now().strftime('%y-%m-%dT%H:%M') return self.next() From 2da4aff978598c9ebdb60c566b0561e2ffc6e504 Mon Sep 17 00:00:00 2001 From: Manuel83 Date: Mon, 29 Oct 2018 22:15:03 +0100 Subject: [PATCH 24/25] Update package because of vulnerability Flask: CVE-2018-1000656 More information moderate severity Vulnerable versions: < 0.12.3 Patched version: 0.12.3 The Pallets Project flask version Before 0.12.3 contains a CWE-20: Improper Input Validation vulnerability in flask that can result in Large amount of memory usage possibly leading to denial of service. This attack appear to be exploitable via Attacker provides JSON data in incorrect encoding. This vulnerability appears to have been fixed in 0.12.3. Requests: CVE-2018-18074 More information moderate severity Vulnerable versions: <= 2.19.1 Patched version: 2.20.0 The Requests package through 2.19.1 before 2018-09-14 for Python sends an HTTP Authorization header to an http URI upon receiving a same-hostname https-to-http redirect, which makes it easier for remote attackers to discover credentials by sniffing the network. --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index be2b386..2529782 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Flask==0.11.1 +Flask==0.12.4 Flask-SocketIO==2.6.2 eventlet==0.19.0 greenlet==0.4.10 @@ -7,7 +7,7 @@ python-engineio==0.9.2 python-mimeparse==1.5.2 python-socketio==1.4.4 PyYAML==3.11 -requests==2.11.0 +requests==2.20.0 Werkzeug==0.11.10 httplib2==0.9.2 flask-classy==0.6.10 From 4d574b12784bcec3c07a07ac319abfb55a7cb922 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 1 Nov 2018 08:28:30 +0100 Subject: [PATCH 25/25] merged from original fork --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 871f6a8..a6bd52a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ app_1 | [2018-08-13 12:54:44,264] ERROR in __init__: BUZZER not working app_1 | (1) wsgi starting up on http://0.0.0.0:5000 ``` -The contents of this folder will be mounted to `/usr/src/craftbeerpi3` and the server will be accesible on `localhost:5000`. +The contents of this folder will be mounted to `/usr/src/craftbeerpi3` and the server will be accesible on `localhost:3000`. ## Donation