mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-12-22 13:34:55 +01:00
Added Fermenters (development)
Added fermenter type Added fermenter logic (incl. new class) -> will require cbpi4ui -> >= 0.1.a1 Still under development, but fermentation w/o steps should be working
This commit is contained in:
parent
bee645ff96
commit
d7c1b64493
17 changed files with 651 additions and 29 deletions
|
@ -1 +1 @@
|
|||
__version__ = "4.0.0.59"
|
||||
__version__ = "4.0.1.a1"
|
||||
|
|
|
@ -9,6 +9,7 @@ __all__ = ["CBPiActor",
|
|||
"parameters",
|
||||
"background_task",
|
||||
"CBPiKettleLogic",
|
||||
"CBPiFermenterLogic",
|
||||
"CBPiException",
|
||||
"KettleException",
|
||||
"SensorException",
|
||||
|
@ -22,5 +23,6 @@ from cbpi.api.extension import *
|
|||
from cbpi.api.property import *
|
||||
from cbpi.api.decorator import *
|
||||
from cbpi.api.kettle_logic import *
|
||||
from cbpi.api.fermenter_logic import *
|
||||
from cbpi.api.step import *
|
||||
from cbpi.api.exceptions import *
|
|
@ -29,6 +29,15 @@ class CBPiBase(metaclass=ABCMeta):
|
|||
async def set_target_temp(self,id, temp):
|
||||
await self.cbpi.kettle.set_target_temp(id, temp)
|
||||
|
||||
def get_fermenter(self,id):
|
||||
return self.cbpi.fermenter._find_by_id(id)
|
||||
|
||||
def get_fermenter_target_temp(self,id):
|
||||
return self.cbpi.fermenter._find_by_id(id).target_temp
|
||||
|
||||
async def set_fermenter_target_temp(self,id, temp):
|
||||
await self.cbpi.fermenter.set_target_temp(id, temp)
|
||||
|
||||
def get_sensor(self,id):
|
||||
return self.cbpi.sensor.find_by_id(id)
|
||||
|
||||
|
|
|
@ -8,5 +8,6 @@ class ConfigType(Enum):
|
|||
ACTOR = "actor"
|
||||
SENSOR = "sensor"
|
||||
STEP = "step"
|
||||
FERMENTER = "fermenter"
|
||||
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ class Actor:
|
|||
def __str__(self):
|
||||
return "name={} props={}, state={}, type={}, power={}".format(self.name, self.props, self.state, self.type, self.power)
|
||||
def to_dict(self):
|
||||
return dict(id=self.id, name=self.name, type=self.type, props=self.props.to_dict(), state2="HELLO WORLD", state=self.instance.get_state(), power=self.power)
|
||||
return dict(id=self.id, name=self.name, type=self.type, props=self.props.to_dict(), state=self.instance.get_state(), power=self.power)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -125,15 +125,30 @@ class Step:
|
|||
class Fermenter:
|
||||
id: str = None
|
||||
name: str = None
|
||||
sensor: Sensor = None
|
||||
heater: Actor = None
|
||||
cooler: Actor = None
|
||||
brewname: str = None
|
||||
props: Props = Props()
|
||||
target_temp: int = 0
|
||||
type: str = None
|
||||
steps: List[Step]= field(default_factory=list)
|
||||
instance: str = None
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "id={} name={} brewname={} props={} temp={} steps={}".format(self.id, self.name, self.brewname, self.props, self.target_temp, self.steps)
|
||||
return "id={} name={} sensor={} heater={} cooler={} brewname={} props={} temp={} type={} steps={}".format(self.id, self.name, self.sensor, self.heater, self.cooler, self.brewname, self.props, self.target_temp, self.type, self.steps)
|
||||
def to_dict(self):
|
||||
|
||||
if self.instance is not None:
|
||||
|
||||
state = self.instance.state
|
||||
|
||||
else:
|
||||
state = False
|
||||
|
||||
steps = list(map(lambda item: item.to_dict(), self.steps))
|
||||
return dict(id=self.id, name=self.name, target_temp=self.target_temp, steps=steps, props=self.props.to_dict() if self.props is not None else None)
|
||||
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)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -162,6 +177,7 @@ class ConfigType(Enum):
|
|||
NUMBER="number"
|
||||
SELECT="select"
|
||||
STEP="step"
|
||||
FERMENTER="fermenter"
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
|
|
51
cbpi/api/fermenter_logic.py
Normal file
51
cbpi/api/fermenter_logic.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
from cbpi.api.base import CBPiBase
|
||||
from cbpi.api.extension import CBPiExtension
|
||||
from abc import ABCMeta
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
|
||||
|
||||
class CBPiFermenterLogic(CBPiBase, metaclass=ABCMeta):
|
||||
|
||||
def __init__(self, cbpi, id, props):
|
||||
self.cbpi = cbpi
|
||||
self.id = id
|
||||
self.props = props
|
||||
self.state = False
|
||||
self.running = False
|
||||
|
||||
def init(self):
|
||||
pass
|
||||
|
||||
async def on_start(self):
|
||||
pass
|
||||
|
||||
async def on_stop(self):
|
||||
pass
|
||||
|
||||
async def run(self):
|
||||
pass
|
||||
|
||||
async def _run(self):
|
||||
|
||||
try:
|
||||
await self.on_start()
|
||||
self.cancel_reason = await self.run()
|
||||
except asyncio.CancelledError as e:
|
||||
pass
|
||||
finally:
|
||||
await self.on_stop()
|
||||
|
||||
def get_state(self):
|
||||
return dict(running=self.state)
|
||||
|
||||
async def start(self):
|
||||
|
||||
self.state = True
|
||||
|
||||
async def stop(self):
|
||||
|
||||
self.task.cancel()
|
||||
await self.task
|
||||
self.state = False
|
|
@ -98,6 +98,23 @@ class Property(object):
|
|||
The user select a kettle which is available in the system. The value of this variable will be the kettle id
|
||||
'''
|
||||
|
||||
def __init__(self, label, description=""):
|
||||
'''
|
||||
|
||||
:param label:
|
||||
:param description:
|
||||
'''
|
||||
|
||||
PropertyType.__init__(self)
|
||||
self.label = label
|
||||
self.configurable = True
|
||||
self.description = description
|
||||
|
||||
class Fermenter(PropertyType):
|
||||
'''
|
||||
The user select a fermenter which is available in the system. The value of this variable will be the fermenter id
|
||||
'''
|
||||
|
||||
def __init__(self, label, description=""):
|
||||
'''
|
||||
|
||||
|
|
|
@ -42,6 +42,11 @@ def create_config_file():
|
|||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "fermenter_data.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "fermenter_data.json")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
|
||||
if os.path.exists(os.path.join(".", 'config', "step_data.json")) is False:
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "step_data.json")
|
||||
destfile = os.path.join(".", 'config')
|
||||
|
|
5
cbpi/config/fermenter_data.json
Normal file
5
cbpi/config/fermenter_data.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"data": [
|
||||
|
||||
]
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import asyncio
|
||||
import cbpi
|
||||
import copy
|
||||
|
@ -9,19 +8,15 @@ from os import listdir
|
|||
from os.path import isfile, join
|
||||
import shortuuid
|
||||
from cbpi.api.dataclasses import Fermenter, FermenterStep, Props, Step
|
||||
from cbpi.controller.basic_controller2 import BasicController
|
||||
from tabulate import tabulate
|
||||
import sys, os
|
||||
from ..api.step import CBPiStep, StepMove, StepResult, StepState
|
||||
|
||||
|
||||
|
||||
logging.basicConfig(format='%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s',
|
||||
datefmt='%Y-%m-%d:%H:%M:%S',
|
||||
level=logging.INFO)
|
||||
|
||||
class FermentStep:
|
||||
|
||||
|
||||
def __init__(self, cbpi, step, on_done) -> None:
|
||||
self.cbpi = cbpi
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
@ -85,9 +80,10 @@ class FermentStep:
|
|||
async def on_stop(self):
|
||||
pass
|
||||
|
||||
class FermenationController:
|
||||
class FermentationController:
|
||||
|
||||
def __init__(self, cbpi):
|
||||
self.update_key = "fermenterupdate"
|
||||
self.cbpi = cbpi
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.path = os.path.join(".", 'config', "fermenter_data.json")
|
||||
|
@ -96,6 +92,27 @@ class FermenationController:
|
|||
self.types = {}
|
||||
self.cbpi.app.on_cleanup.append(self.shutdown)
|
||||
|
||||
async def init(self):
|
||||
logging.info("INIT Fermentation Controller")
|
||||
self.check_fermenter_file()
|
||||
await self.load()
|
||||
pass
|
||||
|
||||
def check_fermenter_file(self):
|
||||
if os.path.exists(os.path.join(".", 'config', "fermenter_data.json")) is False:
|
||||
logging.info("INIT fermenter_data.json file")
|
||||
data = {
|
||||
"data": [
|
||||
]
|
||||
}
|
||||
destfile = os.path.join(".", 'config', "fermenter_data.json")
|
||||
json.dump(data,open(destfile,'w'),indent=4, sort_keys=True)
|
||||
|
||||
def push_update(self):
|
||||
self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))))
|
||||
self.cbpi.push_update("cbpi/{}/update".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
||||
pass
|
||||
|
||||
async def shutdown(self, app=None):
|
||||
self.save()
|
||||
for fermenter in self.data:
|
||||
|
@ -114,7 +131,11 @@ class FermenationController:
|
|||
with open(self.path) as json_file:
|
||||
d = json.load(json_file)
|
||||
self.data = list(map(lambda item: self._create(item), d))
|
||||
|
||||
|
||||
#for item in self.data:
|
||||
# logging.info("{} Starting ".format(item.name))
|
||||
# await self.start(item.id)
|
||||
|
||||
def _create_step(self, fermenter, item):
|
||||
id = item.get("id")
|
||||
name = item.get("name")
|
||||
|
@ -140,20 +161,37 @@ class FermenationController:
|
|||
def _create(self, data):
|
||||
id = data.get("id")
|
||||
name = data.get("name")
|
||||
sensor = data.get("sensor")
|
||||
heater = data.get("heater")
|
||||
cooler = data.get("cooler")
|
||||
logictype = data.get("type")
|
||||
temp = data.get("target_temp")
|
||||
brewname = data.get("brewname")
|
||||
props = Props(data.get("props", {}))
|
||||
fermenter = Fermenter(id, name, brewname, props, 0)
|
||||
fermenter = Fermenter(id, name, sensor, heater, cooler, brewname, props, temp, logictype)
|
||||
fermenter.steps = list(map(lambda item: self._create_step(fermenter, item), data.get("steps", [])))
|
||||
self.push_update()
|
||||
#self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))))
|
||||
#self.cbpi.push_update("cbpi/{}/update".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
||||
|
||||
return fermenter
|
||||
|
||||
def _find_by_id(self, id):
|
||||
return next((item for item in self.data if item.id == id), None)
|
||||
|
||||
async def init(self):
|
||||
pass
|
||||
|
||||
async def get_all(self):
|
||||
return self.data
|
||||
return list(map(lambda x: x.to_dict(), self.data))
|
||||
|
||||
def get_types(self):
|
||||
# logging.info("{} Get Types".format(self.name))
|
||||
result = {}
|
||||
for key, value in self.types.items():
|
||||
result[key] = dict(name=value.get("name"), properties=value.get("properties"), actions=value.get("actions"))
|
||||
return result
|
||||
|
||||
def get_state(self):
|
||||
# logging.info("{} Get State".format(self.name))
|
||||
return {"data": list(map(lambda x: x.to_dict(), self.data)), "types":self.get_types()}
|
||||
|
||||
async def get(self, id: str ):
|
||||
return self._find_by_id(id)
|
||||
|
@ -162,12 +200,21 @@ class FermenationController:
|
|||
data.id = shortuuid.uuid()
|
||||
self.data.append(data)
|
||||
self.save()
|
||||
self.push_update()
|
||||
#self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))))
|
||||
#self.cbpi.push_update("cbpi/{}/update".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
||||
return data
|
||||
|
||||
async def update(self, item: Fermenter ):
|
||||
|
||||
logging.info(item)
|
||||
|
||||
def _update(old_item: Fermenter, item: Fermenter):
|
||||
old_item.name = item.name
|
||||
old_item.sensor = item.sensor
|
||||
old_item.heater = item.heater
|
||||
old_item.cooler = item.cooler
|
||||
old_item.type = item.type
|
||||
old_item.brewname = item.brewname
|
||||
old_item.props = item.props
|
||||
old_item.target_temp = item.target_temp
|
||||
|
@ -175,12 +222,31 @@ class FermenationController:
|
|||
|
||||
self.data = list(map(lambda old: _update(old, item) if old.id == item.id else old, self.data))
|
||||
self.save()
|
||||
self.push_update()
|
||||
#self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))))
|
||||
#self.cbpi.push_update("cbpi/{}/update".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
||||
return item
|
||||
|
||||
async def set_target_temp(self, id: str, target_temp):
|
||||
try:
|
||||
item = self._find_by_id(id)
|
||||
logging.info(item.target_temp)
|
||||
if item:
|
||||
item.target_temp = target_temp
|
||||
self.save()
|
||||
self.push_update()
|
||||
#self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))))
|
||||
#self.cbpi.push_update("cbpi/{}/update".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
||||
except Exception as e:
|
||||
logging.error("Failed to set Target Temp {} {}".format(id, e))
|
||||
|
||||
async def delete(self, id: str ):
|
||||
item = self._find_by_id(id)
|
||||
self.data = list(filter(lambda item: item.id != id, self.data))
|
||||
self.save()
|
||||
self.push_update()
|
||||
#self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))))
|
||||
#self.cbpi.push_update("cbpi/{}/update".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
||||
|
||||
def save(self):
|
||||
with open(self.path, "w") as file:
|
||||
|
@ -191,7 +257,7 @@ class FermenationController:
|
|||
step.id = shortuuid.uuid()
|
||||
item = self._find_by_id(id)
|
||||
|
||||
step.instance = FermentStep( self.cbpi, step.id, step.name, None, self._done)
|
||||
step.instance = FermentStep( self.cbpi, step, self._done)
|
||||
|
||||
item.steps.append(step)
|
||||
self.save()
|
||||
|
@ -223,10 +289,11 @@ class FermenationController:
|
|||
|
||||
if step is None:
|
||||
self.logger.info("No futher step to start")
|
||||
else:
|
||||
await step.instance.start()
|
||||
step.status = StepState.ACTIVE
|
||||
self.save()
|
||||
|
||||
await step.instance.start()
|
||||
step.status = StepState.ACTIVE
|
||||
self.save()
|
||||
except Exception as e:
|
||||
self.logger.error(e)
|
||||
|
||||
|
@ -240,6 +307,46 @@ class FermenationController:
|
|||
except Exception as e:
|
||||
self.logger.error(e)
|
||||
|
||||
async def start_logic(self, id):
|
||||
try:
|
||||
item = self._find_by_id(id)
|
||||
logging.info("{} Start Id {} ".format(item.name, id))
|
||||
if item.instance is not None and item.instance.running is True:
|
||||
logging.warning("{} already running {}".format(item.name, id))
|
||||
return
|
||||
if item.type is None:
|
||||
logging.warning("{} No Type {}".format(item.name, id))
|
||||
return
|
||||
clazz = self.types[item.type]["class"]
|
||||
item.instance = clazz(self.cbpi, item.id, item.props)
|
||||
|
||||
await item.instance.start()
|
||||
item.instance.running = True
|
||||
item.instance.task = self._loop.create_task(item.instance._run())
|
||||
|
||||
logging.info("{} started {}".format(item.name, id))
|
||||
|
||||
# await self.push_udpate()
|
||||
except Exception as e:
|
||||
logging.error("{} Cant start {} - {}".format(item.name, id, e))
|
||||
|
||||
async def toggle(self, id):
|
||||
|
||||
try:
|
||||
item = self._find_by_id(id)
|
||||
#logging.info(item)
|
||||
|
||||
if item.instance is None or item.instance.state == False:
|
||||
await self.start_logic(id)
|
||||
else:
|
||||
await item.instance.stop()
|
||||
self.push_update()
|
||||
#self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))))
|
||||
#self.cbpi.push_update("cbpi/{}/update".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Failed to switch on FermenterLogic {} {}".format(id, e))
|
||||
|
||||
|
||||
async def next(self, id):
|
||||
self.logger.info("Next {} ".format(id))
|
||||
|
@ -283,5 +390,4 @@ class FermenationController:
|
|||
|
||||
except Exception as e:
|
||||
self.logger.error(e)
|
||||
|
||||
|
||||
|
|
@ -31,8 +31,7 @@ class PluginController():
|
|||
try:
|
||||
logger.info("Trying to load plugin %s" % filename)
|
||||
data = load_config(os.path.join(
|
||||
this_directory, "../extension/%s/config.yaml" % filename))
|
||||
|
||||
this_directory, "../extension/%s/config.yaml" % filename))
|
||||
if (data.get("active") is True and data.get("version") == 4):
|
||||
self.modules[filename] = import_module(
|
||||
"cbpi.extension.%s" % (filename))
|
||||
|
@ -75,6 +74,9 @@ class PluginController():
|
|||
if issubclass(clazz, CBPiKettleLogic):
|
||||
self.cbpi.kettle.types[name] = self._parse_step_props(clazz, name)
|
||||
|
||||
if issubclass(clazz, CBPiFermenterLogic):
|
||||
self.cbpi.fermenter.types[name] = self._parse_step_props(clazz, name)
|
||||
|
||||
if issubclass(clazz, CBPiSensor):
|
||||
self.cbpi.sensor.types[name] = self._parse_step_props(clazz, name)
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ from cbpi.controller.sensor_controller import SensorController
|
|||
from cbpi.controller.step_controller import StepController
|
||||
from cbpi.controller.recipe_controller import RecipeController
|
||||
from cbpi.controller.upload_controller import UploadController
|
||||
from cbpi.controller.fermentation_controller import FermentationController
|
||||
|
||||
from cbpi.controller.system_controller import SystemController
|
||||
from cbpi.controller.satellite_controller import SatelliteController
|
||||
|
@ -49,6 +50,7 @@ from cbpi.http_endpoints.http_system import SystemHttpEndpoints
|
|||
from cbpi.http_endpoints.http_log import LogHttpEndpoints
|
||||
from cbpi.http_endpoints.http_notification import NotificationHttpEndpoints
|
||||
from cbpi.http_endpoints.http_upload import UploadHttpEndpoints
|
||||
from cbpi.http_endpoints.http_fermentation import FermentationHttpEndpoints
|
||||
|
||||
import shortuuid
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -115,8 +117,9 @@ class CraftBeerPi:
|
|||
self.satellite = None
|
||||
if str(self.static_config.get("mqtt", False)).lower() == "true":
|
||||
self.satellite: SatelliteController = SatelliteController(self)
|
||||
|
||||
self.dashboard = DashboardController(self)
|
||||
self.fermenter : FermentationController = FermentationController(self)
|
||||
|
||||
self.http_step = StepHttpEndpoints(self)
|
||||
self.http_recipe = RecipeHttpEndpoints(self)
|
||||
self.http_sensor = SensorHttpEndpoints(self)
|
||||
|
@ -129,7 +132,7 @@ class CraftBeerPi:
|
|||
self.http_log = LogHttpEndpoints(self)
|
||||
self.http_notification = NotificationHttpEndpoints(self)
|
||||
self.http_upload = UploadHttpEndpoints(self)
|
||||
|
||||
self.http_fermenter = FermentationHttpEndpoints(self)
|
||||
|
||||
self.login = Login(self)
|
||||
|
||||
|
@ -279,7 +282,7 @@ class CraftBeerPi:
|
|||
await self.kettle.init()
|
||||
await self.call_initializer(self.app)
|
||||
await self.dashboard.init()
|
||||
|
||||
await self.fermenter.init()
|
||||
|
||||
self._swagger_setup()
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import os, threading, time
|
||||
import os, threading, time, shutil
|
||||
from aiohttp import web
|
||||
import logging
|
||||
from unittest.mock import MagicMock, patch
|
||||
import asyncio
|
||||
import random
|
||||
import json
|
||||
from cbpi.api import *
|
||||
from cbpi.api.config import ConfigType
|
||||
from cbpi.api.base import CBPiBase
|
||||
|
|
115
cbpi/extension/FermenterHysteresis/__init__.py
Normal file
115
cbpi/extension/FermenterHysteresis/__init__.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
import asyncio
|
||||
from asyncio import tasks
|
||||
import logging
|
||||
from cbpi.api import *
|
||||
import aiohttp
|
||||
from aiohttp import web
|
||||
from cbpi.controller.fermentation_controller import FermentationController
|
||||
from cbpi.api.dataclasses import Fermenter, Props, Step
|
||||
from cbpi.api.base import CBPiBase
|
||||
from cbpi.api.config import ConfigType
|
||||
import json
|
||||
import webbrowser
|
||||
|
||||
class FermenterAutostart(CBPiExtension):
|
||||
|
||||
def __init__(self,cbpi):
|
||||
self.cbpi = cbpi
|
||||
self._task = asyncio.create_task(self.run())
|
||||
self.controller : FermentationController = cbpi.fermenter
|
||||
|
||||
|
||||
async def run(self):
|
||||
logging.info("Starting Fermenter Autorun")
|
||||
#get all kettles
|
||||
self.fermenter = self.controller.get_state()
|
||||
for id in self.fermenter['data']:
|
||||
try:
|
||||
self.autostart=(id['props']['AutoStart'])
|
||||
if self.autostart == "Yes":
|
||||
fermenter_id=(id['id'])
|
||||
logging.info("Enabling Autostart for Fermenter {}".format(fermenter_id))
|
||||
self.fermenter=self.cbpi.fermenter._find_by_id(fermenter_id)
|
||||
try:
|
||||
if (self.fermenter.instance is None or self.fermenter.instance.state == False):
|
||||
await self.cbpi.fermenter.toggle(self.fermenter.id)
|
||||
logging.info("Successfully switched on Ferenterlogic for Fermenter {}".format(self.fermenter.id))
|
||||
except Exception as e:
|
||||
logging.error("Failed to switch on FermenterLogic {} {}".format(self.fermenter.id, e))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@parameters([Property.Number(label="HeaterOffsetOn", configurable=True, description="Offset as decimal number when the heater is switched on. Should be greater then 'HeaterOffsetOff'. For example a value of 2 switches on the heater if the current temperature is 2 degrees below the target temperature"),
|
||||
Property.Number(label="HeaterOffsetOff", configurable=True, description="Offset as decimal number when the heater is switched off. Should be smaller then 'HeaterOffsetOn'. For example a value of 1 switches off the heater if the current temperature is 1 degree below the target temperature"),
|
||||
Property.Number(label="CoolerOffsetOn", configurable=True, description="Offset as decimal number when the cooler is switched on. Should be greater then 'CoolerOffsetOff'. For example a value of 2 switches on the cooler if the current temperature is 2 degrees below the target temperature"),
|
||||
Property.Number(label="CoolerOffsetOff", configurable=True, description="Offset as decimal number when the cooler is switched off. Should be smaller then 'CoolerOffsetOn'. For example a value of 1 switches off the cooler if the current temperature is 1 degree below the target temperature"),
|
||||
Property.Select(label="AutoStart", options=["Yes","No"],description="Autostart Fermenter on cbpi start")])
|
||||
|
||||
class FermenterHysteresis(CBPiFermenterLogic):
|
||||
|
||||
async def run(self):
|
||||
try:
|
||||
self.heater_offset_min = float(self.props.get("HeaterOffsetOn", 0))
|
||||
self.heater_offset_max = float(self.props.get("HeaterOffsetOff", 0))
|
||||
self.cooler_offset_min = float(self.props.get("CoolerOffsetOn", 0))
|
||||
self.cooler_offset_max = float(self.props.get("CoolerOffsetOff", 0))
|
||||
|
||||
self.fermenter = self.get_fermenter(self.id)
|
||||
self.heater = self.fermenter.heater
|
||||
self.cooler = self.fermenter.cooler
|
||||
|
||||
target_temp = self.get_fermenter_target_temp(self.id)
|
||||
if target_temp == 0:
|
||||
await self.set_fermenter_target_temp(self.id,int(self.props.get("TargetTemp", 0)))
|
||||
|
||||
|
||||
while self.running == True:
|
||||
|
||||
sensor_value = self.get_sensor_value(self.fermenter.sensor).get("value")
|
||||
target_temp = self.get_fermenter_target_temp(self.id)
|
||||
|
||||
if sensor_value + self.heater_offset_min <= target_temp:
|
||||
if self.heater:
|
||||
await self.actor_on(self.heater)
|
||||
|
||||
if sensor_value + self.heater_offset_max >= target_temp:
|
||||
if self.heater:
|
||||
await self.actor_off(self.heater)
|
||||
|
||||
if sensor_value >= self.cooler_offset_min + target_temp:
|
||||
if self.cooler:
|
||||
await self.actor_on(self.cooler)
|
||||
|
||||
if sensor_value <= self.cooler_offset_max + target_temp:
|
||||
if self.cooler:
|
||||
await self.actor_off(self.cooler)
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except asyncio.CancelledError as e:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.error("CustomLogic Error {}".format(e))
|
||||
finally:
|
||||
self.running = False
|
||||
if self.heater:
|
||||
await self.actor_off(self.heater)
|
||||
if self.cooler:
|
||||
await self.actor_off(self.cooler)
|
||||
|
||||
|
||||
|
||||
def setup(cbpi):
|
||||
|
||||
'''
|
||||
This method is called by the server during startup
|
||||
Here you need to register your plugins at the server
|
||||
|
||||
:param cbpi: the cbpi core
|
||||
:return:
|
||||
'''
|
||||
|
||||
cbpi.plugin.register("Fermenter Hysteresis", FermenterHysteresis)
|
||||
cbpi.plugin.register("Fermenter Autostart", FermenterAutostart)
|
||||
|
3
cbpi/extension/FermenterHysteresis/config.yaml
Normal file
3
cbpi/extension/FermenterHysteresis/config.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
name: FermenterHysteresis
|
||||
version: 4
|
||||
active: true
|
285
cbpi/http_endpoints/http_fermentation.py
Normal file
285
cbpi/http_endpoints/http_fermentation.py
Normal file
|
@ -0,0 +1,285 @@
|
|||
from cbpi.controller.fermentation_controller import FermentationController
|
||||
from cbpi.api.dataclasses import Fermenter, Step, Props
|
||||
from aiohttp import web
|
||||
from cbpi.api import *
|
||||
import logging
|
||||
import json
|
||||
|
||||
auth = False
|
||||
|
||||
class FermentationHttpEndpoints():
|
||||
|
||||
def __init__(self, cbpi):
|
||||
self.cbpi = cbpi
|
||||
self.controller : FermentationController = cbpi.fermenter
|
||||
self.cbpi.register(self, "/fermenter")
|
||||
|
||||
@request_mapping(path="/", auth_required=False)
|
||||
async def http_get_all(self, request):
|
||||
"""
|
||||
---
|
||||
description: Show all Fermenters
|
||||
tags:
|
||||
- Fermenter
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
data= self.controller.get_state()
|
||||
return web.json_response(data=data)
|
||||
|
||||
|
||||
@request_mapping(path="/", method="POST", auth_required=False)
|
||||
async def http_add(self, request):
|
||||
"""
|
||||
---
|
||||
description: add one Fermenter
|
||||
tags:
|
||||
- Fermenter
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
description: Create a Fermenter
|
||||
required: true
|
||||
|
||||
schema:
|
||||
type: object
|
||||
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
sensor:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
heater:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
cooler:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
target_temp:
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
type:
|
||||
type: string
|
||||
props:
|
||||
type: object
|
||||
example:
|
||||
name: "Fermenter 1"
|
||||
type: "CustomFermenterLogic"
|
||||
sensor: "FermenterSensor"
|
||||
heater: "FermenterHeater"
|
||||
cooler: "FermenterCooler"
|
||||
props: {}
|
||||
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
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"))
|
||||
response_data = await self.controller.create(fermenter)
|
||||
return web.json_response(data=response_data.to_dict())
|
||||
|
||||
|
||||
@request_mapping(path="/{id}", method="PUT", auth_required=False)
|
||||
async def http_update(self, request):
|
||||
"""
|
||||
---
|
||||
description: Update a Fermenter (NOT YET IMPLEMENTED)
|
||||
tags:
|
||||
- Fermenter
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Fermenter ID"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
- in: body
|
||||
name: body
|
||||
description: Update a Fermenter
|
||||
required: false
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
config:
|
||||
props: object
|
||||
responses:
|
||||
"200":
|
||||
description: successful operation
|
||||
"""
|
||||
id = request.match_info['id']
|
||||
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"))
|
||||
return web.json_response(data=(await self.controller.update(fermenter)).to_dict())
|
||||
|
||||
@request_mapping(path="/{id}", method="DELETE", auth_required=False)
|
||||
async def http_delete_one(self, request):
|
||||
"""
|
||||
---
|
||||
description: Delete an actor
|
||||
tags:
|
||||
- Fermenter
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Fermenter ID"
|
||||
required: true
|
||||
type: "string"
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
id = request.match_info['id']
|
||||
await self.controller.delete(id)
|
||||
return web.Response(status=204)
|
||||
|
||||
# @request_mapping(path="/{id}/on", method="POST", auth_required=False)
|
||||
# async def http_on(self, request) -> web.Response:
|
||||
"""
|
||||
|
||||
---
|
||||
description: Switch actor on
|
||||
tags:
|
||||
- Fermenter
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Actor ID"
|
||||
required: true
|
||||
type: "string"
|
||||
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"405":
|
||||
description: invalid HTTP Met
|
||||
"""
|
||||
# id = request.match_info['id']
|
||||
# await self.controller.start(id)
|
||||
# return web.Response(status=204)
|
||||
|
||||
# @request_mapping(path="/{id}/off", method="POST", auth_required=False)
|
||||
# async def http_off(self, request) -> web.Response:
|
||||
"""
|
||||
|
||||
---
|
||||
description: Switch actor on
|
||||
tags:
|
||||
- Fermenter
|
||||
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Actor ID"
|
||||
required: true
|
||||
type: "string"
|
||||
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"405":
|
||||
description: invalid HTTP Met
|
||||
"""
|
||||
# id = request.match_info['id']
|
||||
# await self.controller.off(id)
|
||||
# return web.Response(status=204)
|
||||
|
||||
@request_mapping(path="/{id}/toggle", method="POST", auth_required=False)
|
||||
async def http_toggle(self, request) -> web.Response:
|
||||
"""
|
||||
|
||||
---
|
||||
description: Switch actor on
|
||||
tags:
|
||||
- Fermenter
|
||||
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Kettle ID"
|
||||
required: true
|
||||
type: "string"
|
||||
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"405":
|
||||
description: invalid HTTP Met
|
||||
"""
|
||||
id = request.match_info['id']
|
||||
await self.controller.toggle(id)
|
||||
return web.Response(status=204)
|
||||
|
||||
# @request_mapping(path="/{id}/action", method="POST", auth_required=auth)
|
||||
# async def http_action(self, request) -> web.Response:
|
||||
"""
|
||||
|
||||
---
|
||||
description: Toogle an actor on or off
|
||||
tags:
|
||||
- Fermenter
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Actor ID"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
- in: body
|
||||
name: body
|
||||
description: Update an actor
|
||||
required: false
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
parameter:
|
||||
type: object
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
# actor_id = request.match_info['id']
|
||||
# data = await request.json()
|
||||
# await self.controller.call_action(actor_id, data.get("name"), data.get("parameter"))
|
||||
|
||||
return web.Response(status=204)
|
||||
@request_mapping(path="/{id}/target_temp", method="POST", auth_required=auth)
|
||||
async def http_target(self, request) -> web.Response:
|
||||
"""
|
||||
|
||||
---
|
||||
description: Set Target Temp for Fermenter
|
||||
tags:
|
||||
- Fermenter
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "Fermenter ID"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
- in: body
|
||||
name: body
|
||||
description: Update Temp
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
temp:
|
||||
type: integer
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
id = request.match_info['id']
|
||||
data = await request.json()
|
||||
await self.controller.set_target_temp(id,data.get("temp"))
|
||||
return web.Response(status=204)
|
|
@ -29,6 +29,7 @@ class SystemHttpEndpoints:
|
|||
"""
|
||||
return web.json_response(data=dict(
|
||||
actor=self.cbpi.actor.get_state(),
|
||||
fermenter=self.cbpi.fermenter.get_state(),
|
||||
sensor=self.cbpi.sensor.get_state(),
|
||||
kettle=self.cbpi.kettle.get_state(),
|
||||
step=self.cbpi.step.get_state(),
|
||||
|
|
Loading…
Reference in a new issue