Merge pull request #27 from avollkopf/development

Merge from Development Branch -> Add Fermenter Class
This commit is contained in:
Alexander Vollkopf 2022-01-11 07:03:23 +01:00 committed by GitHub
commit 029a6c280e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 949 additions and 62 deletions

View file

@ -1 +1 @@
__version__ = "4.0.0.59" __version__ = "4.0.1.0"

View file

@ -9,12 +9,14 @@ __all__ = ["CBPiActor",
"parameters", "parameters",
"background_task", "background_task",
"CBPiKettleLogic", "CBPiKettleLogic",
"CBPiFermenterLogic",
"CBPiException", "CBPiException",
"KettleException", "KettleException",
"SensorException", "SensorException",
"ActorException", "ActorException",
"CBPiSensor", "CBPiSensor",
"CBPiStep"] "CBPiStep",
"CBPiFermentationStep"]
from cbpi.api.actor import * from cbpi.api.actor import *
from cbpi.api.sensor import * from cbpi.api.sensor import *
@ -22,5 +24,6 @@ from cbpi.api.extension import *
from cbpi.api.property import * from cbpi.api.property import *
from cbpi.api.decorator import * from cbpi.api.decorator import *
from cbpi.api.kettle_logic import * from cbpi.api.kettle_logic import *
from cbpi.api.fermenter_logic import *
from cbpi.api.step import * from cbpi.api.step import *
from cbpi.api.exceptions import * from cbpi.api.exceptions import *

View file

@ -29,6 +29,15 @@ class CBPiBase(metaclass=ABCMeta):
async def set_target_temp(self,id, temp): async def set_target_temp(self,id, temp):
await self.cbpi.kettle.set_target_temp(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): def get_sensor(self,id):
return self.cbpi.sensor.find_by_id(id) return self.cbpi.sensor.find_by_id(id)

View file

@ -8,5 +8,6 @@ class ConfigType(Enum):
ACTOR = "actor" ACTOR = "actor"
SENSOR = "sensor" SENSOR = "sensor"
STEP = "step" STEP = "step"
FERMENTER = "fermenter"

View file

@ -62,7 +62,7 @@ class Actor:
def __str__(self): def __str__(self):
return "name={} props={}, state={}, type={}, power={}".format(self.name, self.props, self.state, self.type, self.power) return "name={} props={}, state={}, type={}, power={}".format(self.name, self.props, self.state, self.type, self.power)
def to_dict(self): 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 @dataclass
@ -125,15 +125,32 @@ class Step:
class Fermenter: class Fermenter:
id: str = None id: str = None
name: str = None name: str = None
sensor: Sensor = None
heater: Actor = None
cooler: Actor = None
brewname: str = None brewname: str = None
props: Props = Props() props: Props = Props()
target_temp: int = 0 target_temp: int = 0
type: str = None
steps: List[Step]= field(default_factory=list) steps: List[Step]= field(default_factory=list)
instance: str = None
def __str__(self): 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): 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)) 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 @dataclass
@ -162,6 +179,7 @@ class ConfigType(Enum):
NUMBER="number" NUMBER="number"
SELECT="select" SELECT="select"
STEP="step" STEP="step"
FERMENTER="fermenter"
@dataclass @dataclass
class Config: class Config:

View file

@ -1,4 +1,4 @@
__all__ = ["CBPiException","KettleException","SensorException","ActorException"] __all__ = ["CBPiException","KettleException","FermenterException","SensorException","ActorException"]
class CBPiException(Exception): class CBPiException(Exception):
@ -7,6 +7,9 @@ class CBPiException(Exception):
class KettleException(CBPiException): class KettleException(CBPiException):
pass pass
class FermenterException(CBPiException):
pass
class SensorException(CBPiException): class SensorException(CBPiException):
pass pass

View 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

View file

@ -109,3 +109,21 @@ class Property(object):
self.label = label self.label = label
self.configurable = True 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

View file

@ -35,7 +35,8 @@ class CBPiSensor(CBPiBase, metaclass=ABCMeta):
def push_update(self, value): def push_update(self, value):
try: try:
self.cbpi.ws.send(dict(topic="sensorstate", id=self.id, value=value)) 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: except:
logging.error("Faild to push sensor update") logging.error("Faild to push sensor update")

View file

@ -4,7 +4,7 @@ from abc import abstractmethod
from cbpi.api.base import CBPiBase from cbpi.api.base import CBPiBase
__all__ = ["StepResult", "StepState", "StepMove", "CBPiStep"] __all__ = ["StepResult", "StepState", "StepMove", "CBPiStep", "CBPiFermentationStep"]
from enum import Enum from enum import Enum

View file

@ -42,6 +42,11 @@ def create_config_file():
destfile = os.path.join(".", 'config') destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile) 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: 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") srcfile = os.path.join(os.path.dirname(__file__), "config", "step_data.json")
destfile = os.path.join(".", 'config') destfile = os.path.join(".", 'config')

View file

@ -0,0 +1,5 @@
{
"data": [
]
}

View file

@ -21,7 +21,7 @@ class ActorController(BasicController):
if item.instance.state is False: if item.instance.state is False:
await item.instance.on(power) await item.instance.on(power)
await self.push_udpate() 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: else:
await self.set_power(id, power) await self.set_power(id, power)
@ -34,7 +34,7 @@ class ActorController(BasicController):
if item.instance.state is True: if item.instance.state is True:
await item.instance.off() await item.instance.off()
await self.push_udpate() 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: except Exception as e:
logging.error("Failed to switch on Actor {} {}".format(id, e), True) logging.error("Failed to switch on Actor {} {}".format(id, e), True)
@ -43,7 +43,7 @@ class ActorController(BasicController):
item = self.find_by_id(id) item = self.find_by_id(id)
instance = item.get("instance") instance = item.get("instance")
await instance.toggle() 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: except Exception as e:
logging.error("Failed to toggle Actor {} {}".format(id, e)) logging.error("Failed to toggle Actor {} {}".format(id, e))
@ -59,6 +59,6 @@ class ActorController(BasicController):
item = self.find_by_id(id) item = self.find_by_id(id)
item.power = round(power) item.power = round(power)
await self.push_udpate() 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: except Exception as e:
logging.error("Failed to update Actor {} {}".format(id, e)) logging.error("Failed to update Actor {} {}".format(id, e))

View file

@ -2,7 +2,7 @@
import logging import logging
import os.path import os.path
import json import json
from cbpi.api.dataclasses import Actor, Props from cbpi.api.dataclasses import Fermenter, Actor, Props
import sys, os import sys, os
import shortuuid import shortuuid
import asyncio import asyncio
@ -55,7 +55,9 @@ class BasicController:
async def push_udpate(self): 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.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): def find_by_id(self, id):
return next((item for item in self.data if item.id == id), None) return next((item for item in self.data if item.id == id), None)

View file

@ -1,4 +1,3 @@
import asyncio import asyncio
import cbpi import cbpi
import copy import copy
@ -9,19 +8,15 @@ from os import listdir
from os.path import isfile, join from os.path import isfile, join
import shortuuid import shortuuid
from cbpi.api.dataclasses import Fermenter, FermenterStep, Props, Step from cbpi.api.dataclasses import Fermenter, FermenterStep, Props, Step
from cbpi.controller.basic_controller2 import BasicController
from tabulate import tabulate from tabulate import tabulate
import sys, os 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: class FermentStep:
def __init__(self, cbpi, step, on_done) -> None: def __init__(self, cbpi, step, on_done) -> None:
self.cbpi = cbpi self.cbpi = cbpi
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
@ -85,17 +80,43 @@ class FermentStep:
async def on_stop(self): async def on_stop(self):
pass pass
class FermenationController: class FermentationController:
def __init__(self, cbpi): def __init__(self, cbpi):
self.update_key = "fermenterupdate"
self.cbpi = cbpi self.cbpi = cbpi
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.path = os.path.join(".", 'config', "fermenter_data.json") self.path = os.path.join(".", 'config', "fermenter_data.json")
self._loop = asyncio.get_event_loop() self._loop = asyncio.get_event_loop()
self.data = {} self.data = []
self.types = {} self.types = {}
self.steptypes = {}
self.cbpi.app.on_cleanup.append(self.shutdown) 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): async def shutdown(self, app=None):
self.save() self.save()
for fermenter in self.data: for fermenter in self.data:
@ -108,12 +129,14 @@ class FermenationController:
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:
json.dump(dict(basic={}, steps=[]), file, indent=4, sort_keys=True) # json.dump(dict(basic={}, steps=[]), file, indent=4, sort_keys=True)
with open(self.path) as json_file: with open(self.path) as json_file:
d = json.load(json_file) data = json.load(json_file)
self.data = list(map(lambda item: self._create(item), d))
for i in data["data"]:
self.data.append(self._create(i))
def _create_step(self, fermenter, item): def _create_step(self, fermenter, item):
id = item.get("id") id = item.get("id")
@ -138,22 +161,47 @@ class FermenationController:
asyncio.create_task(self.start(step_instance.step.fermenter.id)) asyncio.create_task(self.start(step_instance.step.fermenter.id))
def _create(self, data): def _create(self, data):
id = data.get("id") try:
name = data.get("name") id = data.get("id")
brewname = data.get("brewname") name = data.get("name")
props = Props(data.get("props", {})) sensor = data.get("sensor")
fermenter = Fermenter(id, name, brewname, props, 0) heater = data.get("heater")
fermenter.steps = list(map(lambda item: self._create_step(fermenter, item), data.get("steps", []))) cooler = data.get("cooler")
return fermenter 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): def _find_by_id(self, id):
return next((item for item in self.data if item.id == id), None) return next((item for item in self.data if item.id == id), None)
async def init(self):
pass
async def get_all(self): 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 ): async def get(self, id: str ):
return self._find_by_id(id) return self._find_by_id(id)
@ -162,12 +210,19 @@ class FermenationController:
data.id = shortuuid.uuid() data.id = shortuuid.uuid()
self.data.append(data) self.data.append(data)
self.save() self.save()
self.push_update()
return data return data
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.heater = item.heater
old_item.cooler = item.cooler
old_item.type = item.type
old_item.brewname = item.brewname old_item.brewname = item.brewname
old_item.props = item.props old_item.props = item.props
old_item.target_temp = item.target_temp 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.data = list(map(lambda old: _update(old, item) if old.id == item.id else old, self.data))
self.save() self.save()
self.push_update()
return item 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 ): async def delete(self, id: str ):
item = self._find_by_id(id) item = self._find_by_id(id)
self.data = list(filter(lambda item: item.id != id, self.data)) self.data = list(filter(lambda item: item.id != id, self.data))
self.save() self.save()
self.push_update()
def save(self): def save(self):
data = dict(data=list(map(lambda item: item.to_dict(), self.data)))
with open(self.path, "w") as file: 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): async def create_step(self, id, step: Step):
try: try:
step.id = shortuuid.uuid() step.id = shortuuid.uuid()
item = self._find_by_id(id) 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) item.steps.append(step)
self.save() self.save()
@ -223,10 +292,11 @@ class FermenationController:
if step is None: if step is None:
self.logger.info("No futher step to start") 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: except Exception as e:
self.logger.error(e) self.logger.error(e)
@ -240,6 +310,42 @@ class FermenationController:
except Exception as e: except Exception as e:
self.logger.error(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): async def next(self, id):
self.logger.info("Next {} ".format(id)) self.logger.info("Next {} ".format(id))
@ -284,4 +390,3 @@ class FermenationController:
except Exception as e: except Exception as e:
self.logger.error(e) self.logger.error(e)

View file

@ -32,7 +32,6 @@ class PluginController():
logger.info("Trying to load plugin %s" % filename) logger.info("Trying to load plugin %s" % filename)
data = load_config(os.path.join( 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): if (data.get("active") is True and data.get("version") == 4):
self.modules[filename] = import_module( self.modules[filename] = import_module(
"cbpi.extension.%s" % (filename)) "cbpi.extension.%s" % (filename))
@ -75,12 +74,18 @@ class PluginController():
if issubclass(clazz, CBPiKettleLogic): if issubclass(clazz, CBPiKettleLogic):
self.cbpi.kettle.types[name] = self._parse_step_props(clazz, name) 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): if issubclass(clazz, CBPiSensor):
self.cbpi.sensor.types[name] = self._parse_step_props(clazz, name) self.cbpi.sensor.types[name] = self._parse_step_props(clazz, name)
if issubclass(clazz, CBPiStep): if issubclass(clazz, CBPiStep):
self.cbpi.step.types[name] = self._parse_step_props(clazz, name) 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): if issubclass(clazz, CBPiExtension):
self.c = clazz(self.cbpi) self.c = clazz(self.cbpi)
@ -100,6 +105,8 @@ class PluginController():
return {"label": p.label, "type": "sensor", "configurable": p.configurable, "description": p.description} return {"label": p.label, "type": "sensor", "configurable": p.configurable, "description": p.description}
elif isinstance(p, Property.Kettle): elif isinstance(p, Property.Kettle):
return {"label": p.label, "type": "kettle", "configurable": p.configurable, "description": p.description} 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): def _parse_step_props(self, cls, name):
@ -160,6 +167,11 @@ class PluginController():
result["properties"].append( result["properties"].append(
{"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, {"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable,
"description": t.description}) "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(): for method_name, method in cls.__dict__.items():
if hasattr(method, "action"): if hasattr(method, "action"):

View file

@ -72,7 +72,7 @@ class StepController:
self._loop.create_task(self.start_step(active_step)) self._loop.create_task(self.start_step(active_step))
async def add(self, item: Step): async def add(self, item: Step):
logging.debug("Add step") logging.info("Add step")
item.id = shortuuid.uuid() item.id = shortuuid.uuid()
item.status = StepState.INITIAL item.status = StepState.INITIAL
try: try:
@ -258,6 +258,8 @@ class StepController:
self.cbpi.ws.send(dict(topic="step_update", data=list(map(lambda item: item.to_dict(), self.profile)))) 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))) 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): async def start_step(self,step):
try: try:

View file

@ -264,7 +264,7 @@ class UploadController:
"props": { "props": {
"AutoMode": self.AutoMode, "AutoMode": self.AutoMode,
"Kettle": self.boilid, "Kettle": self.boilid,
"Sensor": self.kettle.sensor, "Sensor": self.boilkettle.sensor,
"Temp": int(self.BoilTemp), "Temp": int(self.BoilTemp),
"Timer": BoilTime, "Timer": BoilTime,
"First_Wort": FirstWort, "First_Wort": FirstWort,
@ -393,7 +393,7 @@ class UploadController:
step_type = self.boil if self.boil != "" else "BoilStep" step_type = self.boil if self.boil != "" else "BoilStep"
step_time = str(int(boil_time)) step_time = str(int(boil_time))
step_temp = self.BoilTemp step_temp = self.BoilTemp
sensor = self.kettle.sensor sensor = self.boilkettle.sensor
LidAlert = "Yes" LidAlert = "Yes"
step_string = { "name": "Boil Step", step_string = { "name": "Boil Step",
@ -584,7 +584,7 @@ class UploadController:
step_time = str(int(BoilTime)) step_time = str(int(BoilTime))
step_type = self.boil if self.boil != "" else "BoilStep" step_type = self.boil if self.boil != "" else "BoilStep"
step_temp = self.BoilTemp step_temp = self.BoilTemp
sensor = self.kettle.sensor sensor = self.boilkettle.sensor
LidAlert = "Yes" LidAlert = "Yes"
step_string = { "name": "Boil Step", step_string = { "name": "Boil Step",
@ -697,7 +697,7 @@ class UploadController:
if step_type == "CooldownStep": if step_type == "CooldownStep":
cooldown_sensor = self.cbpi.config.get("steps_cooldown_sensor", None) cooldown_sensor = self.cbpi.config.get("steps_cooldown_sensor", None)
if cooldown_sensor is None or cooldown_sensor == '': 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_timer = ""
step_temp = int(self.CoolDownTemp) step_temp = int(self.CoolDownTemp)
step_string = { "name": "Cooldown", step_string = { "name": "Cooldown",
@ -744,7 +744,7 @@ class UploadController:
try: try:
self.boilkettle = self.cbpi.kettle.find_by_id(self.boilid) self.boilkettle = self.cbpi.kettle.find_by_id(self.boilid)
except: except:
pass self.boilkettle = self.kettle
config_values = { "kettle": self.kettle, config_values = { "kettle": self.kettle,
"kettle_id": str(self.id), "kettle_id": str(self.id),

View file

@ -26,6 +26,7 @@ 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.upload_controller import UploadController from cbpi.controller.upload_controller import UploadController
from cbpi.controller.fermentation_controller import FermentationController
from cbpi.controller.system_controller import SystemController from cbpi.controller.system_controller import SystemController
from cbpi.controller.satellite_controller import SatelliteController 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_log import LogHttpEndpoints
from cbpi.http_endpoints.http_notification import NotificationHttpEndpoints from cbpi.http_endpoints.http_notification import NotificationHttpEndpoints
from cbpi.http_endpoints.http_upload import UploadHttpEndpoints from cbpi.http_endpoints.http_upload import UploadHttpEndpoints
from cbpi.http_endpoints.http_fermentation import FermentationHttpEndpoints
import shortuuid import shortuuid
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -115,8 +117,9 @@ class CraftBeerPi:
self.satellite = None self.satellite = None
if str(self.static_config.get("mqtt", False)).lower() == "true": if str(self.static_config.get("mqtt", False)).lower() == "true":
self.satellite: SatelliteController = SatelliteController(self) self.satellite: SatelliteController = SatelliteController(self)
self.dashboard = DashboardController(self) self.dashboard = DashboardController(self)
self.fermenter : FermentationController = FermentationController(self)
self.http_step = StepHttpEndpoints(self) self.http_step = StepHttpEndpoints(self)
self.http_recipe = RecipeHttpEndpoints(self) self.http_recipe = RecipeHttpEndpoints(self)
self.http_sensor = SensorHttpEndpoints(self) self.http_sensor = SensorHttpEndpoints(self)
@ -129,7 +132,7 @@ class CraftBeerPi:
self.http_log = LogHttpEndpoints(self) self.http_log = LogHttpEndpoints(self)
self.http_notification = NotificationHttpEndpoints(self) self.http_notification = NotificationHttpEndpoints(self)
self.http_upload = UploadHttpEndpoints(self) self.http_upload = UploadHttpEndpoints(self)
self.http_fermenter = FermentationHttpEndpoints(self)
self.login = Login(self) self.login = Login(self)
@ -279,7 +282,7 @@ class CraftBeerPi:
await self.kettle.init() await self.kettle.init()
await self.call_initializer(self.app) await self.call_initializer(self.app)
await self.dashboard.init() await self.dashboard.init()
await self.fermenter.init()
self._swagger_setup() self._swagger_setup()

View file

@ -1,9 +1,10 @@
import os, threading, time import os, threading, time, shutil
from aiohttp import web from aiohttp import web
import logging import logging
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import asyncio import asyncio
import random import random
import json
from cbpi.api import * from cbpi.api import *
from cbpi.api.config import ConfigType from cbpi.api.config import ConfigType
from cbpi.api.base import CBPiBase from cbpi.api.base import CBPiBase

View 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)

View file

@ -0,0 +1,3 @@
name: FermentationStep
version: 4
active: true

View 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)

View file

@ -0,0 +1,3 @@
name: FermenterHysteresis
version: 4
active: true

View file

@ -319,7 +319,7 @@ class ActorStep(CBPiStep):
Property.Number(label="Temp", description="Boil temperature", configurable=True), Property.Number(label="Temp", description="Boil temperature", configurable=True),
Property.Sensor(label="Sensor"), Property.Sensor(label="Sensor"),
Property.Kettle(label="Kettle"), 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(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.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)"), Property.Number("Hop_1", configurable = True, description="First Hop alert (minutes before finish)"),

View file

@ -9,7 +9,7 @@ class MQTTActor(CBPiActor):
# Custom property which can be configured by the user # Custom property which can be configured by the user
@action("Set Power", parameters=[Property.Number(label="Power", configurable=True, description="Power Setting [0-100]")]) @action("Set Power", parameters=[Property.Number(label="Power", configurable=True, description="Power Setting [0-100]")])
async def setpower(self,Power = 100 ,**kwargs): async def setpower(self,Power = 100 ,**kwargs):
self.power=round(Power) self.power=int(Power)
if self.power < 0: if self.power < 0:
self.power = 0 self.power = 0
if self.power > 100: if self.power > 100:

View 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)

View file

@ -29,6 +29,7 @@ class SystemHttpEndpoints:
""" """
return web.json_response(data=dict( return web.json_response(data=dict(
actor=self.cbpi.actor.get_state(), actor=self.cbpi.actor.get_state(),
fermenter=self.cbpi.fermenter.get_state(),
sensor=self.cbpi.sensor.get_state(), sensor=self.cbpi.sensor.get_state(),
kettle=self.cbpi.kettle.get_state(), kettle=self.cbpi.kettle.get_state(),
step=self.cbpi.step.get_state(), step=self.cbpi.step.get_state(),