Added fermenter recipe book

This commit is contained in:
avollkopf 2022-02-24 13:46:56 +01:00
parent 2d481bff81
commit b2d29678b5
7 changed files with 347 additions and 34 deletions

View file

@ -1 +1 @@
__version__ = "4.0.1.18.a9" __version__ = "4.0.2.0.a1"

View file

@ -129,6 +129,7 @@ class Fermenter:
heater: Actor = None heater: Actor = None
cooler: Actor = None cooler: Actor = None
brewname: str = None brewname: str = None
description : str = None
props: Props = Props() props: Props = Props()
target_temp: int = 0 target_temp: int = 0
type: str = None type: str = None
@ -150,7 +151,7 @@ class Fermenter:
state = False state = False
steps = list(map(lambda item: item.to_dict(), self.steps)) steps = list(map(lambda item: item.to_dict(), self.steps))
return dict(id=self.id, name=self.name, state=state, sensor=self.sensor, heater=self.heater, cooler=self.cooler, brewname=self.brewname, props=self.props.to_dict() if self.props is not None else None, target_temp=self.target_temp, type=self.type, steps=steps) return dict(id=self.id, name=self.name, state=state, sensor=self.sensor, heater=self.heater, cooler=self.cooler, brewname=self.brewname, description=self.description, props=self.props.to_dict() if self.props is not None else None, target_temp=self.target_temp, type=self.type, steps=steps)
@dataclass @dataclass

View file

@ -3,8 +3,10 @@ import asyncio
import cbpi import cbpi
import copy import copy
import json import json
import yaml
import logging import logging
import os.path import os.path
import pathlib
from os import listdir from os import listdir
from os.path import isfile, join from os.path import isfile, join
import shortuuid import shortuuid
@ -114,10 +116,22 @@ class FermentationController:
} }
destfile = os.path.join(".", 'config', "fermenter_data.json") destfile = os.path.join(".", 'config', "fermenter_data.json")
json.dump(data,open(destfile,'w'),indent=4, sort_keys=True) json.dump(data,open(destfile,'w'),indent=4, sort_keys=True)
pathlib.Path(os.path.join(".", 'config/fermenterrecipes')).mkdir(parents=True, exist_ok=True)
async def shutdown(self, app=None): async def shutdown(self, app=None, fermenterid=None):
self.save() self.save()
for fermenter in self.data: if (fermenterid == None):
for fermenter in self.data:
self.logger.info("Shutdown {}".format(fermenter.name))
for step in fermenter.steps:
try:
self.logger.info("Stop {}".format(step.name))
await step.instance.stop()
except Exception as e:
self.logger.error(e)
else:
fermenter = self._find_by_id(fermenterid)
self.logger.info("Shutdown {}".format(fermenter.name)) self.logger.info("Shutdown {}".format(fermenter.name))
for step in fermenter.steps: for step in fermenter.steps:
try: try:
@ -126,6 +140,7 @@ class FermentationController:
except Exception as e: except Exception as e:
self.logger.error(e) self.logger.error(e)
async def load(self): async def load(self):
# if os.path.exists(self.path) is False: # if os.path.exists(self.path) is False:
# with open(self.path, "w") as file: # with open(self.path, "w") as file:
@ -170,8 +185,9 @@ class FermentationController:
logictype = data.get("type") logictype = data.get("type")
temp = data.get("target_temp") temp = data.get("target_temp")
brewname = data.get("brewname") brewname = data.get("brewname")
description = data.get("description")
props = Props(data.get("props", {})) props = Props(data.get("props", {}))
fermenter = Fermenter(id, name, sensor, heater, cooler, brewname, props, temp, logictype) fermenter = Fermenter(id, name, sensor, heater, cooler, brewname, description, props, temp, logictype)
fermenter.steps = list(map(lambda item: self._create_step(fermenter, item), data.get("steps", []))) fermenter.steps = list(map(lambda item: self._create_step(fermenter, item), data.get("steps", [])))
self.push_update() self.push_update()
return fermenter return fermenter
@ -246,8 +262,6 @@ class FermentationController:
async def update(self, item: Fermenter ): async def update(self, item: Fermenter ):
logging.info(item)
def _update(old_item: Fermenter, item: Fermenter): def _update(old_item: Fermenter, item: Fermenter):
old_item.name = item.name old_item.name = item.name
old_item.sensor = item.sensor old_item.sensor = item.sensor
@ -255,6 +269,7 @@ class FermentationController:
old_item.cooler = item.cooler old_item.cooler = item.cooler
old_item.type = item.type old_item.type = item.type
old_item.brewname = item.brewname old_item.brewname = item.brewname
old_item.description = item.description
old_item.props = item.props old_item.props = item.props
old_item.target_temp = item.target_temp old_item.target_temp = item.target_temp
return old_item return old_item
@ -544,4 +559,44 @@ class FermentationController:
logging.info(item) logging.info(item)
await item.instance.__getattribute__(action)(**parameter) await item.instance.__getattribute__(action)(**parameter)
except Exception as e: except Exception as e:
logging.error("FermenterStep Controller - Failed to call action on {} {} {}".format(id, action, e)) logging.error("FermenterStep Controller - Failed to call action on {} {} {}".format(id, action, e))
# todo: Sensors may need to be removed when saving the recipe -> need to be replaced when assinging later to Fermenter with 'fermenter.sensor'
async def savetobook(self, fermenterid):
name = shortuuid.uuid()
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
fermenter=self._find_by_id(fermenterid)
try:
brewname = fermenter.brewname
description = fermenter.description
# todo add escription at later point of time, once description has been added to fermenter dataclass
except:
brewname = ""
description = ""
self.basic_data={"name": brewname, "description": description}
try:
fermentersteps = fermenter.steps
except:
fermentersteps = []
data = dict(basic=self.basic_data, steps=list(map(lambda item: item.to_dict(), fermentersteps)))
with open(path, "w") as file:
yaml.dump(data, file)
async def load_recipe(self, data, fermenterid):
try:
await self.shutdown(None, fermenterid)
except:
pass
fermenter = self._find_by_id(fermenterid)
def add_runtime_data(item):
item["status"] = "I"
item["id"] = shortuuid.uuid()
item["props"]["Sensor"] = fermenter.sensor
list(map(lambda item: add_runtime_data(item), data.get("steps")))
fermenter.description = data['basic']['desc']
fermenter.brewname = data['basic']['name']
fermenter.steps=[]
await self.update(fermenter)
for item in data.get("steps"):
await self.create_step(fermenterid, item)

View file

@ -0,0 +1,88 @@
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
class FermenterRecipeController:
def __init__(self, cbpi):
self.cbpi = cbpi
self.logger = logging.getLogger(__name__)
def urlify(self, s):
# Remove all non-word characters (everything except numbers and letters)
s = re.sub(r"[^\w\s]", '', s)
# Replace all runs of whitespace with a single dash
s = re.sub(r"\s+", '-', s)
return s
async def create(self, name):
id = shortuuid.uuid()
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(id))
data = dict(basic=dict(name=name), steps=[])
with open(path, "w") as file:
yaml.dump(data, file)
return id
async def save(self, name, data):
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
logging.info(data)
with open(path, "w") as file:
yaml.dump(data, file, indent=4, sort_keys=True)
async def get_recipes(self):
path = os.path.join(".", 'config', "fermenterrecipes")
onlyfiles = [os.path.splitext(f)[0] for f in listdir(path) if isfile(join(path, f)) and f.endswith(".yaml")]
result = []
for filename in onlyfiles:
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % filename)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
dataset = data["basic"]
dataset["file"] = filename
result.append(dataset)
logging.info(result)
return result
async def get_by_name(self, name):
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % name)
with open(recipe_path) as file:
return yaml.load(file, Loader=yaml.FullLoader)
async def remove(self, name):
path = os.path.join(".", 'config', "fermenterrecipes", "{}.yaml".format(name))
os.remove(path)
async def brew(self, name, fermenterid):
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % name)
logging.info(recipe_path)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
await self.cbpi.fermenter.load_recipe(data, fermenterid)
async def clone(self, id, new_name):
recipe_path = os.path.join(".", 'config', "fermenterrecipes", "%s.yaml" % id)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
data["basic"]["name"] = new_name
new_id = shortuuid.uuid()
await self.save(new_id, data)
return new_id

View file

@ -25,6 +25,7 @@ from cbpi.controller.plugin_controller import PluginController
from cbpi.controller.sensor_controller import SensorController from cbpi.controller.sensor_controller import SensorController
from cbpi.controller.step_controller import StepController from cbpi.controller.step_controller import StepController
from cbpi.controller.recipe_controller import RecipeController from cbpi.controller.recipe_controller import RecipeController
from cbpi.controller.fermenter_recipe_controller import FermenterRecipeController
from cbpi.controller.upload_controller import UploadController from cbpi.controller.upload_controller import UploadController
from cbpi.controller.fermentation_controller import FermentationController from cbpi.controller.fermentation_controller import FermentationController
@ -45,6 +46,7 @@ from cbpi.http_endpoints.http_kettle import KettleHttpEndpoints
from cbpi.http_endpoints.http_sensor import SensorHttpEndpoints from cbpi.http_endpoints.http_sensor import SensorHttpEndpoints
from cbpi.http_endpoints.http_step import StepHttpEndpoints from cbpi.http_endpoints.http_step import StepHttpEndpoints
from cbpi.http_endpoints.http_recipe import RecipeHttpEndpoints from cbpi.http_endpoints.http_recipe import RecipeHttpEndpoints
from cbpi.http_endpoints.http_fermenterrecipe import FermenterRecipeHttpEndpoints
from cbpi.http_endpoints.http_plugin import PluginHttpEndpoints from cbpi.http_endpoints.http_plugin import PluginHttpEndpoints
from cbpi.http_endpoints.http_system import SystemHttpEndpoints from cbpi.http_endpoints.http_system import SystemHttpEndpoints
from cbpi.http_endpoints.http_log import LogHttpEndpoints from cbpi.http_endpoints.http_log import LogHttpEndpoints
@ -112,6 +114,7 @@ class CraftBeerPi:
self.fermenter : FermentationController = FermentationController(self) self.fermenter : FermentationController = FermentationController(self)
self.step : StepController = StepController(self) self.step : StepController = StepController(self)
self.recipe : RecipeController = RecipeController(self) self.recipe : RecipeController = RecipeController(self)
self.fermenterrecipe : FermenterRecipeController = FermenterRecipeController(self)
self.upload : UploadController = UploadController(self) self.upload : UploadController = UploadController(self)
self.notification : NotificationController = NotificationController(self) self.notification : NotificationController = NotificationController(self)
self.satellite = None self.satellite = None
@ -121,6 +124,7 @@ class CraftBeerPi:
self.http_step = StepHttpEndpoints(self) self.http_step = StepHttpEndpoints(self)
self.http_recipe = RecipeHttpEndpoints(self) self.http_recipe = RecipeHttpEndpoints(self)
self.http_fermenterrecipe = FermenterRecipeHttpEndpoints(self)
self.http_sensor = SensorHttpEndpoints(self) self.http_sensor = SensorHttpEndpoints(self)
self.http_config = ConfigHttpEndpoints(self) self.http_config = ConfigHttpEndpoints(self)
self.http_actor = ActorHttpEndpoints(self) self.http_actor = ActorHttpEndpoints(self)

View file

@ -77,7 +77,7 @@ class FermentationHttpEndpoints():
description: successful operation description: successful operation
""" """
data = await request.json() data = await request.json()
fermenter = Fermenter(id=id, name=data.get("name"), sensor=data.get("sensor"), heater=data.get("heater"), cooler=data.get("cooler"), brewname=data.get("brewname"), target_temp=data.get("target_temp"), props=Props(data.get("props", {})), type=data.get("type")) fermenter = Fermenter(id=id, name=data.get("name"), sensor=data.get("sensor"), heater=data.get("heater"), cooler=data.get("cooler"), brewname=data.get("brewname"), description=data.get("description"), target_temp=data.get("target_temp"), props=Props(data.get("props", {})), type=data.get("type"))
response_data = await self.controller.create(fermenter) response_data = await self.controller.create(fermenter)
return web.json_response(data=response_data.to_dict()) return web.json_response(data=response_data.to_dict())
@ -115,7 +115,7 @@ class FermentationHttpEndpoints():
""" """
id = request.match_info['id'] id = request.match_info['id']
data = await request.json() data = await request.json()
fermenter = Fermenter(id=id, name=data.get("name"), sensor=data.get("sensor"), heater=data.get("heater"), cooler=data.get("cooler"), brewname=data.get("brewname"), target_temp=data.get("target_temp"), props=Props(data.get("props", {})), type=data.get("type")) fermenter = Fermenter(id=id, name=data.get("name"), sensor=data.get("sensor"), heater=data.get("heater"), cooler=data.get("cooler"), brewname=data.get("brewname"), description=data.get("description"), target_temp=data.get("target_temp"), props=Props(data.get("props", {})), type=data.get("type"))
return web.json_response(data=(await self.controller.update(fermenter)).to_dict()) return web.json_response(data=(await self.controller.update(fermenter)).to_dict())
@request_mapping(path="/{id}", method="DELETE", auth_required=False) @request_mapping(path="/{id}", method="DELETE", auth_required=False)
@ -513,30 +513,6 @@ class FermentationHttpEndpoints():
await self.controller.stop(fermenterid) await self.controller.stop(fermenterid)
return web.Response(status=200) return web.Response(status=200)
@request_mapping(path="/{id}/nextstep", method="POST", auth_required=False)
async def http_next_step(self, request):
"""
---
description: Stop steps for Fermenter with fermenterid
tags:
- Fermenter
parameters:
- name: "id"
in: "path"
description: "Fermenter ID"
required: true
type: "integer"
format: "int64"
responses:
"200":
description: successful operation
"""
fermenterid= request.match_info['id']
await self.controller.next(fermenterid)
return web.Response(status=200)
@request_mapping(path="/{id}/nextstep", method="POST", auth_required=False) @request_mapping(path="/{id}/nextstep", method="POST", auth_required=False)
async def http_next_step(self, request): async def http_next_step(self, request):
@ -620,4 +596,21 @@ class FermentationHttpEndpoints():
id = request.match_info['id'] id = request.match_info['id']
await self.controller.call_action(id,data.get("action"), data.get("parameter",[])) await self.controller.call_action(id,data.get("action"), data.get("parameter",[]))
return web.Response(status=204)
@request_mapping(path="/savetobook/{id}", method="POST", auth_required=False)
async def http_savetobook(self, request):
"""
---
description: Save Active FermenterRecipe to Fermenter Recipe Book
tags:
- Fermenter
responses:
"204":
description: successful operation
"""
fermenterid = request.match_info['id']
await self.controller.savetobook(fermenterid)
return web.Response(status=204) return web.Response(status=204)

View file

@ -0,0 +1,172 @@
from cbpi.controller.fermenter_recipe_controller import FermenterRecipeController
from cbpi.api.dataclasses import Props, Step
from aiohttp import web
from cbpi.api import *
import logging
class FermenterRecipeHttpEndpoints():
def __init__(self, cbpi):
self.cbpi = cbpi
self.controller : FermenterRecipeController = cbpi.fermenterrecipe
self.cbpi.register(self, "/fermenterrecipe")
@request_mapping(path="/", method="GET", auth_required=False)
async def http_get_all(self, request):
"""
---
description: Get all recipes
tags:
- FermenterRecipe
responses:
"200":
description: successful operation
"""
return web.json_response(await self.controller.get_recipes())
@request_mapping(path="/{name}", method="GET", auth_required=False)
async def get_by_name(self, request):
"""
---
description: Get all recipes
tags:
- FermenterRecipe
parameters:
- name: "name"
in: "path"
description: "Recipe Name"
required: true
type: "string"
responses:
"200":
description: successful operation
"""
name = request.match_info['name']
return web.json_response(await self.controller.get_by_name(name))
@request_mapping(path="/create", method="POST", auth_required=False)
async def http_create(self, request):
"""
---
description: Add Recipe
tags:
- FermenterRecipe
responses:
"200":
description: successful operation
"""
data = await request.json()
print(data)
return web.json_response(dict(id=await self.controller.create(data.get("name"))))
@request_mapping(path="/{name}", method="PUT", auth_required=False)
async def http_save(self, request):
"""
---
description: Save Recipe
tags:
- FermenterRecipe
parameters:
- name: "id"
in: "path"
description: "Recipe Id"
required: true
type: "string"
- in: body
name: body
description: Recipe Data
required: false
schema:
type: object
responses:
"200":
description: successful operation
"""
data = await request.json()
name = request.match_info['name']
await self.controller.save(name, data)
print(data)
return web.Response(status=204)
@request_mapping(path="/{name}", method="DELETE", auth_required=False)
async def http_remove(self, request):
"""
---
description: Delete
tags:
- FermenterRecipe
parameters:
- name: "id"
in: "path"
description: "Recipe Id"
required: true
type: "string"
responses:
"200":
description: successful operation
"""
name = request.match_info['name']
await self.controller.remove(name)
return web.Response(status=204)
@request_mapping(path="/{name}/{fermenterid}/brew", method="POST", auth_required=False)
async def http_brew(self, request):
"""
---
description: Send Recipe to Fermenter
tags:
- FermenterRecipe
parameters:
- name: "name"
in: "path"
description: "Recipe Id"
required: true
type: "string"
responses:
"200":
description: successful operation
"""
name = request.match_info['name']
fermenterid = request.match_info['fermenterid']
await self.controller.brew(name,fermenterid)
return web.Response(status=204)
@request_mapping(path="/{id}/clone", method="POST", auth_required=False)
async def http_clone(self, request):
"""
---
description: Brew
tags:
- FermenterRecipe
parameters:
- name: "id"
in: "path"
description: "Recipe Id"
required: true
type: "string"
- in: body
name: body
description: Recipe Data
required: false
schema:
type: object
responses:
"200":
description: successful operation
"""
id = request.match_info['id']
data = await request.json()
return web.json_response(dict(id=await self.controller.clone(id, data.get("name"))))