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")
|
||||
def myaction(self):
|
||||
print "WOOHOO"
|
||||
pass
|
||||
|
||||
def on(self, power=100):
|
||||
@@ -29,6 +30,7 @@ class MyController(KettleController):
|
||||
def run(self):
|
||||
while self.is_running():
|
||||
|
||||
|
||||
self.sleep(1)
|
||||
|
||||
@cbpi.addon.fermenter.controller()
|
||||
@@ -38,6 +40,8 @@ class MyController2(FermenterController):
|
||||
def run(self):
|
||||
while self.is_running():
|
||||
print "HALLO"
|
||||
|
||||
self.get_target_temp()
|
||||
self.sleep(1)
|
||||
|
||||
@cbpi.addon.core.initializer(order=200)
|
||||
|
||||
@@ -24,7 +24,7 @@ class Dummy(Sensor):
|
||||
@cbpi.addon.sensor.action("WOHOO")
|
||||
def myaction(self):
|
||||
|
||||
print "SENSOR ACTION HALLO!!!"
|
||||
self.api.notify(headline="WOHOO", message="HALLO")
|
||||
|
||||
def execute(self):
|
||||
while True:
|
||||
@@ -32,7 +32,7 @@ class Dummy(Sensor):
|
||||
self.update_value(int(self.text))
|
||||
except:
|
||||
pass
|
||||
self.api.sleep(1)
|
||||
self.api.sleep(5)
|
||||
|
||||
@cbpi.addon.core.action(key="clear", label="Clear all Logs")
|
||||
def woohoo(cbpi):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from modules.core.basetypes import Step
|
||||
from modules.core.core import cbpi
|
||||
from modules.core.proptypes import Property
|
||||
import time
|
||||
|
||||
|
||||
@cbpi.addon.step.type("Dummy Step")
|
||||
@@ -23,3 +24,222 @@ class Dummy(Step):
|
||||
def reset(self):
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
class Step(Base, Timer):
|
||||
|
||||
|
||||
@@ -267,6 +269,25 @@ class Step(Base, Timer):
|
||||
self.value = None
|
||||
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):
|
||||
return self.__dirty
|
||||
|
||||
|
||||
+10
-1
@@ -364,13 +364,22 @@ class CraftBeerPI(object):
|
||||
method.callback = True
|
||||
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)
|
||||
if cfg is None:
|
||||
return default
|
||||
else:
|
||||
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):
|
||||
|
||||
if self.eventbus.get(key) is not None:
|
||||
|
||||
@@ -132,3 +132,4 @@ class FermenterStep(DBModel):
|
||||
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,))
|
||||
get_db().commit()
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ class FermenterView(BaseView):
|
||||
|
||||
@route('/<int:id>/start', methods=['POST'])
|
||||
def start_fermentation(self, id):
|
||||
print "START"
|
||||
active = None
|
||||
for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
|
||||
if s.state == 'A':
|
||||
@@ -196,6 +197,7 @@ class FermenterView(BaseView):
|
||||
return cbpi.cache["fermenter"].get(id)
|
||||
|
||||
def target_temp_reached(self,id, step):
|
||||
print "TARGET TEMP REACHED"
|
||||
timestamp = time.time()
|
||||
|
||||
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])
|
||||
|
||||
def check_step(self):
|
||||
print "CHECK STEP"
|
||||
print cbpi.cache["fermenter_task"]
|
||||
for key, value in cbpi.cache["fermenter_task"].iteritems():
|
||||
|
||||
print value
|
||||
try:
|
||||
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:
|
||||
|
||||
print "TIMER IS NONE"
|
||||
if value.direction == "H" :
|
||||
|
||||
print "TIMER WATING FOR HEATING"
|
||||
if current_temp >= value.temp:
|
||||
self.target_temp_reached(key,value)
|
||||
else:
|
||||
print "TIMER WATING FOR COILING"
|
||||
if current_temp <= value.temp:
|
||||
self.target_temp_reached(key, value)
|
||||
else:
|
||||
@@ -231,6 +236,7 @@ class FermenterView(BaseView):
|
||||
else:
|
||||
pass
|
||||
except Exception as e:
|
||||
print e
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -217,6 +217,17 @@ class KettleView(BaseView):
|
||||
self.api.brewing.toggle_automatic(id)
|
||||
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)
|
||||
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")
|
||||
self.api.cache["plugins"] = self.merge(yaml.load(response.text), self.api.cache["plugins"])
|
||||
for key, value in cbpi.cache["plugins"].iteritems():
|
||||
print key
|
||||
value["installed"] = os.path.isdir("./plugins/%s/" % (key))
|
||||
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':
|
||||
file = request.files['file']
|
||||
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")
|
||||
return ('', 204)
|
||||
return ('', 404)
|
||||
except Exception as e:
|
||||
print e
|
||||
self.api.notify(headline="Upload Failed", message="Failed to upload Beer xml", type="danger")
|
||||
return ('', 500)
|
||||
|
||||
@@ -88,12 +89,12 @@ class BeerXMLImport(FlaskView):
|
||||
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")
|
||||
mash_kettle = cbpi.get_config_parameter("step_mash_kettle", None)
|
||||
mashstep_type = self.api.get_config_parameter("step_mash", "MashStep")
|
||||
mash_kettle = self.api.get_config_parameter("step_mash_kettle", None)
|
||||
|
||||
boilstep_type = cbpi.get_config_parameter("step_boil", "BoilStep")
|
||||
boil_kettle = cbpi.get_config_parameter("step_boil_kettle", None)
|
||||
boil_temp = 100 if cbpi.get_config_parameter("unit", "C") == "C" else 212
|
||||
boilstep_type = self.api.get_config_parameter("step_boil", "BoilStep")
|
||||
boil_kettle = self.api.get_config_parameter("step_boil_kettle", None)
|
||||
boil_temp = 100 if self.api.get_config_parameter("unit", "C") == "C" else 212
|
||||
|
||||
# READ KBH DATABASE
|
||||
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}})
|
||||
## Add Whirlpool step
|
||||
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="")
|
||||
except Exception as e:
|
||||
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)
|
||||
|
||||
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))):
|
||||
|
||||
@@ -43,6 +43,7 @@ class StepView(BaseView):
|
||||
"""
|
||||
self.model.delete_all()
|
||||
self.api.emit("ALL_BREWING_STEPS_DELETED")
|
||||
self.api.set_config_parameter("brew_name", "")
|
||||
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||
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.WebViewReactJs import *
|
||||
from modules.example_plugins.swagger import *
|
||||
from modules.recipe_book import *
|
||||
|
||||
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