mirror of
https://github.com/Manuel83/craftbeerpi3
synced 2026-09-19 15:59:07 +02:00
- Recipe Book
- BugFixes - Refactoring
This commit is contained in:
@@ -8,6 +8,7 @@ class Dummy(Actor):
|
|||||||
|
|
||||||
@cbpi.addon.actor.action("WOHOO")
|
@cbpi.addon.actor.action("WOHOO")
|
||||||
def myaction(self):
|
def myaction(self):
|
||||||
|
print "WOOHOO"
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on(self, power=100):
|
def on(self, power=100):
|
||||||
@@ -29,6 +30,7 @@ class MyController(KettleController):
|
|||||||
def run(self):
|
def run(self):
|
||||||
while self.is_running():
|
while self.is_running():
|
||||||
|
|
||||||
|
|
||||||
self.sleep(1)
|
self.sleep(1)
|
||||||
|
|
||||||
@cbpi.addon.fermenter.controller()
|
@cbpi.addon.fermenter.controller()
|
||||||
@@ -38,6 +40,8 @@ class MyController2(FermenterController):
|
|||||||
def run(self):
|
def run(self):
|
||||||
while self.is_running():
|
while self.is_running():
|
||||||
print "HALLO"
|
print "HALLO"
|
||||||
|
|
||||||
|
self.get_target_temp()
|
||||||
self.sleep(1)
|
self.sleep(1)
|
||||||
|
|
||||||
@cbpi.addon.core.initializer(order=200)
|
@cbpi.addon.core.initializer(order=200)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class Dummy(Sensor):
|
|||||||
@cbpi.addon.sensor.action("WOHOO")
|
@cbpi.addon.sensor.action("WOHOO")
|
||||||
def myaction(self):
|
def myaction(self):
|
||||||
|
|
||||||
print "SENSOR ACTION HALLO!!!"
|
self.api.notify(headline="WOHOO", message="HALLO")
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
while True:
|
while True:
|
||||||
@@ -32,7 +32,7 @@ class Dummy(Sensor):
|
|||||||
self.update_value(int(self.text))
|
self.update_value(int(self.text))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self.api.sleep(1)
|
self.api.sleep(5)
|
||||||
|
|
||||||
@cbpi.addon.core.action(key="clear", label="Clear all Logs")
|
@cbpi.addon.core.action(key="clear", label="Clear all Logs")
|
||||||
def woohoo(cbpi):
|
def woohoo(cbpi):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from modules.core.basetypes import Step
|
from modules.core.basetypes import Step
|
||||||
from modules.core.core import cbpi
|
from modules.core.core import cbpi
|
||||||
from modules.core.proptypes import Property
|
from modules.core.proptypes import Property
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
@cbpi.addon.step.type("Dummy Step")
|
@cbpi.addon.step.type("Dummy Step")
|
||||||
@@ -22,4 +23,223 @@ class Dummy(Step):
|
|||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
|
|
||||||
self.stop_timer()
|
self.stop_timer()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.addon.step.type("Dummy Step")
|
||||||
|
class MashStep(Step):
|
||||||
|
'''
|
||||||
|
Just put the decorator @cbpi.step on top of a method
|
||||||
|
'''
|
||||||
|
# Properties
|
||||||
|
temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step")
|
||||||
|
kettle = Property.Kettle("Kettle", description="Kettle in which the mashing takes place")
|
||||||
|
timer = Property.Number("Timer in Minutes", configurable=True, description="Timer is started when the target temperature is reached")
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
'''
|
||||||
|
Initialize Step. This method is called once at the beginning of the step
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
# set target tep
|
||||||
|
self.set_target_temp(self.temp, self.kettle)
|
||||||
|
|
||||||
|
@cbpi.addon.step.action("Start Timer")
|
||||||
|
def start(self):
|
||||||
|
'''
|
||||||
|
Custom Action which can be execute form the brewing dashboard.
|
||||||
|
All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.stop_timer()
|
||||||
|
self.set_target_temp(self.temp, self.kettle)
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
self.set_target_temp(0, self.kettle)
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
'''
|
||||||
|
This method is execute in an interval
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Check if Target Temp is reached
|
||||||
|
if self.get_kettle_temp(self.kettle) >= float(self.temp):
|
||||||
|
# Check if Timer is Running
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
|
||||||
|
# Check if timer finished and go to next step
|
||||||
|
if self.is_timer_finished() == True:
|
||||||
|
self.notify("Mash Step Completed!", "Starting the next step", timeout=None)
|
||||||
|
self.next()
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.addon.step.type("MashInStep")
|
||||||
|
class MashInStep(Step):
|
||||||
|
'''
|
||||||
|
Just put the decorator @cbpi.step on top of a method
|
||||||
|
'''
|
||||||
|
# Properties
|
||||||
|
temp = Property.Number("Temperature", configurable=True, description="Target Temperature of Mash Step")
|
||||||
|
kettle = Property.Kettle("Kettle", description="Kettle in which the mashing takes place")
|
||||||
|
s = False
|
||||||
|
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
'''
|
||||||
|
Initialize Step. This method is called once at the beginning of the step
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
# set target tep
|
||||||
|
self.s = False
|
||||||
|
self.set_target_temp(self.temp, self.kettle)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
'''
|
||||||
|
This method is execute in an interval
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Check if Target Temp is reached
|
||||||
|
if self.get_kettle_temp(self.kettle) >= float(self.temp) and self.s is False:
|
||||||
|
self.s = True
|
||||||
|
self.notify("Step Temp Reached!", "Please press the next button to continue", timeout=None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.addon.step.type("MashInStep")
|
||||||
|
class ChilStep(Step):
|
||||||
|
|
||||||
|
timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately")
|
||||||
|
|
||||||
|
@cbpi.addon.step.action("Start Timer")
|
||||||
|
def start(self):
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.stop_timer()
|
||||||
|
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
|
||||||
|
if self.is_timer_finished() == True:
|
||||||
|
self.next()
|
||||||
|
|
||||||
|
@cbpi.addon.step.type("MashInStep")
|
||||||
|
class PumpStep(Step):
|
||||||
|
|
||||||
|
pump = Property.Actor("Pump", description="Pump actor gets toogled")
|
||||||
|
timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately")
|
||||||
|
|
||||||
|
@cbpi.addon.step.action("Start Timer")
|
||||||
|
def start(self):
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.stop_timer()
|
||||||
|
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
self.actor_off(int(self.pump))
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.actor_on(int(self.pump))
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
|
||||||
|
if self.is_timer_finished() == True:
|
||||||
|
self.next()
|
||||||
|
|
||||||
|
@cbpi.addon.step.type("MashInStep")
|
||||||
|
class BoilStep(Step):
|
||||||
|
'''
|
||||||
|
Just put the decorator @cbpi.step on top of a method
|
||||||
|
'''
|
||||||
|
# Properties
|
||||||
|
temp = Property.Number("Temperature", configurable=True, default_value=100, description="Target temperature for boiling")
|
||||||
|
kettle = Property.Kettle("Kettle", description="Kettle in which the boiling step takes place")
|
||||||
|
timer = Property.Number("Timer in Minutes", configurable=True, default_value=90, description="Timer is started when target temperature is reached")
|
||||||
|
hop_1 = Property.Number("Hop 1 Addition", configurable=True, description="Fist Hop alert")
|
||||||
|
hop_1_added = Property.Number("",default_value=None)
|
||||||
|
hop_2 = Property.Number("Hop 2 Addition", configurable=True, description="Second Hop alert")
|
||||||
|
hop_2_added = Property.Number("", default_value=None)
|
||||||
|
hop_3 = Property.Number("Hop 3 Addition", configurable=True)
|
||||||
|
hop_3_added = Property.Number("", default_value=None, description="Third Hop alert")
|
||||||
|
hop_4 = Property.Number("Hop 4 Addition", configurable=True)
|
||||||
|
hop_4_added = Property.Number("", default_value=None, description="Fourth Hop alert")
|
||||||
|
hop_5 = Property.Number("Hop 5 Addition", configurable=True)
|
||||||
|
hop_5_added = Property.Number("", default_value=None, description="Fives Hop alert")
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
'''
|
||||||
|
Initialize Step. This method is called once at the beginning of the step
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
# set target tep
|
||||||
|
self.set_target_temp(self.temp, self.kettle)
|
||||||
|
|
||||||
|
@cbpi.addon.step.action("Start Timer")
|
||||||
|
def start(self):
|
||||||
|
'''
|
||||||
|
Custom Action which can be execute form the brewing dashboard.
|
||||||
|
All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.stop_timer()
|
||||||
|
self.set_target_temp(self.temp, self.kettle)
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
self.set_target_temp(0, self.kettle)
|
||||||
|
|
||||||
|
|
||||||
|
def check_hop_timer(self, number, value):
|
||||||
|
|
||||||
|
if self.__getattribute__("hop_%s_added" % number) is not True and time.time() > (
|
||||||
|
self.timer_end - (int(self.timer) * 60 - int(value) * 60)):
|
||||||
|
self.__setattr__("hop_%s_added" % number, True)
|
||||||
|
self.notify("Hop Alert", "Please add Hop %s" % number, timeout=None)
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
'''
|
||||||
|
This method is execute in an interval
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
# Check if Target Temp is reached
|
||||||
|
if self.get_kettle_temp(self.kettle) >= float(self.temp):
|
||||||
|
# Check if Timer is Running
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
else:
|
||||||
|
self.check_hop_timer(1, self.hop_1)
|
||||||
|
self.check_hop_timer(2, self.hop_2)
|
||||||
|
self.check_hop_timer(3, self.hop_3)
|
||||||
|
self.check_hop_timer(4, self.hop_4)
|
||||||
|
self.check_hop_timer(5, self.hop_5)
|
||||||
|
# Check if timer finished and go to next step
|
||||||
|
if self.is_timer_finished() == True:
|
||||||
|
self.notify("Boil Step Completed!", "Starting the next step", timeout=None)
|
||||||
|
self.next()
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ class Timer(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Step(Base, Timer):
|
class Step(Base, Timer):
|
||||||
|
|
||||||
|
|
||||||
@@ -267,6 +269,25 @@ class Step(Base, Timer):
|
|||||||
self.value = None
|
self.value = None
|
||||||
self.__dirty = False
|
self.__dirty = False
|
||||||
|
|
||||||
|
def set_target_temp(self, temp, id=None):
|
||||||
|
temp = float(temp)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if id is None:
|
||||||
|
self.api.emit("SET_TARGET_TEMP", id=self.kettle_id, temp=temp)
|
||||||
|
else:
|
||||||
|
self.api.emit("SET_TARGET_TEMP", id=id, temp=temp)
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
self.api.notify("Faild to set Target Temp", "", type="warning")
|
||||||
|
|
||||||
|
|
||||||
|
def get_kettle_temp(self, id=None):
|
||||||
|
id = int(id)
|
||||||
|
if id is None:
|
||||||
|
id = self.kettle_id
|
||||||
|
return self.api.sensor.get_value(int(self.api.cache.get("kettle").get(id).sensor))
|
||||||
|
|
||||||
def is_dirty(self):
|
def is_dirty(self):
|
||||||
return self.__dirty
|
return self.__dirty
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -364,13 +364,22 @@ class CraftBeerPI(object):
|
|||||||
method.callback = True
|
method.callback = True
|
||||||
self.cache[key] = method
|
self.cache[key] = method
|
||||||
|
|
||||||
def get_config_parameter(self, key, default):
|
def get_config_parameter(self, key, default=None):
|
||||||
cfg = self.cache["config"].get(key)
|
cfg = self.cache["config"].get(key)
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
return default
|
return default
|
||||||
else:
|
else:
|
||||||
return cfg.value
|
return cfg.value
|
||||||
|
|
||||||
|
def set_config_parameter(self, name, value):
|
||||||
|
from modules.config import Config
|
||||||
|
with self._app.app_context():
|
||||||
|
update_data = {"name": name, "value": value}
|
||||||
|
self.cache.get("config")[name].__dict__.update(**update_data)
|
||||||
|
c = Config.update(**update_data)
|
||||||
|
self.ws_emit("UPDATE_CONFIG", c)
|
||||||
|
|
||||||
|
|
||||||
def emit(self, key, **kwargs):
|
def emit(self, key, **kwargs):
|
||||||
|
|
||||||
if self.eventbus.get(key) is not None:
|
if self.eventbus.get(key) is not None:
|
||||||
|
|||||||
@@ -131,4 +131,5 @@ class FermenterStep(DBModel):
|
|||||||
def reset_all_steps(cls,id):
|
def reset_all_steps(cls,id):
|
||||||
cur = get_db().cursor()
|
cur = get_db().cursor()
|
||||||
cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,))
|
cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,))
|
||||||
get_db().commit()
|
get_db().commit()
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ class FermenterView(BaseView):
|
|||||||
|
|
||||||
@route('/<int:id>/start', methods=['POST'])
|
@route('/<int:id>/start', methods=['POST'])
|
||||||
def start_fermentation(self, id):
|
def start_fermentation(self, id):
|
||||||
|
print "START"
|
||||||
active = None
|
active = None
|
||||||
for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
|
for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
|
||||||
if s.state == 'A':
|
if s.state == 'A':
|
||||||
@@ -196,6 +197,7 @@ class FermenterView(BaseView):
|
|||||||
return cbpi.cache["fermenter"].get(id)
|
return cbpi.cache["fermenter"].get(id)
|
||||||
|
|
||||||
def target_temp_reached(self,id, step):
|
def target_temp_reached(self,id, step):
|
||||||
|
print "TARGET TEMP REACHED"
|
||||||
timestamp = time.time()
|
timestamp = time.time()
|
||||||
|
|
||||||
days = step.days * 24 * 60 * 60
|
days = step.days * 24 * 60 * 60
|
||||||
@@ -210,19 +212,22 @@ class FermenterView(BaseView):
|
|||||||
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
|
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
|
||||||
|
|
||||||
def check_step(self):
|
def check_step(self):
|
||||||
|
print "CHECK STEP"
|
||||||
|
print cbpi.cache["fermenter_task"]
|
||||||
for key, value in cbpi.cache["fermenter_task"].iteritems():
|
for key, value in cbpi.cache["fermenter_task"].iteritems():
|
||||||
|
print value
|
||||||
try:
|
try:
|
||||||
fermenter = self.get_fermenter(key)
|
fermenter = self.get_fermenter(key)
|
||||||
current_temp = current_temp = cbpi.get_sensor_value(int(fermenter.sensor))
|
current_temp = current_temp = cbpi.sensor.get_value(int(fermenter.sensor))
|
||||||
|
|
||||||
if value.timer_start is None:
|
if value.timer_start is None:
|
||||||
|
print "TIMER IS NONE"
|
||||||
if value.direction == "H" :
|
if value.direction == "H" :
|
||||||
|
print "TIMER WATING FOR HEATING"
|
||||||
if current_temp >= value.temp:
|
if current_temp >= value.temp:
|
||||||
self.target_temp_reached(key,value)
|
self.target_temp_reached(key,value)
|
||||||
else:
|
else:
|
||||||
|
print "TIMER WATING FOR COILING"
|
||||||
if current_temp <= value.temp:
|
if current_temp <= value.temp:
|
||||||
self.target_temp_reached(key, value)
|
self.target_temp_reached(key, value)
|
||||||
else:
|
else:
|
||||||
@@ -231,6 +236,7 @@ class FermenterView(BaseView):
|
|||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print e
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,17 @@ class KettleView(BaseView):
|
|||||||
self.api.brewing.toggle_automatic(id)
|
self.api.brewing.toggle_automatic(id)
|
||||||
return ('', 204)
|
return ('', 204)
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.addon.core.listen("SET_TARGET_TEMP")
|
||||||
|
def set_target_temp(id, temp):
|
||||||
|
'''
|
||||||
|
Change Taget Temp Event
|
||||||
|
:param id: kettle id
|
||||||
|
:param temp: target temp to set
|
||||||
|
:return: None
|
||||||
|
'''
|
||||||
|
KettleView().postTargetTemp(id,temp)
|
||||||
|
|
||||||
@cbpi.addon.core.backgroundjob(key="read_target_temps", interval=5)
|
@cbpi.addon.core.backgroundjob(key="read_target_temps", interval=5)
|
||||||
def read_target_temps(api):
|
def read_target_temps(api):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class PluginView(FlaskView):
|
|||||||
response = requests.get("https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins.yaml")
|
response = requests.get("https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins.yaml")
|
||||||
self.api.cache["plugins"] = self.merge(yaml.load(response.text), self.api.cache["plugins"])
|
self.api.cache["plugins"] = self.merge(yaml.load(response.text), self.api.cache["plugins"])
|
||||||
for key, value in cbpi.cache["plugins"].iteritems():
|
for key, value in cbpi.cache["plugins"].iteritems():
|
||||||
|
print key
|
||||||
value["installed"] = os.path.isdir("./plugins/%s/" % (key))
|
value["installed"] = os.path.isdir("./plugins/%s/" % (key))
|
||||||
return json.dumps(cbpi.cache["plugins"])
|
return json.dumps(cbpi.cache["plugins"])
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from flask import json, request, send_from_directory
|
||||||
|
from flask_classy import route, FlaskView
|
||||||
|
|
||||||
|
from modules.core.db import DBModel
|
||||||
|
from modules.core.baseview import BaseView
|
||||||
|
from modules.core.core import cbpi
|
||||||
|
from modules.database.dbmodel import Step
|
||||||
|
from yaml import Loader, Dumper
|
||||||
|
from yaml import load, dump
|
||||||
|
|
||||||
|
from modules.step import StepView
|
||||||
|
|
||||||
|
|
||||||
|
class RecipeBook(FlaskView):
|
||||||
|
|
||||||
|
|
||||||
|
@route('/load', methods=["POST"])
|
||||||
|
def load(self):
|
||||||
|
|
||||||
|
data = request.json
|
||||||
|
recipe_name = data.get("name")
|
||||||
|
if re.match("^[A-Za-z0-9_-]*$", recipe_name) is None:
|
||||||
|
return ('Recipie Name contains not allowed characters', 500)
|
||||||
|
|
||||||
|
with open("./recipes/%s.json" % recipe_name) as json_data:
|
||||||
|
d = json.load(json_data)
|
||||||
|
Step.delete_all()
|
||||||
|
StepView().reset()
|
||||||
|
for s in d["steps"]:
|
||||||
|
Step.insert(**{"name": s.get("name"), "type": s.get("type"), "config": s.get("config")})
|
||||||
|
|
||||||
|
self.api.ws_emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||||
|
self.api.notify(headline="Recipe %s loaded successfully" % recipe_name, message="")
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/download/<name>', methods=["GET"])
|
||||||
|
def download(self, name):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Download a log file by name
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- logs
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: file
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: filename
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Log file downloaded
|
||||||
|
"""
|
||||||
|
file = "%s.json" % name
|
||||||
|
print file
|
||||||
|
if not self.check_filename(file):
|
||||||
|
return ('File Not Found111', 404)
|
||||||
|
return send_from_directory('../../recipes', file, as_attachment=True, attachment_filename=file)
|
||||||
|
|
||||||
|
def check_filename(self, name):
|
||||||
|
import re
|
||||||
|
print "CHECK"
|
||||||
|
pattern = re.compile('^([A-Za-z0-9-_])+.json$')
|
||||||
|
return True if pattern.match(name) else False
|
||||||
|
|
||||||
|
@route('/<name>', methods=["DELETE"])
|
||||||
|
def remove(self, name):
|
||||||
|
|
||||||
|
|
||||||
|
recipe_name = name
|
||||||
|
if re.match("^[A-Za-z0-9_-]*$", recipe_name) is None:
|
||||||
|
return ('Recipie Name contains not allowed characters', 500)
|
||||||
|
|
||||||
|
filename = "./recipes/%s.json" % recipe_name
|
||||||
|
if os.path.isfile(filename) == True:
|
||||||
|
os.remove(filename)
|
||||||
|
self.api.notify(headline="Recipe %s deleted successfully" % recipe_name, message="")
|
||||||
|
return ('', 204)
|
||||||
|
else:
|
||||||
|
self.api.notify(headline="Faild to delete Recipe %s deleted" % recipe_name, message="")
|
||||||
|
return ('', 404)
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
|
||||||
|
@route('/', methods=["GET"])
|
||||||
|
def get_all(self):
|
||||||
|
result = []
|
||||||
|
for filename in os.listdir("./recipes"):
|
||||||
|
if filename.endswith(".json"):
|
||||||
|
|
||||||
|
result.append({"id":filename.split(".")[0], "name": filename.split(".")[0], "change_date": self._modification_date('./recipes/%s' % filename)})
|
||||||
|
return json.dumps(result)
|
||||||
|
|
||||||
|
|
||||||
|
def _modification_date(self, filename):
|
||||||
|
t = os.path.getmtime(filename)
|
||||||
|
return datetime.datetime.fromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
@route('/save', methods=["POST"])
|
||||||
|
def save(self):
|
||||||
|
"""
|
||||||
|
Save Recepie
|
||||||
|
---
|
||||||
|
tags:
|
||||||
|
- steps
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: Recipe saved
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
recipe_name = self.api.get_config_parameter("brew_name")
|
||||||
|
|
||||||
|
if recipe_name is None or len(recipe_name) <= 0:
|
||||||
|
self.api.notify(headline="Please set brew name", message="Recipe not saved!", type="danger")
|
||||||
|
return ('Recipie Name contains not allowed characters', 500)
|
||||||
|
|
||||||
|
if re.match("^[\sA-Za-z0-9_-]*$", recipe_name) is None:
|
||||||
|
self.api.notify(headline="Only alphanummeric charaters are allowd for recipe name", message="", type="danger")
|
||||||
|
return ('Recipie Name contains not allowed characters', 500)
|
||||||
|
|
||||||
|
recipe_data = {"name": recipe_name, "steps": Step.get_all()}
|
||||||
|
|
||||||
|
file_name = recipe_name.replace(" ", "_")
|
||||||
|
with open('./recipes/%s.json' % file_name, 'w') as outfile:
|
||||||
|
json.dump(recipe_data, outfile, indent=4)
|
||||||
|
|
||||||
|
self.api.notify(headline="Recipe %s saved successfully" % recipe_name, message="")
|
||||||
|
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.addon.core.initializer(order=2000)
|
||||||
|
def init(cbpi):
|
||||||
|
RecipeBook.api = cbpi
|
||||||
|
RecipeBook.register(cbpi._app, route_base='/api/recipebook')
|
||||||
@@ -55,11 +55,12 @@ class BeerXMLImport(FlaskView):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
file = request.files['file']
|
file = request.files['file']
|
||||||
if file and self.allowed_file(file.filename):
|
if file and self.allowed_file(file.filename):
|
||||||
file.save(os.path.join(self.api.app.config['UPLOAD_FOLDER'], "beer.xml"))
|
file.save(os.path.join(self.api.get_config_parameter('UPLOAD_FOLDER', "./upload"), "beer.xml"))
|
||||||
self.api.notify(headline="Upload Successful", message="The Beer XML file was uploaded succesfully")
|
self.api.notify(headline="Upload Successful", message="The Beer XML file was uploaded succesfully")
|
||||||
return ('', 204)
|
return ('', 204)
|
||||||
return ('', 404)
|
return ('', 404)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print e
|
||||||
self.api.notify(headline="Upload Failed", message="Failed to upload Beer xml", type="danger")
|
self.api.notify(headline="Upload Failed", message="Failed to upload Beer xml", type="danger")
|
||||||
return ('', 500)
|
return ('', 500)
|
||||||
|
|
||||||
@@ -88,12 +89,12 @@ class BeerXMLImport(FlaskView):
|
|||||||
name = self.getRecipeName(id)
|
name = self.getRecipeName(id)
|
||||||
self.api.set_config_parameter("brew_name", name)
|
self.api.set_config_parameter("brew_name", name)
|
||||||
boil_time = self.getBoilTime(id)
|
boil_time = self.getBoilTime(id)
|
||||||
mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep")
|
mashstep_type = self.api.get_config_parameter("step_mash", "MashStep")
|
||||||
mash_kettle = cbpi.get_config_parameter("step_mash_kettle", None)
|
mash_kettle = self.api.get_config_parameter("step_mash_kettle", None)
|
||||||
|
|
||||||
boilstep_type = cbpi.get_config_parameter("step_boil", "BoilStep")
|
boilstep_type = self.api.get_config_parameter("step_boil", "BoilStep")
|
||||||
boil_kettle = cbpi.get_config_parameter("step_boil_kettle", None)
|
boil_kettle = self.api.get_config_parameter("step_boil_kettle", None)
|
||||||
boil_temp = 100 if cbpi.get_config_parameter("unit", "C") == "C" else 212
|
boil_temp = 100 if self.api.get_config_parameter("unit", "C") == "C" else 212
|
||||||
|
|
||||||
# READ KBH DATABASE
|
# READ KBH DATABASE
|
||||||
Step.delete_all()
|
Step.delete_all()
|
||||||
@@ -108,7 +109,8 @@ class BeerXMLImport(FlaskView):
|
|||||||
Step.insert(**{"name": "Boil", "type": boilstep_type, "config": {"kettle": boil_kettle, "temp": boil_temp, "timer": boil_time}})
|
Step.insert(**{"name": "Boil", "type": boilstep_type, "config": {"kettle": boil_kettle, "temp": boil_temp, "timer": boil_time}})
|
||||||
## Add Whirlpool step
|
## Add Whirlpool step
|
||||||
Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}})
|
Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}})
|
||||||
self.api.emit("UPDATE_ALL_STEPS", Step.get_all())
|
|
||||||
|
self.api.ws_emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||||
self.api.notify(headline="Recipe %s loaded successfully" % name, message="")
|
self.api.notify(headline="Recipe %s loaded successfully" % name, message="")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.api.notify(headline="Failed to load Recipe", message=e.message, type="danger")
|
self.api.notify(headline="Failed to load Recipe", message=e.message, type="danger")
|
||||||
@@ -125,9 +127,6 @@ class BeerXMLImport(FlaskView):
|
|||||||
return float(e.find('./RECIPE[%s]/BOIL_TIME' % (str(id))).text)
|
return float(e.find('./RECIPE[%s]/BOIL_TIME' % (str(id))).text)
|
||||||
|
|
||||||
def getSteps(self, id):
|
def getSteps(self, id):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot()
|
e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot()
|
||||||
steps = []
|
steps = []
|
||||||
for e in e.findall('./RECIPE[%s]/MASH/MASH_STEPS/MASH_STEP' % (str(id))):
|
for e in e.findall('./RECIPE[%s]/MASH/MASH_STEPS/MASH_STEP' % (str(id))):
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class StepView(BaseView):
|
|||||||
"""
|
"""
|
||||||
self.model.delete_all()
|
self.model.delete_all()
|
||||||
self.api.emit("ALL_BREWING_STEPS_DELETED")
|
self.api.emit("ALL_BREWING_STEPS_DELETED")
|
||||||
|
self.api.set_config_parameter("brew_name", "")
|
||||||
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
|
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||||
return ('', 204)
|
return ('', 204)
|
||||||
|
|
||||||
|
|||||||
+44
-44
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
recipe_name = "Manuel 8881881 18181 "
|
||||||
|
print re.match("^[\sA-Za-z0-9_-]*$", recipe_name)
|
||||||
@@ -29,5 +29,6 @@ from modules.base_plugins.steps import *
|
|||||||
from modules.example_plugins.WebViewJquery import *
|
from modules.example_plugins.WebViewJquery import *
|
||||||
from modules.example_plugins.WebViewReactJs import *
|
from modules.example_plugins.WebViewReactJs import *
|
||||||
from modules.example_plugins.swagger import *
|
from modules.example_plugins.swagger import *
|
||||||
|
from modules.recipe_book import *
|
||||||
|
|
||||||
cbpi.run()
|
cbpi.run()
|
||||||
|
|||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<RECIPES>
|
||||||
|
<RECIPE>
|
||||||
|
<NAME>Pale Ale</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<TYPE>All Grain</TYPE>
|
||||||
|
<BREWER></BREWER>
|
||||||
|
<DISPLAY_BATCH_SIZE>6.5 gal</DISPLAY_BATCH_SIZE>
|
||||||
|
<DISPLAY_BOIL_SIZE>7.5 gal</DISPLAY_BOIL_SIZE>
|
||||||
|
<BATCH_SIZE>24.60517657</BATCH_SIZE>
|
||||||
|
<BOIL_SIZE>28.39058835</BOIL_SIZE>
|
||||||
|
<BOIL_TIME>60</BOIL_TIME>
|
||||||
|
<EFFICIENCY>63</EFFICIENCY>
|
||||||
|
<NOTES>
|
||||||
|
</NOTES>
|
||||||
|
<PRIMARY_TEMP>20</PRIMARY_TEMP>
|
||||||
|
<EST_COLOR>8.75</EST_COLOR>
|
||||||
|
<IBU>62.22</IBU>
|
||||||
|
<IBU_METHOD>Tinseth</IBU_METHOD>
|
||||||
|
<EST_ABV>4.88</EST_ABV>
|
||||||
|
<EST_OG>1.047 sg</EST_OG>
|
||||||
|
<EST_FG>1.01 sg</EST_FG>
|
||||||
|
<OG>1.047</OG>
|
||||||
|
<FG>1.01</FG>
|
||||||
|
<PRIMING_SUGAR_NAME></PRIMING_SUGAR_NAME>
|
||||||
|
<CARBONATION_USED></CARBONATION_USED>
|
||||||
|
<BF_PRIMING_METHOD></BF_PRIMING_METHOD>
|
||||||
|
<BF_PRIMING_AMOUNT></BF_PRIMING_AMOUNT>
|
||||||
|
<BF_CO2_LEVEL></BF_CO2_LEVEL>
|
||||||
|
<BF_CO2_UNIT>Volumes</BF_CO2_UNIT>
|
||||||
|
<URL></URL>
|
||||||
|
<BATCH_SIZE_MODE>f</BATCH_SIZE_MODE>
|
||||||
|
<YEAST_STARTER>false</YEAST_STARTER>
|
||||||
|
<NO_CHILL_EXTRA_MINUTES></NO_CHILL_EXTRA_MINUTES>
|
||||||
|
<STARTING_MASH_THICKNESS>3.33880656</STARTING_MASH_THICKNESS>
|
||||||
|
<PITCH_RATE>0.35</PITCH_RATE>
|
||||||
|
<FERMENTABLES>
|
||||||
|
<FERMENTABLE>
|
||||||
|
<NAME>Pale 2-Row</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<TYPE>Grain</TYPE>
|
||||||
|
<AMOUNT>4.08233133</AMOUNT>
|
||||||
|
<YIELD>80.43</YIELD>
|
||||||
|
<COLOR>1.8</COLOR>
|
||||||
|
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
|
||||||
|
<ORIGIN>American</ORIGIN>
|
||||||
|
</FERMENTABLE>
|
||||||
|
<FERMENTABLE>
|
||||||
|
<NAME>Caramel / Crystal 60L</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<TYPE>Grain</TYPE>
|
||||||
|
<AMOUNT>0.45359237</AMOUNT>
|
||||||
|
<YIELD>73.91</YIELD>
|
||||||
|
<COLOR>60</COLOR>
|
||||||
|
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
|
||||||
|
<ORIGIN>American</ORIGIN>
|
||||||
|
</FERMENTABLE>
|
||||||
|
<FERMENTABLE>
|
||||||
|
<NAME>Rye</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<TYPE>Grain</TYPE>
|
||||||
|
<AMOUNT>1.0205828325</AMOUNT>
|
||||||
|
<YIELD>82.61</YIELD>
|
||||||
|
<COLOR>3.5</COLOR>
|
||||||
|
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
|
||||||
|
<ORIGIN>American</ORIGIN>
|
||||||
|
</FERMENTABLE>
|
||||||
|
<FERMENTABLE>
|
||||||
|
<NAME>Flaked Wheat</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<TYPE>Adjunct</TYPE>
|
||||||
|
<AMOUNT>0.226796185</AMOUNT>
|
||||||
|
<YIELD>73.91</YIELD>
|
||||||
|
<COLOR>2</COLOR>
|
||||||
|
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
|
||||||
|
<ORIGIN></ORIGIN>
|
||||||
|
</FERMENTABLE>
|
||||||
|
<FERMENTABLE>
|
||||||
|
<NAME>Carapils</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<TYPE>Grain</TYPE>
|
||||||
|
<AMOUNT>0.226796185</AMOUNT>
|
||||||
|
<YIELD>76.09</YIELD>
|
||||||
|
<COLOR>1.3</COLOR>
|
||||||
|
<ADD_AFTER_BOIL>false</ADD_AFTER_BOIL>
|
||||||
|
<ORIGIN>German</ORIGIN>
|
||||||
|
</FERMENTABLE>
|
||||||
|
</FERMENTABLES>
|
||||||
|
<HOPS>
|
||||||
|
<HOP>
|
||||||
|
<NAME>El Dorado</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<ALPHA>15.7</ALPHA>
|
||||||
|
<AMOUNT>0.0283495231</AMOUNT>
|
||||||
|
<USE>First Wort</USE>
|
||||||
|
<USER_HOP_USE>First Wort</USER_HOP_USE>
|
||||||
|
<TIME>0</TIME>
|
||||||
|
<FORM>Leaf</FORM>
|
||||||
|
</HOP>
|
||||||
|
<HOP>
|
||||||
|
<NAME>El Dorado</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<ALPHA>15.7</ALPHA>
|
||||||
|
<AMOUNT>0.0283495231</AMOUNT>
|
||||||
|
<USE>Boil</USE>
|
||||||
|
<USER_HOP_USE>Boil</USER_HOP_USE>
|
||||||
|
<TIME>30</TIME>
|
||||||
|
<FORM>Leaf</FORM>
|
||||||
|
</HOP>
|
||||||
|
<HOP>
|
||||||
|
<NAME>El Dorado</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<ALPHA>15.7</ALPHA>
|
||||||
|
<AMOUNT>0.0283495231</AMOUNT>
|
||||||
|
<USE>Dry Hop</USE>
|
||||||
|
<USER_HOP_USE>Dry Hop</USER_HOP_USE>
|
||||||
|
<TIME>14400</TIME>
|
||||||
|
<FORM>Leaf</FORM>
|
||||||
|
</HOP>
|
||||||
|
</HOPS>
|
||||||
|
<MISCS/>
|
||||||
|
<MASH>
|
||||||
|
<NAME>Mash Steps</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<GRAIN_TEMP>20</GRAIN_TEMP>
|
||||||
|
<MASH_STEPS>
|
||||||
|
<MASH_STEP>
|
||||||
|
<NAME>STEP1</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<TYPE>Infusion</TYPE>
|
||||||
|
<STEP_TIME>60</STEP_TIME>
|
||||||
|
<INFUSE_AMOUNT>1.5141647136</INFUSE_AMOUNT>
|
||||||
|
<STEP_TEMP>65.555555555556</STEP_TEMP>
|
||||||
|
</MASH_STEP>
|
||||||
|
</MASH_STEPS>
|
||||||
|
</MASH>
|
||||||
|
<YEASTS>
|
||||||
|
<YEAST>
|
||||||
|
<NAME>California Ale Yeast WLP001</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<TYPE>Ale</TYPE>
|
||||||
|
<FORM>Liquid</FORM>
|
||||||
|
<AMOUNT>0.1</AMOUNT>
|
||||||
|
<PRODUCT_ID>WLP001</PRODUCT_ID>
|
||||||
|
<LABORATORY>White Labs</LABORATORY>
|
||||||
|
<ATTENUATION>76.5</ATTENUATION>
|
||||||
|
<FLOCCULATION>Medium</FLOCCULATION>
|
||||||
|
<MIN_TEMPERATURE>20</MIN_TEMPERATURE>
|
||||||
|
<MAX_TEMPERATURE>22.777777777778</MAX_TEMPERATURE>
|
||||||
|
</YEAST>
|
||||||
|
</YEASTS>
|
||||||
|
<WATERS/>
|
||||||
|
<STYLE>
|
||||||
|
<NAME>English IPA</NAME>
|
||||||
|
<VERSION>1</VERSION>
|
||||||
|
<CATEGORY>India Pale Ale (IPA)</CATEGORY>
|
||||||
|
<CATEGORY_NUMBER>14</CATEGORY_NUMBER>
|
||||||
|
<STYLE_LETTER>A</STYLE_LETTER>
|
||||||
|
<STYLE_GUIDE>BJCP</STYLE_GUIDE>
|
||||||
|
<TYPE>Ale</TYPE>
|
||||||
|
<OG_MIN>1.05</OG_MIN>
|
||||||
|
<OG_MAX>1.075</OG_MAX>
|
||||||
|
<FG_MIN>1.01</FG_MIN>
|
||||||
|
<FG_MAX>1.018</FG_MAX>
|
||||||
|
<ABV_MIN>5</ABV_MIN>
|
||||||
|
<ABV_MAX>7.5</ABV_MAX>
|
||||||
|
<IBU_MIN>40</IBU_MIN>
|
||||||
|
<IBU_MAX>60</IBU_MAX>
|
||||||
|
<COLOR_MIN>8</COLOR_MIN>
|
||||||
|
<COLOR_MAX>14</COLOR_MAX>
|
||||||
|
</STYLE>
|
||||||
|
</RECIPE>
|
||||||
|
</RECIPES>
|
||||||
Reference in New Issue
Block a user