Merge pull request #31 from pascal1404/master

Upload MMuM-JSON recipe -> test for development branch -> will also require update on UI
This commit is contained in:
Alexander Vollkopf 2022-01-24 22:19:42 +01:00 committed by GitHub
commit 69405a1c31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 305 additions and 10 deletions

View file

@ -59,6 +59,16 @@ class UploadController:
except: except:
return [] return []
async def get_json_recipes(self):
try:
path = os.path.join(".", 'config', "upload", "mmum.json")
e = json.load(open(path))
result =[]
result.append({'value': str(1), 'label': e['Name']})
return result
except:
return []
async def get_brewfather_recipes(self,offset=0): async def get_brewfather_recipes(self,offset=0):
brewfather = True brewfather = True
result=[] result=[]
@ -123,6 +133,20 @@ class UploadController:
self.cbpi.notify("Error" "XML Recipe upload failed: {}".format(e), NotificationType.ERROR) self.cbpi.notify("Error" "XML Recipe upload failed: {}".format(e), NotificationType.ERROR)
pass pass
elif content_type == 'application/json':
try:
mmum_json = recipe_file.read().decode('utf-8','replace')
if recipe_file and self.allowed_file(filename, 'json'):
self.path = os.path.join(".", 'config', "upload", "mmum.json")
f = open(self.path, "w")
f.write(mmum_json)
f.close()
self.cbpi.notify("Success", "JSON Recipe {} has been uploaded".format(filename), NotificationType.SUCCESS)
except Exception as e:
self.cbpi.notify("Error" "JSON Recipe upload failed: {}".format(e), NotificationType.ERROR)
pass
elif content_type == 'application/octet-stream': elif content_type == 'application/octet-stream':
try: try:
content = recipe_file.read() content = recipe_file.read()
@ -293,6 +317,217 @@ class UploadController:
else: else:
self.cbpi.notify('Recipe Upload', 'No default Kettle defined. Please specify default Kettle in settings', NotificationType.ERROR) self.cbpi.notify('Recipe Upload', 'No default Kettle defined. Please specify default Kettle in settings', NotificationType.ERROR)
def findMax(self, string):
self.path = os.path.join(".", 'config', "upload", "mmum.json")
e = json.load(open(self.path))
for idx in range(1,20):
search_string = string.replace("%%",str(idx))
i = idx
if search_string not in e:
break
return i
def getJsonMashin(self, id):
self.path = os.path.join(".", 'config', "upload", "mmum.json")
e = json.load(open(self.path))
return float(e['Infusion_Einmaischtemperatur'])
async def json_recipe_creation(self, Recipe_ID):
config = self.get_config_values()
if self.kettle is not None:
# load mmum-json file located in upload folder
self.path = os.path.join(".", 'config', "upload", "mmum.json")
if os.path.exists(self.path) is False:
self.cbpi.notify("File Not Found", "Please upload a MMuM-JSON File", NotificationType.ERROR)
e = json.load(open(self.path))
name = e['Name']
boil_time = float(e['Kochzeit_Wuerze'])
await self.create_recipe(name)
hops = []
for idx in range(1,self.findMax("Hopfen_%%_Kochzeit")):
hops_name = "%sg %s %s%% alpha" % (e["Hopfen_{}_Menge".format(idx)],e["Hopfen_{}_Sorte".format(idx)],e["Hopfen_{}_alpha".format(idx)])
if e["Hopfen_{}_Kochzeit".format(idx)].isnumeric():
if boil_time is not e["Hopfen_{}_Kochzeit".format(idx)].isnumeric():
alert = float(e["Hopfen_{}_Kochzeit".format(idx)])
elif e["Hopfen_{}_Kochzeit".format(idx)] == "Whirlpool":
alert = float(1)
else:
self.api.notify(headline="No Number at Hoptime", message="Please change json-File at Hopfen_{}_Kochzeit".format(idx), type="danger")
alert = float(1)
hops.append({"name":hops_name,"time":alert})
firstHops=[]
for idx in range(1,self.findMax("Hopfen_VWH_%%_Sorte")):
firstHops_name = "%sg %s %s%% alpha" % (e["Hopfen_VWH_{}_Menge".format(idx)],e["Hopfen_VWH_{}_Sorte".format(idx)],e["Hopfen_VWH_{}_alpha".format(idx)])
firstHops.append({"name":firstHops_name})
FirstWort= self.getFirstWort(firstHops, "json")
miscs = []
for idx in range(1,self.findMax("WeitereZutat_Wuerze_%%_Kochzeit")):
miscs_name = "%s%s %s" % (e["WeitereZutat_Wuerze_{}_Menge".format(idx)],e["WeitereZutat_Wuerze_{}_Einheit".format(idx)],e["WeitereZutat_Wuerze_{}_Name".format(idx)])
if e["WeitereZutat_Wuerze_{}_Kochzeit".format(idx)].isnumeric():
alert = float(e["WeitereZutat_Wuerze_{}_Kochzeit".format(idx)])
elif e["WeitereZutat_Wuerze_{}_Kochzeit".format(idx)] == "Whirlpool":
alert = float(1)
else:
self.api.notify(headline="No Number at Hoptime", message="Please change json-File at WeitereZutat_Wuerze_{}_Kochzeit".format(idx), type="danger")
alert = float(1)
miscs.append({"name":miscs_name,"time":alert})
# Mash Steps -> first step is different as it heats up to defined temp and stops with notification to add malt
# AutoMode is yes to start and stop automatic mode or each step
MashIn_Flag = True
step_kettle = self.id
logging.info(step_kettle) ###################################################
for row in self.getSteps(Recipe_ID, "json"):
step_name = str(row.get("name"))
step_timer = str(int(row.get("timer")))
step_temp = str(int(row.get("temp")))
sensor = self.kettle.sensor
if MashIn_Flag == True:
if row.get("timer") == 0:
step_type = self.mashin if self.mashin != "" else "MashInStep"
Notification = "Target temperature reached. Please add malt."
MashIn_Flag = False
if step_name is None or step_name == "":
step_name = "MashIn"
elif self.addmashin == "Yes":
step_type = self.mashin if self.mashin != "" else "MashInStep"
Notification = "Target temperature reached. Please add malt."
MashIn_Flag = False
step_string = { "name": "MashIn",
"props": {
"AutoMode": self.AutoMode,
"Kettle": self.id,
"Sensor": self.kettle.sensor,
"Temp": self.getJsonMashin(Recipe_ID),
"Timer": 0,
"Notification": Notification
},
"status_text": "",
"status": "I",
"type": step_type
}
await self.create_step(step_string)
logging.info(step_kettle) ###################################################
step_type = self.mash if self.mash != "" else "MashStep"
Notification = ""
else:
step_type = self.mash if self.mash != "" else "MashStep"
Notification = ""
else:
step_type = self.mash if self.mash != "" else "MashStep"
Notification = ""
step_string = { "name": step_name,
"props": {
"AutoMode": self.AutoMode,
"Kettle": self.id,
"Sensor": self.kettle.sensor,
"Temp": step_temp,
"Timer": step_timer,
"Notification": Notification
},
"status_text": "",
"status": "I",
"type": step_type
}
await self.create_step(step_string)
# MashOut -> Simple step that sends notification and waits for user input to move to next step (AutoNext=No)
if self.mashout == "NotificationStep":
step_string = { "name": "Lautering",
"props": {
"AutoNext": "No",
"Kettle": self.id,
"Notification": "Mash Process completed. Please start lautering and press next to start boil."
},
"status_text": "",
"status": "I",
"type": self.mashout
}
await self.create_step(step_string)
# Measure Original Gravity -> Simple step that sends notification
step_string = { "name": "Measure Original Gravity",
"props": {
"AutoNext": "No",
"Kettle": self.id,
"Notification": "What is the original gravity of the beer wort?"
},
"status_text": "",
"status": "I",
"type": "NotificationStep"
}
await self.create_step(step_string)
# Boil step including hop alarms and alarm for first wort hops -> Automode is set tu yes
Hops = self.getBoilAlerts(hops, miscs, "json")
step_kettle = self.boilid
step_type = self.boil if self.boil != "" else "BoilStep"
step_time = str(int(boil_time))
step_temp = self.BoilTemp
sensor = self.boilkettle.sensor
LidAlert = "Yes"
logging.info(step_temp) ###################################################
step_string = { "name": "Boil Step",
"props": {
"AutoMode": self.AutoMode,
"Kettle": step_kettle,
"Sensor": sensor,
"Temp": step_temp,
"Timer": step_time,
"First_Wort": FirstWort,
"LidAlert": LidAlert,
"Hop_1": Hops[0],
"Hop_2": Hops[1],
"Hop_3": Hops[2],
"Hop_4": Hops[3],
"Hop_5": Hops[4],
"Hop_6": Hops[5]
},
"status_text": "",
"status": "I",
"type": step_type
}
await self.create_step(step_string)
# Measure Original Gravity -> Simple step that sends notification
step_string = { "name": "Measure Original Gravity",
"props": {
"AutoNext": "No",
"Kettle": self.id,
"Notification": "What is the original gravity of the beer wort?"
},
"status_text": "",
"status": "I",
"type": "NotificationStep"
}
await self.create_step(step_string)
await self.create_Whirlpool_Cooldown()
self.cbpi.notify('MMuM-JSON Recipe created ', name, NotificationType.INFO)
else:
self.cbpi.notify('Recipe Upload', 'No default Kettle defined. Please specify default Kettle in settings', NotificationType.ERROR)
async def xml_recipe_creation(self, Recipe_ID): async def xml_recipe_creation(self, Recipe_ID):
config = self.get_config_values() config = self.get_config_values()
@ -316,7 +551,7 @@ class UploadController:
# AutoMode is yes to start and stop automatic mode or each step # AutoMode is yes to start and stop automatic mode or each step
MashIn_Flag = True MashIn_Flag = True
step_kettle = self.id step_kettle = self.id
for row in self.getSteps(Recipe_ID): for row in self.getSteps(Recipe_ID, "xml"):
step_name = str(row.get("name")) step_name = str(row.get("name"))
step_timer = str(int(row.get("timer"))) step_timer = str(int(row.get("timer")))
step_temp = str(int(row.get("temp"))) step_temp = str(int(row.get("temp")))
@ -428,16 +663,27 @@ class UploadController:
# XML functions to retrieve xml repice parameters (if multiple recipes are stored in one xml file, id could be used) # XML functions to retrieve xml repice parameters (if multiple recipes are stored in one xml file, id could be used)
def getSteps(self, id): def getSteps(self, id, recipe_type):
e = xml.etree.ElementTree.parse(self.path).getroot()
steps = [] steps = []
for e in e.findall('./RECIPE[%s]/MASH/MASH_STEPS/MASH_STEP' % (str(id))): if recipe_type == "xml":
if self.cbpi.config.get("TEMP_UNIT", "C") == "C": e = xml.etree.ElementTree.parse(self.path).getroot()
temp = float(e.find("STEP_TEMP").text) for e in e.findall('./RECIPE[%s]/MASH/MASH_STEPS/MASH_STEP' % (str(id))):
else: if self.cbpi.config.get("TEMP_UNIT", "C") == "C":
temp = round(9.0 / 5.0 * float(e.find("STEP_TEMP").text) + 32, 2) temp = float(e.find("STEP_TEMP").text)
steps.append({"name": e.find("NAME").text, "temp": temp, "timer": float(e.find("STEP_TIME").text)}) else:
temp = round(9.0 / 5.0 * float(e.find("STEP_TEMP").text) + 32, 2)
steps.append({"name": e.find("NAME").text, "temp": temp, "timer": float(e.find("STEP_TIME").text)})
elif recipe_type == "json":
self.path = os.path.join(".", 'config', "upload", "mmum.json")
e = json.load(open(self.path))
for idx in range(1,self.findMax("Infusion_Rastzeit%%")):
if self.cbpi.config.get("TEMP_UNIT", "C") == "C":
temp = float(e["Infusion_Rasttemperatur{}".format(idx)])
else:
temp = round(9.0 / 5.0 * float(e["Infusion_Rasttemperatur{}".format(idx)]) + 32, 2)
steps.append({"name": "Rast {}".format(idx), "temp": temp, "timer": float(e["Infusion_Rastzeit{}".format(idx)])})
return steps return steps
async def bf_recipe_creation(self, Recipe_ID): async def bf_recipe_creation(self, Recipe_ID):
@ -632,6 +878,9 @@ class UploadController:
alerts.append(float(hop['time'])) alerts.append(float(hop['time']))
elif recipe_type == "kbh": elif recipe_type == "kbh":
alerts.append(float(hop[0])) alerts.append(float(hop[0]))
elif recipe_type == "json":
alerts.append(float(hop['time']))
## There might also be miscelaneous additions during boild time ## There might also be miscelaneous additions during boild time
if miscs is not None: if miscs is not None:
for misc in miscs: for misc in miscs:
@ -644,6 +893,8 @@ class UploadController:
alerts.append(float(misc['time'])) alerts.append(float(misc['time']))
elif recipe_type == "kbh": elif recipe_type == "kbh":
alerts.append(float(misc[0])) alerts.append(float(misc[0]))
elif recipe_type == "json":
alerts.append(float(misc['time']))
## Dedupe and order the additions by their time, to prevent multiple alerts at the same time ## Dedupe and order the additions by their time, to prevent multiple alerts at the same time
alerts = sorted(list(set(alerts))) alerts = sorted(list(set(alerts)))
## CBP should have these additions in reverse ## CBP should have these additions in reverse
@ -672,6 +923,9 @@ class UploadController:
for hop in hops: for hop in hops:
if hop['use'] == "First Wort": if hop['use'] == "First Wort":
alert="Yes" alert="Yes"
elif recipe_type == "json":
for hop in hops:
alert="Yes"
return alert return alert
async def create_Whirlpool_Cooldown(self): async def create_Whirlpool_Cooldown(self):

View file

@ -101,6 +101,47 @@ class UploadHttpEndpoints():
await self.controller.xml_recipe_creation(xml_id['id']) await self.controller.xml_recipe_creation(xml_id['id'])
return web.Response(status=200) return web.Response(status=200)
@request_mapping(path='/json', method="GET", auth_required=False)
async def get_json_list(self, request):
"""
---
description: Get recipe list from json file
tags:
- Upload
responses:
"200":
description: successful operation
"""
json_list = await self.controller.get_json_recipes()
return web.json_response(json_list)
@request_mapping(path='/json', method="POST", auth_required=False)
async def create_json_recipe(self, request):
"""
---
description: Create recipe from json file with selected id
tags:
- Upload
parameters:
- name: "id"
in: "body"
description: "Recipe ID: {'id': ID}"
required: true
type: "string"
responses:
"200":
description: successful operation
"""
json_id = await request.json()
await self.controller.json_recipe_creation(json_id['id'])
return web.Response(status=200)
@request_mapping(path='/bf/{offset}/', method="POST", auth_required=False) @request_mapping(path='/bf/{offset}/', method="POST", auth_required=False)
async def get_bf_list(self, request): async def get_bf_list(self, request):
""" """