from flask import json, request from flask_classy import FlaskView, route from git import Repo, Git import sqlite3 from modules.app_config import cbpi from werkzeug.utils import secure_filename import pprint import time import os from modules.steps import Step,StepView import xml.etree.ElementTree class BeerXMLImport(FlaskView): BEER_XML_FILE = "./upload/beer.xml" @route('/', methods=['GET']) def get(self): if not os.path.exists(self.BEER_XML_FILE): self.api.notify(headline="File Not Found", message="Please upload a Beer.xml File", type="danger") return ('', 404) result = [] e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() result = [] for idx, val in enumerate(e.findall('RECIPE')): result.append({"id": idx+1, "name": val.find("NAME").text}) return json.dumps(result) def allowed_file(self, filename): return '.' in filename and filename.rsplit('.', 1)[1] in set(['xml']) @route('/upload', methods=['POST']) def upload_file(self): try: 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")) self.api.notify(headline="Upload Successful", message="The Beer XML file was uploaded succesfully") return ('', 204) return ('', 404) except Exception as e: self.api.notify(headline="Upload Failed", message="Failed to upload Beer xml", type="danger") return ('', 500) @route('/', methods=['POST']) def load(self, id): steps = self.getSteps(id) boil_time_alerts = self.getBoilAlerts(id) name = self.getRecipeName(id) self.api.set_config_parameter("brew_name", name) boil_time = self.getBoilTime(id) mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep") mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep") mash_kettle = cbpi.get_config_parameter("step_mash_kettle", None) boilstep_type = cbpi.get_config_parameter("step_boil", "BoilStep") boil_kettle = cbpi.get_config_parameter("step_boil_kettle", None) boil_temp = 100 if cbpi.get_config_parameter("unit", "C") == "C" else 212 # READ KBH DATABASE Step.delete_all() StepView().reset() try: # Add mash in or mash step, depends on timer > 0 for row in steps: if row.get("timer") > 0: Step.insert(**{"name": row.get("name"), "type": mashstep_type, "config": {"kettle": mash_kettle, "temp": float(row.get("temp")), "timer": row.get("timer")}}) else: Step.insert(**{"name": row.get("name"), "type": mashinstep_type, "config": {"kettle": mash_kettle, "temp": float(row.get("temp"))}}) # Add chilling step Step.insert(**{"name": "ChilStep", "type": "ChilStep", "config": {"timer": 15}}) # Add boiling step Step.insert(**{ "name": "Boil", "type": boilstep_type, "config": { "kettle": boil_kettle, "temp": boil_temp, "timer": boil_time, # Beer XML defines additions as the total time spent in boiling, # CBP defines it as time-until-alert # Also, The model supports five boil-time additions. # Set the rest to None to signal them being absent "hop_1": boil_time - boil_time_alerts[0] if len(boil_time_alerts) >= 1 else None, "hop_2": boil_time - boil_time_alerts[1] if len(boil_time_alerts) >= 2 else None, "hop_3": boil_time - boil_time_alerts[2] if len(boil_time_alerts) >= 3 else None, "hop_4": boil_time - boil_time_alerts[3] if len(boil_time_alerts) >= 4 else None, "hop_5": boil_time - boil_time_alerts[4] if len(boil_time_alerts) >= 5 else None } }) # Add Whirlpool step Step.insert(**{"name": "Whirlpool", "type": "ChilStep", "config": {"timer": 15}}) StepView().reset() self.api.emit("UPDATE_ALL_STEPS", Step.get_all()) self.api.notify(headline="Recipe %s loaded successfully" % name, message="") except Exception as e: self.api.notify(headline="Failed to load Recipe", message=e.message, type="danger") return ('', 500) return ('', 204) def getRecipeName(self, id): e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() return e.find('./RECIPE[%s]/NAME' % (str(id))).text def getBoilTime(self, id): e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() return float(e.find('./RECIPE[%s]/BOIL_TIME' % (str(id))).text) def getBoilAlerts(self, id): e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() recipe = e.find('./RECIPE[%s]' % (str(id))) alerts = [] for e in recipe.findall('./HOPS/HOP'): use = e.find('USE').text ## Hops which are not used in the boil step should not cause alerts if use != 'Aroma' and use != 'Boil': continue alerts.append(float(e.find('TIME').text)) ## There might also be miscelaneous additions during boild time for e in recipe.findall('MISCS/MISC[USE="Boil"]'): alerts.append(float(e.find('TIME').text)) ## Dedupe and order the additions by their time, to prevent multiple alerts at the same time alerts = sorted(list(set(alerts))) ## CBP should have these additions in reverse alerts.reverse() return alerts def getSteps(self, id): e = xml.etree.ElementTree.parse(self.BEER_XML_FILE).getroot() steps = [] for e in e.findall('./RECIPE[%s]/MASH/MASH_STEPS/MASH_STEP' % (str(id))): if self.api.get_config_parameter("unit", "C") == "C": temp = float(e.find("STEP_TEMP").text) else: temp = round(9.0 / 5.0 * float(e.find("STEP_TEMP").text) + 32, 2) if e.find("STEP_TIME").text is None: stepTime = 0.0 else: stepTime = float(e.find("STEP_TIME").text) steps.append({"name": e.find("NAME").text, "temp": temp, "timer": stepTime}) return steps @cbpi.initalizer() def init(cbpi): BeerXMLImport.api = cbpi BeerXMLImport.register(cbpi.app, route_base='/api/beerxml')