2021-05-26 21:00:35 +02:00
import os
import pathlib
import aiohttp
from aiohttp import web
import asyncio
from cbpi . api import *
import xml . etree . ElementTree
import sqlite3
from voluptuous . schema_builder import message
2021-12-27 18:11:40 +01:00
from cbpi . api . dataclasses import NotificationAction , NotificationType , Actor , Sensor , Kettle
2021-05-26 21:00:35 +02:00
from cbpi . controller . kettle_controller import KettleController
from cbpi . api . base import CBPiBase
from cbpi . api . config import ConfigType
import webbrowser
2021-06-06 13:00:55 +02:00
import logging
import os . path
from os import listdir
from os . path import isfile , join
import json
import shortuuid
import yaml
from . . api . step import StepMove , StepResult , StepState
import re
2021-06-13 14:38:18 +02:00
import base64
2021-06-06 13:00:55 +02:00
2021-10-28 18:35:00 +02:00
2021-06-06 13:00:55 +02:00
class UploadController :
2021-05-26 21:00:35 +02:00
def __init__ ( self , cbpi ) :
self . cbpi = cbpi
2021-06-06 13:00:55 +02:00
self . logger = logging . getLogger ( __name__ )
async def get_kbh_recipes ( self ) :
try :
path = os . path . join ( " . " , ' config ' , " upload " , " kbh.db " )
conn = sqlite3 . connect ( path )
c = conn . cursor ( )
c . execute ( ' SELECT ID, Sudname, Status FROM Sud ' )
data = c . fetchall ( )
result = [ ]
for row in data :
element = { ' value ' : str ( row [ 0 ] ) , ' label ' : str ( row [ 1 ] ) }
result . append ( element )
return result
except :
return [ ]
2021-05-26 21:00:35 +02:00
2021-06-06 13:00:55 +02:00
async def get_xml_recipes ( self ) :
try :
path = os . path . join ( " . " , ' config ' , " upload " , " beer.xml " )
e = xml . etree . ElementTree . parse ( path ) . getroot ( )
result = [ ]
counter = 1
for idx , val in enumerate ( e . findall ( ' RECIPE ' ) ) :
element = { ' value ' : str ( counter ) , ' label ' : val . find ( " NAME " ) . text }
result . append ( element )
counter + = 1
return result
except :
return [ ]
2021-05-26 21:00:35 +02:00
2021-07-04 17:42:37 +02:00
async def get_brewfather_recipes ( self , offset = 0 ) :
2021-06-13 14:38:18 +02:00
brewfather = True
result = [ ]
self . url = " https://api.brewfather.app/v1/recipes "
brewfather_user_id = self . cbpi . config . get ( " brewfather_user_id " , None )
if brewfather_user_id == " " or brewfather_user_id is None :
brewfather = False
brewfather_api_key = self . cbpi . config . get ( " brewfather_api_key " , None )
if brewfather_api_key == " " or brewfather_api_key is None :
brewfather = False
if brewfather == True :
encodedData = base64 . b64encode ( bytes ( f " { brewfather_user_id } : { brewfather_api_key } " , " ISO-8859-1 " ) ) . decode ( " ascii " )
headers = { " Authorization " : " Basic %s " % encodedData }
2021-07-04 17:42:37 +02:00
parameters = { " limit " : 50 , ' offset ' : offset }
2021-06-13 14:38:18 +02:00
async with aiohttp . ClientSession ( headers = headers ) as bf_session :
2021-07-04 13:39:25 +02:00
async with bf_session . get ( self . url , params = parameters ) as r :
2021-06-13 14:38:18 +02:00
bf_recipe_list = await r . json ( )
await bf_session . close ( )
if bf_recipe_list :
for row in bf_recipe_list :
recipe_id = row [ ' _id ' ]
name = row [ ' name ' ]
element = { ' value ' : recipe_id , ' label ' : name }
result . append ( element )
return result
else :
return [ ]
else :
return [ ]
2021-05-30 11:58:18 +02:00
def get_creation_path ( self ) :
creation_path = self . cbpi . config . get ( " RECIPE_CREATION_PATH " , " upload " )
path = { ' path ' : ' upload ' } if creation_path == ' ' else { ' path ' : creation_path }
return path
2021-06-06 13:00:55 +02:00
def allowed_file ( self , filename , extension ) :
return ' . ' in filename and filename . rsplit ( ' . ' , 1 ) [ 1 ] in set ( [ extension ] )
2021-05-30 11:58:18 +02:00
2021-06-06 13:00:55 +02:00
async def FileUpload ( self , data ) :
2021-05-26 21:00:35 +02:00
fileData = data [ ' File ' ]
2021-06-06 13:00:55 +02:00
filename = fileData . filename
recipe_file = fileData . file
content_type = fileData . content_type
2021-05-26 21:00:35 +02:00
2021-06-06 13:00:55 +02:00
if content_type == ' text/xml ' :
2021-05-26 21:00:35 +02:00
try :
2021-10-28 18:35:00 +02:00
beer_xml = recipe_file . read ( ) . decode ( ' utf-8 ' , ' replace ' )
2021-06-06 13:00:55 +02:00
if recipe_file and self . allowed_file ( filename , ' xml ' ) :
2021-05-26 21:00:35 +02:00
self . path = os . path . join ( " . " , ' config ' , " upload " , " beer.xml " )
f = open ( self . path , " w " )
2021-06-06 13:00:55 +02:00
f . write ( beer_xml )
2021-05-26 21:00:35 +02:00
f . close ( )
2021-06-06 13:00:55 +02:00
self . cbpi . notify ( " Success " , " XML Recipe {} has been uploaded " . format ( filename ) , NotificationType . SUCCESS )
2021-10-28 18:35:00 +02:00
except Exception as e :
self . cbpi . notify ( " Error " " XML Recipe upload failed: {} " . format ( e ) , NotificationType . ERROR )
2021-05-26 21:00:35 +02:00
pass
2021-06-06 13:00:55 +02:00
elif content_type == ' application/octet-stream ' :
2021-05-26 21:00:35 +02:00
try :
2021-06-06 13:00:55 +02:00
content = recipe_file . read ( )
if recipe_file and self . allowed_file ( filename , ' sqlite ' ) :
2021-05-26 21:00:35 +02:00
self . path = os . path . join ( " . " , ' config ' , " upload " , " kbh.db " )
f = open ( self . path , " wb " )
f . write ( content )
f . close ( )
2021-06-06 13:00:55 +02:00
self . cbpi . notify ( " Success " , " Kleiner Brauhelfer database has been uploaded " , NotificationType . SUCCESS )
2021-05-26 21:00:35 +02:00
except :
self . cbpi . notify ( " Error " , " Kleiner Brauhelfer database upload failed " , NotificationType . ERROR )
pass
else :
self . cbpi . notify ( " Error " , " Wrong content type. Upload failed " , NotificationType . ERROR )
async def kbh_recipe_creation ( self , Recipe_ID ) :
2021-06-15 21:03:56 +02:00
config = self . get_config_values ( )
if self . kettle is not None :
2021-05-26 21:00:35 +02:00
# load beerxml file located in upload folder
self . path = os . path . join ( " . " , ' config ' , " upload " , " kbh.db " )
if os . path . exists ( self . path ) is False :
self . cbpi . notify ( " File Not Found " , " Please upload a kbh V2 databsel file " , NotificationType . ERROR )
try :
2021-06-15 21:03:56 +02:00
# Get Recipe Nmae
2021-05-26 21:00:35 +02:00
conn = sqlite3 . connect ( self . path )
c = conn . cursor ( )
c . execute ( ' SELECT Sudname FROM Sud WHERE ID = ? ' , ( Recipe_ID , ) )
row = c . fetchone ( )
name = row [ 0 ]
2021-06-15 21:03:56 +02:00
# get MashIn Temp
mashin_temp = None
2021-05-26 21:00:35 +02:00
c . execute ( ' SELECT Temp FROM Rasten WHERE Typ = 0 AND SudID = ? ' , ( Recipe_ID , ) )
row = c . fetchone ( )
2021-06-15 21:03:56 +02:00
try :
if self . cbpi . config . get ( " TEMP_UNIT " , " C " ) == " C " :
mashin_temp = str ( int ( row [ 0 ] ) )
else :
mashin_temp = str ( round ( 9.0 / 5.0 * int ( row [ 0 ] ) + 32 ) )
except :
pass
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
# get the hop addition times
c . execute ( ' SELECT Zeit FROM Hopfengaben WHERE Vorderwuerze = 0 AND SudID = ? ' , ( Recipe_ID , ) )
hops = c . fetchall ( )
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
# get the misc addition times
c . execute ( ' SELECT Zugabedauer FROM WeitereZutatenGaben WHERE Zeitpunkt = 1 AND SudID = ? ' , ( Recipe_ID , ) )
miscs = c . fetchall ( )
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
try :
c . execute ( ' SELECT Zeit FROM Hopfengaben WHERE Vorderwuerze = 1 AND SudID = ? ' , ( Recipe_ID , ) )
FW_Hops = c . fetchall ( )
FirstWort = self . getFirstWort ( FW_Hops , " kbh " )
except :
FirstWort = " No "
2021-06-13 14:38:18 +02:00
2021-05-26 21:00:35 +02:00
c . execute ( ' SELECT Kochdauer FROM Sud WHERE ID = ? ' , ( Recipe_ID , ) )
row = c . fetchone ( )
2021-06-15 21:03:56 +02:00
BoilTime = str ( int ( row [ 0 ] ) )
2021-05-26 21:00:35 +02:00
2021-06-13 14:38:18 +02:00
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
await self . create_recipe ( name )
2021-06-13 14:38:18 +02:00
2021-06-15 21:03:56 +02:00
if mashin_temp is not None :
step_type = self . mashin if self . mashin != " " else " MashInStep "
step_string = { " name " : " MashIn " ,
" props " : {
2021-06-21 06:51:58 +02:00
" AutoMode " : self . AutoMode ,
2021-06-15 21:03:56 +02:00
" Kettle " : self . id ,
" Sensor " : self . kettle . sensor ,
" Temp " : mashin_temp ,
" Timer " : " 0 " ,
" Notification " : " Target temperature reached. Please add malt. "
} ,
" status_text " : " " ,
" status " : " I " ,
" type " : step_type
}
await self . create_step ( step_string )
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
for row in c . execute ( ' SELECT Name, Temp, Dauer FROM Rasten WHERE Typ <> 0 AND SudID = ? ' , ( Recipe_ID , ) ) :
2021-07-15 07:16:26 +02:00
if mashin_temp is None and self . addmashin == " Yes " :
step_type = self . mashin if self . mashin != " " else " MashInStep "
step_string = { " name " : " MashIn " ,
" props " : {
" AutoMode " : self . AutoMode ,
" Kettle " : self . id ,
" Sensor " : self . kettle . sensor ,
" Temp " : str ( int ( row [ 1 ] ) ) if self . TEMP_UNIT == " C " else str ( round ( 9.0 / 5.0 * int ( row [ 1 ] ) + 32 ) ) ,
" Timer " : " 0 " ,
" Notification " : " Target temperature reached. Please add malt. "
} ,
" status_text " : " " ,
" status " : " I " ,
" type " : step_type
}
await self . create_step ( step_string )
2021-06-15 21:03:56 +02:00
step_type = self . mash if self . mash != " " else " MashStep "
step_string = { " name " : str ( row [ 0 ] ) ,
" props " : {
2021-06-21 06:51:58 +02:00
" AutoMode " : self . AutoMode ,
2021-06-15 21:03:56 +02:00
" Kettle " : self . id ,
" Sensor " : self . kettle . sensor ,
" Temp " : str ( int ( row [ 1 ] ) ) if self . TEMP_UNIT == " C " else str ( round ( 9.0 / 5.0 * int ( row [ 1 ] ) + 32 ) ) ,
" Timer " : str ( int ( row [ 2 ] ) )
} ,
" status_text " : " " ,
" status " : " I " ,
" type " : step_type
}
await self . create_step ( step_string )
2021-06-13 14:38:18 +02:00
2021-06-15 21:03:56 +02:00
# MashOut -> Notification 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. "
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
} ,
" status_text " : " " ,
" status " : " I " ,
" type " : self . mashout
}
await self . create_step ( step_string )
Hops = self . getBoilAlerts ( hops , miscs , " kbh " )
step_type = self . boil if self . boil != " " else " BoilStep "
step_string = { " name " : " Boil Step " ,
" props " : {
2021-06-21 06:51:58 +02:00
" AutoMode " : self . AutoMode ,
2021-09-21 18:06:23 +02:00
" Kettle " : self . boilid ,
2022-01-03 17:53:22 +01:00
" Sensor " : self . boilkettle . sensor ,
2021-06-15 21:03:56 +02:00
" Temp " : int ( self . BoilTemp ) ,
" Timer " : BoilTime ,
" First_Wort " : FirstWort ,
" LidAlert " : " Yes " ,
" 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
}
2021-06-13 14:38:18 +02:00
2021-06-15 21:03:56 +02:00
await self . create_step ( step_string )
2021-06-13 14:38:18 +02:00
2021-06-15 21:03:56 +02:00
await self . create_Whirlpool_Cooldown ( )
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
self . cbpi . notify ( ' KBH Recipe created ' , name , NotificationType . INFO )
2021-05-26 21:00:35 +02:00
except :
2021-06-15 21:03:56 +02:00
self . cbpi . notify ( ' KBH Recipe creation failure ' , name , NotificationType . ERROR )
2021-05-26 21:00:35 +02:00
pass
2021-06-15 21:03:56 +02:00
else :
self . cbpi . notify ( ' Recipe Upload ' , ' No default Kettle defined. Please specify default Kettle in settings ' , NotificationType . ERROR )
2021-06-10 20:57:22 +02:00
2021-05-26 21:00:35 +02:00
async def xml_recipe_creation ( self , Recipe_ID ) :
2021-06-15 21:03:56 +02:00
config = self . get_config_values ( )
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
if self . kettle is not None :
2021-05-26 21:00:35 +02:00
# load beerxml file located in upload folder
self . path = os . path . join ( " . " , ' config ' , " upload " , " beer.xml " )
if os . path . exists ( self . path ) is False :
self . cbpi . notify ( " File Not Found " , " Please upload a Beer.xml File " , NotificationType . ERROR )
2021-06-15 21:03:56 +02:00
e = xml . etree . ElementTree . parse ( self . path ) . getroot ( )
recipe = e . find ( ' ./RECIPE[ %s ] ' % ( str ( Recipe_ID ) ) )
hops = recipe . findall ( ' ./HOPS/HOP ' )
miscs = recipe . findall ( ' MISCS/MISC[USE= " Boil " ] ' )
name = e . find ( ' ./RECIPE[ %s ]/NAME ' % ( str ( Recipe_ID ) ) ) . text
boil_time = float ( e . find ( ' ./RECIPE[ %s ]/BOIL_TIME ' % ( str ( Recipe_ID ) ) ) . text )
FirstWort = self . getFirstWort ( hops , " xml " )
await self . create_recipe ( name )
2021-05-26 21:00:35 +02:00
# 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
for row in self . getSteps ( Recipe_ID ) :
step_name = str ( row . get ( " name " ) )
step_timer = str ( int ( row . get ( " timer " ) ) )
step_temp = str ( int ( row . get ( " temp " ) ) )
sensor = self . kettle . sensor
2021-07-15 07:16:26 +02:00
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 " : step_temp ,
" Timer " : 0 ,
" Notification " : Notification
} ,
" status_text " : " " ,
" status " : " I " ,
" type " : step_type
}
await self . create_step ( step_string )
step_type = self . mash if self . mash != " " else " MashStep "
Notification = " "
else :
step_type = self . mash if self . mash != " " else " MashStep "
Notification = " "
2021-05-26 21:00:35 +02:00
else :
step_type = self . mash if self . mash != " " else " MashStep "
Notification = " "
2021-06-15 21:03:56 +02:00
step_string = { " name " : step_name ,
" props " : {
2021-06-21 06:51:58 +02:00
" AutoMode " : self . AutoMode ,
2021-06-15 21:03:56 +02:00
" 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 )
2021-05-26 21:00:35 +02:00
# MashOut -> Simple step that sends notification and waits for user input to move to next step (AutoNext=No)
if self . mashout == " NotificationStep " :
2021-06-15 21:03:56 +02:00
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 )
2021-05-26 21:00:35 +02:00
# Boil step including hop alarms and alarm for first wort hops -> Automode is set tu yes
2021-06-15 21:03:56 +02:00
Hops = self . getBoilAlerts ( hops , miscs , " xml " )
2021-09-21 18:06:23 +02:00
step_kettle = self . boilid
2021-05-26 21:00:35 +02:00
step_type = self . boil if self . boil != " " else " BoilStep "
2021-06-15 21:03:56 +02:00
step_time = str ( int ( boil_time ) )
2021-05-26 21:00:35 +02:00
step_temp = self . BoilTemp
2022-01-03 17:53:22 +01:00
sensor = self . boilkettle . sensor
2021-05-26 21:00:35 +02:00
LidAlert = " Yes "
2021-06-15 21:03:56 +02:00
step_string = { " name " : " Boil Step " ,
" props " : {
2021-06-21 06:51:58 +02:00
" AutoMode " : self . AutoMode ,
2021-06-15 21:03:56 +02:00
" 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
}
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
await self . create_step ( step_string )
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
await self . create_Whirlpool_Cooldown ( )
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
self . cbpi . notify ( ' BeerXML Recipe created ' , name , NotificationType . INFO )
else :
self . cbpi . notify ( ' Recipe Upload ' , ' No default Kettle defined. Please specify default Kettle in settings ' , NotificationType . ERROR )
2021-05-26 21:00:35 +02:00
2021-06-15 21:03:56 +02:00
# XML functions to retrieve xml repice parameters (if multiple recipes are stored in one xml file, id could be used)
2021-05-26 21:00:35 +02:00
def getSteps ( self , id ) :
e = xml . etree . ElementTree . parse ( self . path ) . getroot ( )
steps = [ ]
for e in e . findall ( ' ./RECIPE[ %s ]/MASH/MASH_STEPS/MASH_STEP ' % ( str ( id ) ) ) :
if self . cbpi . config . get ( " TEMP_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 )
steps . append ( { " name " : e . find ( " NAME " ) . text , " temp " : temp , " timer " : float ( e . find ( " STEP_TIME " ) . text ) } )
return steps
2021-06-13 14:38:18 +02:00
async def bf_recipe_creation ( self , Recipe_ID ) :
2021-06-15 21:03:56 +02:00
config = self . get_config_values ( )
2021-06-13 14:38:18 +02:00
2021-06-15 21:03:56 +02:00
if self . kettle is not None :
2021-06-13 14:38:18 +02:00
brewfather = True
result = [ ]
self . bf_url = " https://api.brewfather.app/v1/recipes/ " + Recipe_ID
brewfather_user_id = self . cbpi . config . get ( " brewfather_user_id " , None )
if brewfather_user_id == " " or brewfather_user_id is None :
brewfather = False
brewfather_api_key = self . cbpi . config . get ( " brewfather_api_key " , None )
if brewfather_api_key == " " or brewfather_api_key is None :
brewfather = False
if brewfather == True :
encodedData = base64 . b64encode ( bytes ( f " { brewfather_user_id } : { brewfather_api_key } " , " ISO-8859-1 " ) ) . decode ( " ascii " )
headers = { " Authorization " : " Basic %s " % encodedData }
bf_recipe = " "
async with aiohttp . ClientSession ( headers = headers ) as bf_session :
async with bf_session . get ( self . bf_url ) as r :
bf_recipe = await r . json ( )
await bf_session . close ( )
2021-11-03 09:30:16 +01:00
2021-06-13 14:38:18 +02:00
if bf_recipe != " " :
2021-11-03 09:30:16 +01:00
try :
StrikeTemp = bf_recipe [ ' data ' ] [ ' strikeTemp ' ]
except :
2021-11-03 12:53:34 +01:00
StrikeTemp = None
# BF is sending all Temeprature values in °C. If system is running in F, values need to be converted
if StrikeTemp is not None and self . TEMP_UNIT != " C " :
StrikeTemp = round ( ( 9.0 / 5.0 * float ( StrikeTemp ) + 32 ) )
2021-06-13 14:38:18 +02:00
RecipeName = bf_recipe [ ' name ' ]
BoilTime = bf_recipe [ ' boilTime ' ]
mash_steps = bf_recipe [ ' mash ' ] [ ' steps ' ]
hops = bf_recipe [ ' hops ' ]
try :
miscs = bf_recipe [ ' miscs ' ]
except :
miscs = None
2021-06-15 21:03:56 +02:00
FirstWort = self . getFirstWort ( hops , " bf " )
2021-06-13 14:38:18 +02:00
2021-06-15 21:03:56 +02:00
await self . create_recipe ( RecipeName )
2021-06-13 14:38:18 +02:00
# 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
for step in mash_steps :
2021-07-13 07:07:55 +02:00
try :
step_name = step [ ' name ' ]
if step_name == " " :
2021-07-15 07:16:26 +02:00
step_name = " MashStep "
2021-07-13 07:07:55 +02:00
except :
step_name = " MashStep "
2021-07-15 07:16:26 +02:00
2021-06-13 14:38:18 +02:00
step_timer = str ( int ( step [ ' stepTime ' ] ) )
if self . TEMP_UNIT == " C " :
step_temp = str ( int ( step [ ' stepTemp ' ] ) )
else :
step_temp = str ( round ( ( 9.0 / 5.0 * int ( step [ ' stepTemp ' ] ) + 32 ) ) )
sensor = self . kettle . sensor
2021-07-15 07:16:26 +02:00
if MashIn_Flag == True :
if int ( step_timer ) == 0 :
step_type = self . mashin if self . mashin != " " else " MashInStep "
Notification = " Target temperature reached. Please add malt. "
MashIn_Flag = False
elif self . addmashin == " Yes " :
2021-11-03 09:30:16 +01:00
mashin_temp = str ( round ( StrikeTemp ) ) if StrikeTemp is not None else step_temp
2021-07-15 07:16:26 +02:00
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 ,
2021-11-03 09:30:16 +01:00
" Temp " : mashin_temp ,
2021-07-15 07:16:26 +02:00
" Timer " : 0 ,
" Notification " : Notification
} ,
" status_text " : " " ,
" status " : " I " ,
" type " : step_type
}
await self . create_step ( step_string )
step_type = self . mash if self . mash != " " else " MashStep "
Notification = " "
else :
step_type = self . mash if self . mash != " " else " MashStep "
Notification = " "
2021-06-13 14:38:18 +02:00
else :
step_type = self . mash if self . mash != " " else " MashStep "
Notification = " "
2021-06-15 21:03:56 +02:00
step_string = { " name " : step_name ,
" props " : {
2021-06-21 06:51:58 +02:00
" AutoMode " : self . AutoMode ,
2021-06-15 21:03:56 +02:00
" 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 )
2021-06-13 14:38:18 +02:00
# MashOut -> Simple step that sends notification and waits for user input to move to next step (AutoNext=No)
2021-06-15 21:03:56 +02:00
2021-06-13 14:38:18 +02:00
if self . mashout == " NotificationStep " :
2021-06-15 21:03:56 +02:00
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 )
2021-06-13 14:38:18 +02:00
# Boil step including hop alarms and alarm for first wort hops -> Automode is set tu yes
2021-06-15 21:03:56 +02:00
Hops = self . getBoilAlerts ( hops , miscs , " bf " )
2021-06-13 14:38:18 +02:00
2021-09-21 18:06:23 +02:00
step_kettle = self . boilid
2021-06-13 14:38:18 +02:00
step_time = str ( int ( BoilTime ) )
step_type = self . boil if self . boil != " " else " BoilStep "
step_temp = self . BoilTemp
2022-01-03 17:53:22 +01:00
sensor = self . boilkettle . sensor
2021-06-13 14:38:18 +02:00
LidAlert = " Yes "
2021-06-15 21:03:56 +02:00
step_string = { " name " : " Boil Step " ,
" props " : {
2021-06-21 06:51:58 +02:00
" AutoMode " : self . AutoMode ,
2021-06-15 21:03:56 +02:00
" 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 )
await self . create_Whirlpool_Cooldown ( )
2021-06-13 14:38:18 +02:00
2021-06-15 21:03:56 +02:00
self . cbpi . notify ( ' Brewfather App Recipe created: ' , RecipeName , NotificationType . INFO )
else :
self . cbpi . notify ( ' Recipe Upload ' , ' No default Kettle defined. Please specify default Kettle in settings ' , NotificationType . ERROR )
2021-06-13 14:38:18 +02:00
2021-06-15 21:03:56 +02:00
def getBoilAlerts ( self , hops , miscs , recipe_type ) :
2021-06-13 14:38:18 +02:00
alerts = [ ]
for hop in hops :
2021-06-15 21:03:56 +02:00
if recipe_type == " xml " :
use = hop . find ( ' USE ' ) . text
## Hops which are not used in the boil step should not cause alerts
2021-06-13 14:38:18 +02:00
if use != ' Aroma ' and use != ' Boil ' :
continue
2021-06-15 21:03:56 +02:00
alerts . append ( float ( hop . find ( ' TIME ' ) . text ) )
elif recipe_type == " bf " :
use = hop [ ' use ' ]
if use != ' Aroma ' and use != ' Boil ' :
continue
alerts . append ( float ( hop [ ' time ' ] ) )
elif recipe_type == " kbh " :
alerts . append ( float ( hop [ 0 ] ) )
## There might also be miscelaneous additions during boild time
if miscs is not None :
for misc in miscs :
if recipe_type == " xml " :
alerts . append ( float ( misc . find ( ' TIME ' ) . text ) )
elif recipe_type == " bf " :
use = misc [ ' use ' ]
if use != ' Aroma ' and use != ' Boil ' :
continue
alerts . append ( float ( misc [ ' time ' ] ) )
elif recipe_type == " kbh " :
alerts . append ( float ( misc [ 0 ] ) )
2021-06-13 14:38:18 +02:00
## 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 ( )
2021-06-15 21:03:56 +02:00
hop_alerts = [ ]
for i in range ( 0 , 6 ) :
try :
hop_alerts . append ( str ( int ( alerts [ i ] ) ) )
except :
hop_alerts . append ( None )
return hop_alerts
def getFirstWort ( self , hops , recipe_type ) :
alert = " No "
if recipe_type == " kbh " :
if len ( hops ) != 0 :
alert = " Yes "
elif recipe_type == " xml " :
for hop in hops :
use = hop . find ( ' USE ' ) . text
## Hops which are not used in the boil step should not cause alerts
if use != ' First Wort ' :
continue
alert = " Yes "
elif recipe_type == " bf " :
for hop in hops :
if hop [ ' use ' ] == " First Wort " :
alert = " Yes "
return alert
2021-06-13 14:38:18 +02:00
2021-06-15 21:03:56 +02:00
async def create_Whirlpool_Cooldown ( self ) :
# Add Waitstep as Whirlpool
if self . cooldown != " WaiStep " and self . cooldown != " " :
step_string = { " name " : " Whirlpool " ,
2021-05-26 21:00:35 +02:00
" props " : {
2021-09-21 18:06:23 +02:00
" Kettle " : self . boilid ,
2021-06-15 21:03:56 +02:00
" Timer " : " 15 "
2021-05-26 21:00:35 +02:00
} ,
" status_text " : " " ,
" status " : " I " ,
2021-06-15 21:03:56 +02:00
" type " : " WaitStep "
2021-05-26 21:00:35 +02:00
}
2021-06-15 21:03:56 +02:00
await self . create_step ( step_string )
# CoolDown step is sending a notification when cooldowntemp is reached
step_type = self . cooldown if self . cooldown != " " else " WaitStep "
step_name = " CoolDown "
cooldown_sensor = " "
step_temp = " "
step_timer = " 15 "
if step_type == " CooldownStep " :
cooldown_sensor = self . cbpi . config . get ( " steps_cooldown_sensor " , None )
if cooldown_sensor is None or cooldown_sensor == ' ' :
2022-01-03 17:53:22 +01:00
cooldown_sensor = self . boilkettle . sensor # fall back to boilkettle sensor if no other sensor is specified
2021-06-15 21:03:56 +02:00
step_timer = " "
step_temp = int ( self . CoolDownTemp )
step_string = { " name " : " Cooldown " ,
" props " : {
2021-09-21 18:06:23 +02:00
" Kettle " : self . boilid ,
2021-06-15 21:03:56 +02:00
" Timer " : step_timer ,
" Temp " : step_temp ,
" Sensor " : cooldown_sensor
} ,
" status_text " : " " ,
" status " : " I " ,
" type " : step_type
}
await self . create_step ( step_string )
def get_config_values ( self ) :
self . kettle = None
2021-09-21 18:06:23 +02:00
self . boilkettle = None
2021-06-15 21:03:56 +02:00
#Define MashSteps
self . TEMP_UNIT = self . cbpi . config . get ( " TEMP_UNIT " , " C " )
2021-06-21 06:51:58 +02:00
self . AutoMode = self . cbpi . config . get ( " AutoMode " , " Yes " )
2021-06-15 21:03:56 +02:00
self . mashin = self . cbpi . config . get ( " steps_mashin " , " MashInStep " )
self . mash = self . cbpi . config . get ( " steps_mash " , " MashStep " )
self . mashout = self . cbpi . config . get ( " steps_mashout " , None ) # Currently used only for the Braumeister
self . boil = self . cbpi . config . get ( " steps_boil " , " BoilStep " )
self . whirlpool = " Waitstep "
self . cooldown = self . cbpi . config . get ( " steps_cooldown " , " WaitStep " )
#get default boil temp from settings
self . BoilTemp = self . cbpi . config . get ( " steps_boil_temp " , 98 )
#get default cooldown temp alarm setting
self . CoolDownTemp = self . cbpi . config . get ( " steps_cooldown_temp " , 25 )
# get default Kettle from Settings
self . id = self . cbpi . config . get ( ' MASH_TUN ' , None )
2021-09-21 18:06:23 +02:00
self . boilid = self . cbpi . config . get ( ' BoilKettle ' , None )
if self . boilid is None :
self . boilid = self . id
2021-07-15 07:16:26 +02:00
# If next parameter is Yes, MashIn Ste will be added before first mash step if not included in recipe
self . addmashin = self . cbpi . config . get ( ' AddMashInStep ' , " Yes " )
2021-06-15 21:03:56 +02:00
try :
self . kettle = self . cbpi . kettle . find_by_id ( self . id )
except :
self . cbpi . notify ( ' Recipe Upload ' , ' No default Kettle defined. Please specify default Kettle in settings ' , NotificationType . ERROR )
2021-09-21 18:06:23 +02:00
try :
self . boilkettle = self . cbpi . kettle . find_by_id ( self . boilid )
except :
2022-01-03 17:53:22 +01:00
self . boilkettle = self . kettle
2021-09-21 18:06:23 +02:00
2021-06-15 21:03:56 +02:00
config_values = { " kettle " : self . kettle ,
" kettle_id " : str ( self . id ) ,
2021-09-21 18:06:23 +02:00
" boilkettle " : self . boilkettle ,
" boilkettle_id " : str ( self . boilid ) ,
2021-06-15 21:03:56 +02:00
" mashin " : str ( self . mashin ) ,
" mash " : str ( self . mash ) ,
" mashout " : str ( self . mashout ) ,
" boil " : str ( self . boil ) ,
" whirlpool " : str ( self . whirlpool ) ,
" cooldown " : str ( self . cooldown ) ,
" boiltemp " : str ( self . BoilTemp ) ,
" cooldowntemp " : str ( self . CoolDownTemp ) ,
2021-06-21 06:51:58 +02:00
" temp_unit " : str ( self . TEMP_UNIT ) ,
" AutoMode " : str ( self . AutoMode )
2021-06-15 21:03:56 +02:00
}
2021-09-21 18:06:23 +02:00
logging . info ( config_values )
2021-06-15 21:03:56 +02:00
return config_values
async def create_recipe ( self , name ) :
# Create recipe in recipe Book with name of first recipe in xml file
self . recipeID = await self . cbpi . recipe . create ( name )
# send recipe to mash profile
await self . cbpi . recipe . brew ( self . recipeID )
# remove empty recipe from recipe book
await self . cbpi . recipe . remove ( self . recipeID )
# function to create json to be send to api to add a step to the current mash profile. Currently all properties are send to each step which does not cuase an issue
async def create_step ( self , step_string ) :
#get server port from settings and define url for api calls -> adding steps
self . port = str ( self . cbpi . static_config . get ( ' port ' , 8000 ) )
self . url = " http://127.0.0.1: " + self . port + " /step2/ "
2021-05-26 21:00:35 +02:00
# convert step:string to json required for api call.
step = json . dumps ( step_string )
headers = { ' Content-Type ' : ' application/json ' , ' Accept ' : ' application/json ' }
async with aiohttp . ClientSession ( headers = headers ) as session :
async with session . post ( self . url , data = step ) as response :
return await response . text ( )
2021-06-13 14:38:18 +02:00
await self . push_update ( )