| @@ -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 | ||||
| @@ -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") | |||||
| 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 | # 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) | ||||
| @@ -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() | ||||
| @@ -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> | |||||