mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-12-22 21:44:57 +01:00
Merge pull request #27 from avollkopf/development
Merge from Development Branch -> Add Fermenter Class
This commit is contained in:
commit
029a6c280e
28 changed files with 949 additions and 62 deletions
|
@ -1 +1 @@
|
|||
__version__ = "4.0.0.59"
|
||||
__version__ = "4.0.1.0"
|
||||
|
|
|
@ -9,12 +9,14 @@ __all__ = ["CBPiActor",
|
|||
"parameters",
|
||||
"background_task",
|
||||
"CBPiKettleLogic",
|
||||
"CBPiFermenterLogic",
|
||||
"CBPiException",
|
||||
"KettleException",
|
||||
"SensorException",
|
||||
"ActorException",
|
||||
"CBPiSensor",
|
||||
"CBPiStep"]
|
||||
"CBPiStep",
|
||||
"CBPiFermentationStep"]
|
||||
|
||||
from cbpi.api.actor import *
|
||||
from cbpi.api.sensor import *
|
||||
|
@ -22,5 +24,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,32 @@ 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 "name={} props={} temp={}".format(self.name, self.props, self.target_temp)
|
||||
|
||||
# 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 +179,7 @@ class ConfigType(Enum):
|
|||
NUMBER="number"
|
||||
SELECT="select"
|
||||
STEP="step"
|
||||
FERMENTER="fermenter"
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
__all__ = ["CBPiException","KettleException","SensorException","ActorException"]
|
||||
__all__ = ["CBPiException","KettleException","FermenterException","SensorException","ActorException"]
|
||||
|
||||
|
||||
class CBPiException(Exception):
|
||||
|
@ -7,6 +7,9 @@ class CBPiException(Exception):
|
|||
class KettleException(CBPiException):
|
||||
pass
|
||||
|
||||
class FermenterException(CBPiException):
|
||||
pass
|
||||
|
||||
class SensorException(CBPiException):
|
||||
pass
|
||||
|
||||
|
|
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
|
|
@ -108,4 +108,22 @@ class Property(object):
|
|||
PropertyType.__init__(self)
|
||||
self.label = label
|
||||
self.configurable = True
|
||||
self.description = description
|
||||
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=""):
|
||||
'''
|
||||
|
||||
:param label:
|
||||
:param description:
|
||||
'''
|
||||
|
||||
PropertyType.__init__(self)
|
||||
self.label = label
|
||||
self.configurable = True
|
||||
self.description = description
|
||||
|
||||
|
|
|
@ -35,7 +35,8 @@ class CBPiSensor(CBPiBase, metaclass=ABCMeta):
|
|||
def push_update(self, value):
|
||||
try:
|
||||
self.cbpi.ws.send(dict(topic="sensorstate", id=self.id, value=value))
|
||||
self.cbpi.push_update("cbpi/sensor/{}/udpate".format(self.id), dict(id=self.id, value=value), retain=True)
|
||||
self.cbpi.push_update("cbpi/sensordata/{}".format(self.id), dict(id=self.id, value=value), retain=True)
|
||||
# self.cbpi.push_update("cbpi/sensor/{}/udpate".format(self.id), dict(id=self.id, value=value), retain=True)
|
||||
except:
|
||||
logging.error("Faild to push sensor update")
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from abc import abstractmethod
|
|||
|
||||
from cbpi.api.base import CBPiBase
|
||||
|
||||
__all__ = ["StepResult", "StepState", "StepMove", "CBPiStep"]
|
||||
__all__ = ["StepResult", "StepState", "StepMove", "CBPiStep", "CBPiFermentationStep"]
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
|
|
@ -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": [
|
||||
|
||||
]
|
||||
}
|
|
@ -21,7 +21,7 @@ class ActorController(BasicController):
|
|||
if item.instance.state is False:
|
||||
await item.instance.on(power)
|
||||
await self.push_udpate()
|
||||
self.cbpi.push_update("cbpi/actor/"+id, item.to_dict(), True)
|
||||
self.cbpi.push_update("cbpi/actorupdate/{}".format(id), item.to_dict(), True)
|
||||
else:
|
||||
await self.set_power(id, power)
|
||||
|
||||
|
@ -34,7 +34,7 @@ class ActorController(BasicController):
|
|||
if item.instance.state is True:
|
||||
await item.instance.off()
|
||||
await self.push_udpate()
|
||||
self.cbpi.push_update("cbpi/actor/"+id, item.to_dict())
|
||||
self.cbpi.push_update("cbpi/actorupdate/{}".format(id), item.to_dict())
|
||||
except Exception as e:
|
||||
logging.error("Failed to switch on Actor {} {}".format(id, e), True)
|
||||
|
||||
|
@ -43,7 +43,7 @@ class ActorController(BasicController):
|
|||
item = self.find_by_id(id)
|
||||
instance = item.get("instance")
|
||||
await instance.toggle()
|
||||
self.cbpi.push_update("cbpi/actor/update", item.to_dict())
|
||||
self.cbpi.push_update("cbpi/actorupdate/{}".format(id), item.to_dict())
|
||||
except Exception as e:
|
||||
logging.error("Failed to toggle Actor {} {}".format(id, e))
|
||||
|
||||
|
@ -59,6 +59,6 @@ class ActorController(BasicController):
|
|||
item = self.find_by_id(id)
|
||||
item.power = round(power)
|
||||
await self.push_udpate()
|
||||
self.cbpi.push_update("cbpi/actor/"+id, item.to_dict())
|
||||
self.cbpi.push_update("cbpi/actorupdate/{}".format(id), item.to_dict())
|
||||
except Exception as e:
|
||||
logging.error("Failed to update Actor {} {}".format(id, e))
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import logging
|
||||
import os.path
|
||||
import json
|
||||
from cbpi.api.dataclasses import Actor, Props
|
||||
from cbpi.api.dataclasses import Fermenter, Actor, Props
|
||||
import sys, os
|
||||
import shortuuid
|
||||
import asyncio
|
||||
|
@ -55,7 +55,9 @@ class BasicController:
|
|||
|
||||
async def push_udpate(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)))
|
||||
self.cbpi.push_update("cbpi/{}".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
||||
#for item in self.data:
|
||||
# self.cbpi.push_update("cbpi/{}/{}".format(self.update_key,item.id), item.to_dict())
|
||||
|
||||
def find_by_id(self, id):
|
||||
return next((item for item in self.data if item.id == id), None)
|
||||
|
|
|
@ -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
|
||||
from ..api.step import CBPiStep, StepMove, StepResult, StepState, CBPiFermentationStep
|
||||
|
||||
|
||||
|
||||
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,17 +80,43 @@ 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")
|
||||
self._loop = asyncio.get_event_loop()
|
||||
self.data = {}
|
||||
self.data = []
|
||||
self.types = {}
|
||||
self.steptypes = {}
|
||||
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/{}".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
|
||||
|
||||
#for item in self.data:
|
||||
# self.cbpi.push_update("cbpi/{}/{}".format(self.update_key,item.id), item.to_dict())
|
||||
pass
|
||||
|
||||
async def shutdown(self, app=None):
|
||||
self.save()
|
||||
for fermenter in self.data:
|
||||
|
@ -108,13 +129,15 @@ class FermenationController:
|
|||
self.logger.error(e)
|
||||
|
||||
async def load(self):
|
||||
if os.path.exists(self.path) is False:
|
||||
with open(self.path, "w") as file:
|
||||
json.dump(dict(basic={}, steps=[]), file, indent=4, sort_keys=True)
|
||||
# if os.path.exists(self.path) is False:
|
||||
# with open(self.path, "w") as file:
|
||||
# json.dump(dict(basic={}, steps=[]), file, indent=4, sort_keys=True)
|
||||
with open(self.path) as json_file:
|
||||
d = json.load(json_file)
|
||||
self.data = list(map(lambda item: self._create(item), d))
|
||||
|
||||
data = json.load(json_file)
|
||||
|
||||
for i in data["data"]:
|
||||
self.data.append(self._create(i))
|
||||
|
||||
def _create_step(self, fermenter, item):
|
||||
id = item.get("id")
|
||||
name = item.get("name")
|
||||
|
@ -138,22 +161,47 @@ class FermenationController:
|
|||
asyncio.create_task(self.start(step_instance.step.fermenter.id))
|
||||
|
||||
def _create(self, data):
|
||||
id = data.get("id")
|
||||
name = data.get("name")
|
||||
brewname = data.get("brewname")
|
||||
props = Props(data.get("props", {}))
|
||||
fermenter = Fermenter(id, name, brewname, props, 0)
|
||||
fermenter.steps = list(map(lambda item: self._create_step(fermenter, item), data.get("steps", [])))
|
||||
return fermenter
|
||||
try:
|
||||
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, sensor, heater, cooler, brewname, props, temp, logictype)
|
||||
fermenter.steps = list(map(lambda item: self._create_step(fermenter, item), data.get("steps", [])))
|
||||
self.push_update()
|
||||
return fermenter
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
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):
|
||||
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_steptypes(self):
|
||||
result = {}
|
||||
for key, value in self.steptypes.items():
|
||||
result[key] = dict(name=value.get("name"), properties=value.get("properties"), actions=value.get("actions"))
|
||||
return result
|
||||
|
||||
def get_state(self):
|
||||
if self.data == []:
|
||||
logging.info(self.data)
|
||||
|
||||
return {"data": list(map(lambda x: x.to_dict(), self.data)), "types":self.get_types(), "steptypes":self.get_steptypes()}
|
||||
|
||||
async def get(self, id: str ):
|
||||
return self._find_by_id(id)
|
||||
|
@ -162,12 +210,19 @@ class FermenationController:
|
|||
data.id = shortuuid.uuid()
|
||||
self.data.append(data)
|
||||
self.save()
|
||||
self.push_update()
|
||||
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,23 +230,37 @@ 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()
|
||||
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()
|
||||
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()
|
||||
|
||||
def save(self):
|
||||
data = dict(data=list(map(lambda item: item.to_dict(), self.data)))
|
||||
with open(self.path, "w") as file:
|
||||
json.dump(list(map(lambda item: item.to_dict(), self.data)), file, indent=4, sort_keys=True)
|
||||
json.dump(data, file, indent=4, sort_keys=True)
|
||||
|
||||
async def create_step(self, id, step: Step):
|
||||
try:
|
||||
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 +292,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 +310,42 @@ 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))
|
||||
|
||||
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)
|
||||
|
||||
if item.instance is None or item.instance.state == False:
|
||||
await self.start_logic(id)
|
||||
else:
|
||||
await item.instance.stop()
|
||||
self.push_update()
|
||||
|
||||
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))
|
||||
|
@ -284,4 +390,3 @@ 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,12 +74,18 @@ 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)
|
||||
|
||||
if issubclass(clazz, CBPiStep):
|
||||
self.cbpi.step.types[name] = self._parse_step_props(clazz, name)
|
||||
|
||||
if issubclass(clazz, CBPiFermentationStep):
|
||||
self.cbpi.fermenter.steptypes[name] = self._parse_step_props(clazz, name)
|
||||
|
||||
if issubclass(clazz, CBPiExtension):
|
||||
self.c = clazz(self.cbpi)
|
||||
|
||||
|
@ -100,6 +105,8 @@ class PluginController():
|
|||
return {"label": p.label, "type": "sensor", "configurable": p.configurable, "description": p.description}
|
||||
elif isinstance(p, Property.Kettle):
|
||||
return {"label": p.label, "type": "kettle", "configurable": p.configurable, "description": p.description}
|
||||
elif isinstance(p, Property.Fermenter):
|
||||
return {"label": p.label, "type": "fermenter", "configurable": p.configurable, "description": p.description}
|
||||
|
||||
def _parse_step_props(self, cls, name):
|
||||
|
||||
|
@ -160,6 +167,11 @@ class PluginController():
|
|||
result["properties"].append(
|
||||
{"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable,
|
||||
"description": t.description})
|
||||
elif isinstance(tmpObj.__getattribute__(m), Property.Fermenter):
|
||||
t = tmpObj.__getattribute__(m)
|
||||
result["properties"].append(
|
||||
{"name": m, "label": t.label, "type": "fermenter", "configurable": t.configurable,
|
||||
"description": t.description})
|
||||
|
||||
for method_name, method in cls.__dict__.items():
|
||||
if hasattr(method, "action"):
|
||||
|
|
|
@ -72,7 +72,7 @@ class StepController:
|
|||
self._loop.create_task(self.start_step(active_step))
|
||||
|
||||
async def add(self, item: Step):
|
||||
logging.debug("Add step")
|
||||
logging.info("Add step")
|
||||
item.id = shortuuid.uuid()
|
||||
item.status = StepState.INITIAL
|
||||
try:
|
||||
|
@ -256,8 +256,10 @@ class StepController:
|
|||
self.cbpi.ws.send(dict(topic="mash_profile_update", data=self.get_state()))
|
||||
else:
|
||||
self.cbpi.ws.send(dict(topic="step_update", data=list(map(lambda item: item.to_dict(), self.profile))))
|
||||
|
||||
|
||||
self.cbpi.push_update(topic="cbpi/stepupdate", data=list(map(lambda item: item.to_dict(), self.profile)))
|
||||
#for item in self.profile:
|
||||
# self.cbpi.push_update(topic="cbpi/stepupdate/{}".format(item.id), data=(item.to_dict()))
|
||||
|
||||
async def start_step(self,step):
|
||||
try:
|
||||
|
|
|
@ -264,7 +264,7 @@ class UploadController:
|
|||
"props": {
|
||||
"AutoMode": self.AutoMode,
|
||||
"Kettle": self.boilid,
|
||||
"Sensor": self.kettle.sensor,
|
||||
"Sensor": self.boilkettle.sensor,
|
||||
"Temp": int(self.BoilTemp),
|
||||
"Timer": BoilTime,
|
||||
"First_Wort": FirstWort,
|
||||
|
@ -393,7 +393,7 @@ class UploadController:
|
|||
step_type = self.boil if self.boil != "" else "BoilStep"
|
||||
step_time = str(int(boil_time))
|
||||
step_temp = self.BoilTemp
|
||||
sensor = self.kettle.sensor
|
||||
sensor = self.boilkettle.sensor
|
||||
LidAlert = "Yes"
|
||||
|
||||
step_string = { "name": "Boil Step",
|
||||
|
@ -584,7 +584,7 @@ class UploadController:
|
|||
step_time = str(int(BoilTime))
|
||||
step_type = self.boil if self.boil != "" else "BoilStep"
|
||||
step_temp = self.BoilTemp
|
||||
sensor = self.kettle.sensor
|
||||
sensor = self.boilkettle.sensor
|
||||
LidAlert = "Yes"
|
||||
|
||||
step_string = { "name": "Boil Step",
|
||||
|
@ -697,7 +697,7 @@ class UploadController:
|
|||
if step_type == "CooldownStep":
|
||||
cooldown_sensor = self.cbpi.config.get("steps_cooldown_sensor", None)
|
||||
if cooldown_sensor is None or cooldown_sensor == '':
|
||||
cooldown_sensor = self.kettle.sensor # fall back to kettle sensor if no other sensor is specified
|
||||
cooldown_sensor = self.boilkettle.sensor # fall back to boilkettle sensor if no other sensor is specified
|
||||
step_timer = ""
|
||||
step_temp = int(self.CoolDownTemp)
|
||||
step_string = { "name": "Cooldown",
|
||||
|
@ -744,7 +744,7 @@ class UploadController:
|
|||
try:
|
||||
self.boilkettle = self.cbpi.kettle.find_by_id(self.boilid)
|
||||
except:
|
||||
pass
|
||||
self.boilkettle = self.kettle
|
||||
|
||||
config_values = { "kettle": self.kettle,
|
||||
"kettle_id": str(self.id),
|
||||
|
|
|
@ -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
|
||||
|
|
231
cbpi/extension/FermentationStep/__init__.py
Normal file
231
cbpi/extension/FermentationStep/__init__.py
Normal file
|
@ -0,0 +1,231 @@
|
|||
import asyncio
|
||||
|
||||
from cbpi.api import parameters, Property, action
|
||||
from cbpi.api.step import StepResult, CBPiFermentationStep
|
||||
from cbpi.api.timer import Timer
|
||||
from datetime import datetime
|
||||
import time
|
||||
from voluptuous.schema_builder import message
|
||||
from cbpi.api.dataclasses import NotificationAction, NotificationType
|
||||
from cbpi.api.dataclasses import Kettle, Props, Fermenter
|
||||
from cbpi.api import *
|
||||
import logging
|
||||
from socket import timeout
|
||||
from typing import KeysView
|
||||
from cbpi.api.config import ConfigType
|
||||
from cbpi.api.base import CBPiBase
|
||||
import numpy as np
|
||||
import warnings
|
||||
|
||||
|
||||
|
||||
@parameters([Property.Text(label="Notification",configurable = True, description = "Text for notification"),
|
||||
Property.Select(label="AutoNext",options=["Yes","No"], description="Automatically move to next step (Yes) or pause after Notification (No)")])
|
||||
class FermenterNotificationStep(CBPiFermentationStep):
|
||||
|
||||
async def NextStep(self, **kwargs):
|
||||
await self.next()
|
||||
|
||||
async def on_timer_done(self,timer):
|
||||
self.summary = self.props.get("Notification","")
|
||||
|
||||
if self.AutoNext == True:
|
||||
self.cbpi.notify(self.name, self.props.get("Notification",""), NotificationType.INFO)
|
||||
await self.next()
|
||||
else:
|
||||
self.cbpi.notify(self.name, self.props.get("Notification",""), NotificationType.INFO, action=[NotificationAction("Next Step", self.NextStep)])
|
||||
await self.push_update()
|
||||
|
||||
async def on_timer_update(self,timer, seconds):
|
||||
await self.push_update()
|
||||
|
||||
async def on_start(self):
|
||||
self.summary=""
|
||||
self.AutoNext = False if self.props.get("AutoNext", "No") == "No" else True
|
||||
if self.timer is None:
|
||||
self.timer = Timer(1 ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
||||
await self.push_update()
|
||||
|
||||
async def on_stop(self):
|
||||
await self.timer.stop()
|
||||
self.summary = ""
|
||||
await self.push_update()
|
||||
|
||||
async def run(self):
|
||||
while self.running == True:
|
||||
await asyncio.sleep(1)
|
||||
if self.timer.is_running is not True:
|
||||
self.timer.start()
|
||||
self.timer.is_running = True
|
||||
|
||||
return StepResult.DONE
|
||||
|
||||
@parameters([Property.Number(label="Temp", configurable=True),
|
||||
Property.Sensor(label="Sensor"),
|
||||
Property.Kettle(label="Kettle"),
|
||||
Property.Text(label="Notification",configurable = True, description = "Text for notification when Temp is reached"),
|
||||
Property.Select(label="AutoMode",options=["Yes","No"], description="Switch Kettlelogic automatically on and off -> Yes")])
|
||||
class FermenterTargetTempStep(CBPiFermentationStep):
|
||||
|
||||
async def NextStep(self, **kwargs):
|
||||
await self.next()
|
||||
|
||||
async def on_timer_done(self,timer):
|
||||
self.summary = ""
|
||||
self.kettle.target_temp = 0
|
||||
await self.push_update()
|
||||
if self.AutoMode == True:
|
||||
await self.setAutoMode(False)
|
||||
self.cbpi.notify(self.name, self.props.get("Notification","Target Temp reached. Please add malt and klick next to move on."), action=[NotificationAction("Next Step", self.NextStep)])
|
||||
|
||||
async def on_timer_update(self,timer, seconds):
|
||||
await self.push_update()
|
||||
|
||||
async def on_start(self):
|
||||
self.AutoMode = True if self.props.get("AutoMode","No") == "Yes" else False
|
||||
self.kettle=self.get_kettle(self.props.get("Kettle", None))
|
||||
if self.kettle is not None:
|
||||
self.kettle.target_temp = int(self.props.get("Temp", 0))
|
||||
if self.AutoMode == True:
|
||||
await self.setAutoMode(True)
|
||||
self.summary = "Waiting for Target Temp"
|
||||
if self.cbpi.kettle is not None and self.timer is None:
|
||||
self.timer = Timer(1 ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
||||
await self.push_update()
|
||||
|
||||
async def on_stop(self):
|
||||
await self.timer.stop()
|
||||
self.summary = ""
|
||||
if self.AutoMode == True:
|
||||
await self.setAutoMode(False)
|
||||
await self.push_update()
|
||||
|
||||
async def run(self):
|
||||
while self.running == True:
|
||||
await asyncio.sleep(1)
|
||||
sensor_value = self.get_sensor_value(self.props.get("Sensor", None)).get("value")
|
||||
if sensor_value >= int(self.props.get("Temp",0)) and self.timer.is_running is not True:
|
||||
self.timer.start()
|
||||
self.timer.is_running = True
|
||||
await self.push_update()
|
||||
return StepResult.DONE
|
||||
|
||||
async def reset(self):
|
||||
self.timer = Timer(1 ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
||||
|
||||
async def setAutoMode(self, auto_state):
|
||||
try:
|
||||
if (self.kettle.instance is None or self.kettle.instance.state == False) and (auto_state is True):
|
||||
await self.cbpi.kettle.toggle(self.kettle.id)
|
||||
elif (self.kettle.instance.state == True) and (auto_state is False):
|
||||
await self.cbpi.kettle.stop(self.kettle.id)
|
||||
await self.push_update()
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Failed to switch on KettleLogic {} {}".format(self.kettle.id, e))
|
||||
|
||||
|
||||
@parameters([Property.Number(label="Timer", description="Time in Minutes", configurable=True),
|
||||
Property.Number(label="Temp", configurable=True),
|
||||
Property.Sensor(label="Sensor"),
|
||||
Property.Kettle(label="Kettle"),
|
||||
Property.Select(label="AutoMode",options=["Yes","No"], description="Switch Kettlelogic automatically on and off -> Yes")])
|
||||
class FermentationStep(CBPiFermentationStep):
|
||||
|
||||
@action("Start Timer", [])
|
||||
async def start_timer(self):
|
||||
if self.timer.is_running is not True:
|
||||
self.cbpi.notify(self.name, 'Timer started', NotificationType.INFO)
|
||||
self.timer.start()
|
||||
self.timer.is_running = True
|
||||
else:
|
||||
self.cbpi.notify(self.name, 'Timer is already running', NotificationType.WARNING)
|
||||
|
||||
@action("Add 5 Minutes to Timer", [])
|
||||
async def add_timer(self):
|
||||
if self.timer.is_running == True:
|
||||
self.cbpi.notify(self.name, '5 Minutes added', NotificationType.INFO)
|
||||
await self.timer.add(300)
|
||||
else:
|
||||
self.cbpi.notify(self.name, 'Timer must be running to add time', NotificationType.WARNING)
|
||||
|
||||
|
||||
async def on_timer_done(self,timer):
|
||||
self.summary = ""
|
||||
self.kettle.target_temp = 0
|
||||
if self.AutoMode == True:
|
||||
await self.setAutoMode(False)
|
||||
self.cbpi.notify(self.name, 'Step finished', NotificationType.SUCCESS)
|
||||
|
||||
await self.next()
|
||||
|
||||
async def on_timer_update(self,timer, seconds):
|
||||
self.summary = Timer.format_time(seconds)
|
||||
await self.push_update()
|
||||
|
||||
async def on_start(self):
|
||||
self.AutoMode = True if self.props.get("AutoMode", "No") == "Yes" else False
|
||||
self.kettle=self.get_kettle(self.props.Kettle)
|
||||
if self.kettle is not None:
|
||||
self.kettle.target_temp = int(self.props.get("Temp", 0))
|
||||
if self.AutoMode == True:
|
||||
await self.setAutoMode(True)
|
||||
await self.push_update()
|
||||
|
||||
if self.cbpi.kettle is not None and self.timer is None:
|
||||
self.timer = Timer(int(self.props.get("Timer",0)) *60 ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
||||
elif self.cbpi.kettle is not None:
|
||||
try:
|
||||
if self.timer.is_running == True:
|
||||
self.timer.start()
|
||||
except:
|
||||
pass
|
||||
|
||||
self.summary = "Waiting for Target Temp"
|
||||
await self.push_update()
|
||||
|
||||
async def on_stop(self):
|
||||
await self.timer.stop()
|
||||
self.summary = ""
|
||||
if self.AutoMode == True:
|
||||
await self.setAutoMode(False)
|
||||
await self.push_update()
|
||||
|
||||
async def reset(self):
|
||||
self.timer = Timer(int(self.props.get("Timer",0)) *60 ,on_update=self.on_timer_update, on_done=self.on_timer_done)
|
||||
|
||||
async def run(self):
|
||||
while self.running == True:
|
||||
await asyncio.sleep(1)
|
||||
sensor_value = self.get_sensor_value(self.props.get("Sensor", None)).get("value")
|
||||
if sensor_value >= int(self.props.get("Temp",0)) and self.timer.is_running is not True:
|
||||
self.timer.start()
|
||||
self.timer.is_running = True
|
||||
estimated_completion_time = datetime.fromtimestamp(time.time()+ (int(self.props.get("Timer",0)))*60)
|
||||
self.cbpi.notify(self.name, 'Timer started. Estimated completion: {}'.format(estimated_completion_time.strftime("%H:%M")), NotificationType.INFO)
|
||||
return StepResult.DONE
|
||||
|
||||
async def setAutoMode(self, auto_state):
|
||||
try:
|
||||
if (self.kettle.instance is None or self.kettle.instance.state == False) and (auto_state is True):
|
||||
await self.cbpi.kettle.toggle(self.kettle.id)
|
||||
elif (self.kettle.instance.state == True) and (auto_state is False):
|
||||
await self.cbpi.kettle.stop(self.kettle.id)
|
||||
await self.push_update()
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Failed to switch on KettleLogic {} {}".format(self.kettle.id, e))
|
||||
|
||||
|
||||
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("NotificationStep", NotificationStep)
|
||||
cbpi.plugin.register("TargetTempStep", TargetTempStep)
|
||||
cbpi.plugin.register("FermentationStep", FermentationStep)
|
3
cbpi/extension/FermentationStep/config.yaml
Normal file
3
cbpi/extension/FermentationStep/config.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
name: FermentationStep
|
||||
version: 4
|
||||
active: true
|
125
cbpi/extension/FermenterHysteresis/__init__.py
Normal file
125
cbpi/extension/FermenterHysteresis/__init__.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
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
|
||||
try:
|
||||
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
|
||||
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"),
|
||||
Property.Sensor(label="sensor2",description="Optional Sensor for LCDisplay(e.g. iSpindle)")])
|
||||
|
||||
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
|
||||
|
||||
heater = self.cbpi.actor.find_by_id(self.heater)
|
||||
cooler = self.cbpi.actor.find_by_id(self.cooler)
|
||||
|
||||
while self.running == True:
|
||||
|
||||
sensor_value = float(self.get_sensor_value(self.fermenter.sensor).get("value"))
|
||||
target_temp = float(self.get_fermenter_target_temp(self.id))
|
||||
|
||||
try:
|
||||
heater_state = heater.instance.state
|
||||
except:
|
||||
heater_state= False
|
||||
try:
|
||||
cooler_state = cooler.instance.state
|
||||
except:
|
||||
cooler_state= False
|
||||
|
||||
if sensor_value + self.heater_offset_min <= target_temp:
|
||||
if self.heater and (heater_state == False):
|
||||
await self.actor_on(self.heater)
|
||||
|
||||
if sensor_value + self.heater_offset_max >= target_temp:
|
||||
if self.heater and (heater_state == True):
|
||||
await self.actor_off(self.heater)
|
||||
|
||||
if sensor_value >= self.cooler_offset_min + target_temp:
|
||||
if self.cooler and (cooler_state == False):
|
||||
await self.actor_on(self.cooler)
|
||||
|
||||
if sensor_value <= self.cooler_offset_max + target_temp:
|
||||
if self.cooler and (cooler_state == True):
|
||||
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
|
|
@ -319,7 +319,7 @@ class ActorStep(CBPiStep):
|
|||
Property.Number(label="Temp", description="Boil temperature", configurable=True),
|
||||
Property.Sensor(label="Sensor"),
|
||||
Property.Kettle(label="Kettle"),
|
||||
Property.Select(label="LidAlert",options=["Yes","No"], description="Trigger Alert to remove id if temp is close to boil"),
|
||||
Property.Select(label="LidAlert",options=["Yes","No"], description="Trigger Alert to remove lid if temp is close to boil"),
|
||||
Property.Select(label="AutoMode",options=["Yes","No"], description="Switch Kettlelogic automatically on and off -> Yes"),
|
||||
Property.Select("First_Wort", options=["Yes","No"], description="First Wort Hop alert if set to Yes"),
|
||||
Property.Number("Hop_1", configurable = True, description="First Hop alert (minutes before finish)"),
|
||||
|
|
|
@ -9,7 +9,7 @@ class MQTTActor(CBPiActor):
|
|||
# Custom property which can be configured by the user
|
||||
@action("Set Power", parameters=[Property.Number(label="Power", configurable=True, description="Power Setting [0-100]")])
|
||||
async def setpower(self,Power = 100 ,**kwargs):
|
||||
self.power=round(Power)
|
||||
self.power=int(Power)
|
||||
if self.power < 0:
|
||||
self.power = 0
|
||||
if self.power > 100:
|
||||
|
|
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