"mash profiles added"

This commit is contained in:
Manuel Fritsch 2021-02-10 07:38:55 +01:00
parent ab14111787
commit bd311edccd
19 changed files with 422 additions and 157 deletions

View file

@ -1 +1 @@
__version__ = "4.0.0.17"
__version__ = "4.0.0.18"

View file

@ -14,7 +14,8 @@ __all__ = ["CBPiActor",
"SensorException",
"ActorException",
"CBPiSensor",
"CBPiStep"]
"CBPiStep",
"Stop_Reason"]
from cbpi.api.actor import *
from cbpi.api.sensor import *

64
cbpi/api/base.py Normal file
View file

@ -0,0 +1,64 @@
from abc import abstractmethod, ABCMeta
import asyncio
from cbpi.api.config import ConfigType
import time
import logging
class CBPiBase(metaclass=ABCMeta):
def get_static_config_value(self,name,default):
return self.cbpi.static_config.get(name, default)
def get_config_value(self,name,default):
return self.cbpi.config.get(name, default=default)
async def set_config_value(self,name,value):
return await self.cbpi.config.set(name,value)
async def add_config_value(self, name, value, type: ConfigType, description, options=None):
await self.cbpi.add(name, value, type, description, options=None)
def get_kettle(self,id):
return self.cbpi.kettle.find_by_id(id)
async def set_target_temp(self,id, temp):
await self.cbpi.kettle.set_target_temp(id, temp)
def get_sensor(self,id):
return self.cbpi.sensor.find_by_id(id)
def get_sensor_value(self,id):
return self.cbpi.sensor.get_sensor_value(id)
def get_actor(self,id):
return self.cbpi.actor.find_by_id(id)
def get_actor_state(self,id):
try:
actor = self.cbpi.actor.find_by_id(id)
return actor.get("instance").get_state()
except:
logging.error("Faild to read actor state in step - actor {}".format(id))
return None
async def actor_on(self,id):
try:
print("\n\n ON\n\n\n\n" )
await self.cbpi.actor.on(id)
except Exception as e:
pass
async def actor_off(self,id):
try:
print("\n\n OFF\n\n\n\n" )
await self.cbpi.actor.off(id)
except Exception as e:
print("E", e)
pass

View file

@ -50,3 +50,5 @@ class CBPiExtension():
except:
logger.warning("Faild to load config %s/config.yaml" % path)

View file

@ -3,9 +3,9 @@ from abc import abstractmethod, ABCMeta
from cbpi.api.extension import CBPiExtension
from cbpi.api.config import ConfigType
from cbpi.api.base import CBPiBase
class CBPiSensor(metaclass=ABCMeta):
class CBPiSensor(CBPiBase, metaclass=ABCMeta):
def __init__(self, cbpi, id, props):
self.cbpi = cbpi
@ -35,18 +35,6 @@ class CBPiSensor(metaclass=ABCMeta):
def get_unit(self):
pass
def get_static_config_value(self,name,default):
return self.cbpi.static_config.get(name, default)
def get_config_value(self,name,default):
return self.cbpi.config.get(name, default=default)
async def set_config_value(self,name,value):
return await self.cbpi.config.set(name,value)
async def add_config_value(self, name, value, type: ConfigType, description, options=None):
await self.cbpi.add(name, value, type, description, options=None)
def push_update(self, value):
try:
self.cbpi.ws.send(dict(topic="sensorstate", id=self.id, value=value))
@ -57,5 +45,4 @@ class CBPiSensor(metaclass=ABCMeta):
self.running = True
async def stop(self):
self.running = False

View file

@ -5,8 +5,15 @@ import logging
from abc import abstractmethod, ABCMeta
import logging
from cbpi.api.config import ConfigType
from cbpi.api.base import CBPiBase
from enum import Enum
__all__ = ["Stop_Reason", "CBPiStep"]
class Stop_Reason(Enum):
STOP = 1
NEXT = 2
class CBPiStep(metaclass=ABCMeta):
class CBPiStep(CBPiBase, metaclass=ABCMeta):
def __init__(self, cbpi, id, name, props) :
self.cbpi = cbpi
self.props = {**props}
@ -17,91 +24,61 @@ class CBPiStep(metaclass=ABCMeta):
self.stop_reason = None
self.pause = False
self.task = None
self._task = None
self._exception_count = 0
self._max_exceptions = 2
self.state_msg = "No state"
self.state_msg = ""
def get_state(self):
return self.state_msg
def stop(self):
self.stop_reason = "STOP"
self.running = False
def start(self):
self.running = True
def push_update(self):
self.cbpi.step.push_udpate()
async def stop(self):
self.stop_reason = Stop_Reason.STOP
self._task.cancel()
await self._task
async def start(self):
self.stop_reason = None
def next(self):
self.stop_reason = "NEXT"
self.running = False
self._task = asyncio.create_task(self.run())
self._task.add_done_callback(self.cbpi.step.done)
async def next(self):
self.stop_reason = Stop_Reason.NEXT
self._task.cancel()
async def reset(self):
pass
def on_props_update(self, props):
self.props = props
async def update(self, props):
await self.cbpi.step.update_props(self.id, props)
async def run(self):
while self.running:
try:
await self.execute()
except Exception as e:
self._exception_count += 1
logging.error("Step has thrown exception")
if self._exception_count >= self._max_exceptions:
self.stop_reason = "MAX_EXCEPTIONS"
return (self.id, self.stop_reason)
await asyncio.sleep(1)
try:
while True:
try:
await self.execute()
except asyncio.CancelledError as e:
raise e
except Exception as e:
self._exception_count += 1
logging.error("Step has thrown exception")
if self._exception_count >= self._max_exceptions:
self.stop_reason = "MAX_EXCEPTIONS"
return (self.id, self.stop_reason)
except asyncio.CancelledError as e:
return self.id, self.stop_reason
return (self.id, self.stop_reason)
@abstractmethod
async def execute(self):
pass
def get_static_config_value(self,name,default):
return self.cbpi.static_config.get(name, default)
def get_config_value(self,name,default):
return self.cbpi.config.get(name, default=default)
async def set_config_value(self,name,value):
return await self.cbpi.config.set(name,value)
async def add_config_value(self, name, value, type: ConfigType, description, options=None):
await self.cbpi.add(name, value, type, description, options=None)
def get_kettle(self,id):
return self.cbpi.kettle.find_by_id(id)
async def set_target_temp(self,id, temp):
await self.cbpi.kettle.set_target_temp(id, temp)
def get_sensor(self,id):
return self.cbpi.sensor.find_by_id(id)
def get_actor(self,id):
return self.cbpi.actor.find_by_id(id)
def get_actor_state(self,id):
try:
actor = self.cbpi.actor.find_by_id(id)
return actor.get("instance").get_state()
except:
logging.error("Faild to read actor state in step - actor {}".format(id))
return None
async def actor_on(self,id):
try:
await self.cbpi.actor.on(id)
except:
pass
async def actor_off(self,id):
try:
await self.cbpi.actor.off(id)
except:
pass

59
cbpi/api/timer.py Normal file
View file

@ -0,0 +1,59 @@
import time
import asyncio
import math
class Timer(object):
def __init__(self, timeout, callback, update = None) -> None:
super().__init__()
self.timeout = timeout
self._timemout = self.timeout
self._task = None
self._callback = callback
self._update = update
self.start_time = None
async def _job(self):
self.start_time = time.time()
self.count = int(round(self._timemout, 0))
try:
for seconds in range(self.count, -1, -1):
if self._update is not None:
await self._update(seconds, self.format_time(seconds))
await asyncio.sleep(1)
self._callback()
except asyncio.CancelledError:
end = time.time()
duration = end - self.start_time
self._timemout = self._timemout - duration
def start(self):
self._task = asyncio.create_task(self._job())
async def stop(self):
self._task.cancel()
await self._task
def reset(self):
if self.is_running is True:
return
self._timemout = self.timeout
def is_running(self):
return not self._task.done()
def set_time(self,timeout):
if self.is_running is True:
return
self.timeout = timeout
def get_time(self):
return self.format_time(int(round(self._timemout,0)))
def format_time(self, time):
pattern = '{0:02d}:{1:02d}:{2:02d}'
seconds = time % 60
minutes = math.floor(time / 60) % 60
hours = math.floor(time / 3600)
return pattern.format(hours, minutes, seconds)

View file

@ -12,6 +12,7 @@ class ActorController(BasicController):
item = self.find_by_id(id)
instance = item.get("instance")
await instance.on()
await self.push_udpate()
except Exception as e:
logging.error("Faild to switch on Actor {} {}".format(id, e))
@ -20,6 +21,7 @@ class ActorController(BasicController):
item = self.find_by_id(id)
instance = item.get("instance")
await instance.off()
await self.push_udpate()
except Exception as e:
logging.error("Faild to switch on Actor {} {}".format(id, e))

View file

@ -9,10 +9,16 @@ class SensorController(BasicController):
def create_dict(self, data):
try:
instance = data.get("instance")
state = state=instance.get_state()
state =instance.get_state()
except Exception as e:
logging.error("Faild to create sensor dict {} ".format(e))
state = dict()
return dict(name=data.get("name"), id=data.get("id"), type=data.get("type"), state=state,props=data.get("props", []))
def get_sensor_value(self, id):
try:
return self.find_by_id(id).get("instance").get_state()
except Exception as e:
logging.error("Faild read sensor value {} {} ".format(id, e))
return None

View file

@ -7,7 +7,7 @@ import shortuuid
import logging
import os.path
from ..api.step import CBPiStep
from ..api.step import CBPiStep, Stop_Reason
@ -59,8 +59,15 @@ class StepController:
async def update(self, id, data):
logging.info("update step")
self.profile = list(map(lambda old: {**old, **data} if old["id"] == id else old, self.profile))
def merge_data(id, old, data):
step = {**old, **data}
try:
step["instance"] = self.create_step(id,data["type"], data["name"], data["props"])
except Exception as e:
logging.error("Faild create step instance during update props")
return step
self.profile = list(map(lambda old: {**merge_data(id, old, data)} if old["id"] == id else old, self.profile))
await self.save()
return self.find_by_id(id)
@ -69,7 +76,7 @@ class StepController:
data = dict(basic=self.basic_data, profile=list(map(lambda x: dict(name=x["name"], type=x.get("type"), id=x["id"], status=x["status"],props=x["props"]), self.profile)))
with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True)
await self.push_udpate()
self.push_udpate()
async def start(self):
# already running
@ -88,7 +95,7 @@ class StepController:
step = self.find_by_status("I")
if step is not None:
logging.info("Start Step")
logging.info("####### Start Step")
await self.start_step(step)
await self.save()
@ -102,9 +109,14 @@ class StepController:
if step is not None:
instance = step.get("instance")
if instance is not None:
logging.info("Next")
instance.next()
await instance.task
await instance.next()
step = self.find_by_status("P")
if step is not None:
instance = step.get("instance")
if instance is not None:
step["status"] = "D"
await self.save()
await self.start()
else:
logging.info("No Step is running")
@ -123,9 +135,7 @@ class StepController:
if step != None and step.get("instance") is not None:
logging.info("CALLING STOP STEP")
instance = step.get("instance")
instance.stop()
# wait for task to be finished
await instance.task
await instance.stop()
logging.info("STEP STOPPED")
step["status"] = "P"
await self.save()
@ -139,10 +149,10 @@ class StepController:
logging.info("Reset %s" % item.get("name"))
item["status"] = "I"
await item["instance"].reset()
await self.push_udpate()
self.push_udpate()
def create_step(self, id, type, name, props):
print(id, type, name, props)
try:
type_cfg = self.types.get(type)
clazz = type_cfg.get("class")
@ -169,7 +179,7 @@ class StepController:
return
self.profile[index], self.profile[index+direction] = self.profile[index+direction], self.profile[index]
await self.save()
await self.push_udpate()
self.push_udpate()
async def delete(self, id):
step = self.find_by_id(id)
@ -188,22 +198,23 @@ class StepController:
# Stopping all running task
if instance.task != None and instance.task.done() is False:
logging.info("Stop Step")
instance.stop()
await instance.stop()
await instance.task
await self.save()
def done(self, task):
id, reason = task.result()
print("DONE", id, reason)
if reason == "MAX_EXCEPTIONS":
step_current = self.find_by_id(id)
step_current["status"] = "E"
self._loop.create_task(self.save())
return
if reason == "NEXT":
if reason == Stop_Reason.NEXT:
step_current = self.find_by_status("A")
if step_current is not None:
step_current["status"] = "D"
async def wrapper():
## TODO DONT CALL SAVE
@ -221,25 +232,27 @@ class StepController:
def get_index_by_id(self, id):
return next((i for i, item in enumerate(self.profile) if item["id"] == id), None)
async def push_udpate(self):
def push_udpate(self):
self.cbpi.ws.send(dict(topic="step_update", data=list(map(lambda x: self.create_dict(x), self.profile))))
async def start_step(self,step):
logging.info("Start Step")
step.get("instance").start()
step["instance"].task = self._loop.create_task(step["instance"].run())
step["instance"].task .add_done_callback(self.done)
try:
await step["instance"].start()
except Exception as e:
print(".........",e)
step["status"] = "A"
print("STARTED",step)
async def update_props(self, id, props):
logging.info("SAVE PROPS")
step = self.find_by_id(id)
step["props"] = props
await self.save()
await self.push_udpate()
self.push_udpate()
async def save_basic(self, data):
logging.info("SAVE Basic Data")
self.basic_data = {**self.basic_data, **data,}
await self.save()
await self.push_udpate()
self.push_udpate()

View file

@ -28,13 +28,10 @@ class CustomSensor(CBPiSensor):
print("ACTION!", kwargs)
async def run(self):
while self.running is True:
print(self.get_config_value("TEMP_UNIT", "NONE"))
print(self.get_static_config_value("port", "NONE"))
await self.set_config_value("BREWERY_NAME", "WOOHOO HELLO")
self.value = random.randint(0,50)
self.value = random.randint(0,10)
self.push_update(self.value)
await asyncio.sleep(1)

View file

@ -1,31 +1,168 @@
import asyncio
import time
import random
from cbpi.api.timer import Timer
from cbpi.api import *
import logging
@parameters([Property.Number(label="Timer", configurable=True),
@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")])
class MashStep(CBPiStep):
def __init__(self, cbpi, id, name, props):
super().__init__(cbpi, id, name, props)
self.timer = None
def timer_done(self):
self.state_msg = "Done"
asyncio.create_task(self.next())
async def timer_update(self, seconds, time):
self.state_msg = "{}".format(time)
self.push_update()
def start_timer(self):
if self.timer is None:
self.time = int(self.props.get("Timer", 0))
self.timer = Timer(self.time, self.timer_done, self.timer_update)
self.timer.start()
async def stop_timer(self):
if self.timer is not None:
await self.timer.stop()
self.state_msg = "{}".format(self.timer.get_time())
async def next(self):
if self.timer is not None:
await self.timer.stop()
self.state_msg = ""
await super().next()
async def stop(self):
await super().stop()
await self.stop_timer()
async def reset(self):
self.state_msg = ""
self.timer = None
await super().reset()
async def execute(self):
try:
kid = self.props.get("Kettle", None)
kettle = self.get_kettle(kid)
actor = self.get_actor(kettle.get("heater"))
print(self.get_actor_state(kettle.get("heater")))
await self.cbpi.kettle.set_target_temp(kid, random.randint(0,50))
if self.v is True:
await self.actor_on(kettle.get("heater"))
else:
await self.actor_off(kettle.get("heater"))
self.v = not self.v
except:
pass
if self.timer is None:
self.state_msg = "Waiting for Target Temp"
self.push_update()
else:
if self.timer is not None and self.timer.is_running() is False:
self.start_timer()
sensor_value = 0
while True:
await asyncio.sleep(1)
sensor_value = self.get_sensor_value(self.props.get("Sensor"))
if sensor_value.get("value") >= 2 and self.timer == None:
self.start_timer()
@parameters([Property.Number(label="Timer", description="Time in Minutes", configurable=True)])
class WaitStep(CBPiStep):
def __init__(self, cbpi, id, name, props):
super().__init__(cbpi, id, name, props)
self.timer = None
def timer_done(self):
self.state_msg = "Done"
asyncio.create_task(self.next())
async def timer_update(self, seconds, time):
self.state_msg = "{}".format(time)
self.push_update()
def start_timer(self):
if self.timer is None:
self.time = int(self.props.get("Timer", 0))
self.timer = Timer(self.time, self.timer_done, self.timer_update)
self.timer.start()
async def stop_timer(self):
if self.timer is not None:
await self.timer.stop()
self.state_msg = "{}".format(self.timer.get_time())
async def next(self):
if self.timer is not None:
await self.timer.stop()
self.state_msg = ""
await super().next()
async def stop(self):
await super().stop()
await self.stop_timer()
async def reset(self):
self.state_msg = ""
self.timer = None
await super().reset()
async def execute(self):
self.start_timer()
while True:
await asyncio.sleep(1)
@parameters([Property.Number(label="Timer", description="Time in Seconds", configurable=True),
Property.Actor(label="Actor")])
class ActorStep(CBPiStep):
def __init__(self, cbpi, id, name, props):
super().__init__(cbpi, id, name, props)
self.timer = None
def timer_done(self):
self.state_msg = "Done"
asyncio.create_task(self.actor_off(self.actor_id))
asyncio.create_task(self.next())
async def timer_update(self, seconds, time):
self.state_msg = "{}".format(time)
self.push_update()
def start_timer(self):
if self.timer is None:
self.time = int(self.props.get("Timer", 0))
self.timer = Timer(self.time, self.timer_done, self.timer_update)
self.timer.start()
async def stop_timer(self):
if self.timer is not None:
await self.timer.stop()
self.state_msg = "{}".format(self.timer.get_time())
async def next(self):
if self.timer is not None:
await self.timer.stop()
self.state_msg = ""
await super().next()
async def stop(self):
await super().stop()
await self.actor_off(self.actor_id)
await self.stop_timer()
async def reset(self):
self.state_msg = ""
self.timer = None
await super().reset()
async def execute(self):
self.start_timer()
self.actor_id = self.props.get("Actor")
await self.actor_on(self.actor_id)
while True:
await asyncio.sleep(1)
def setup(cbpi):
'''
This method is called by the server during startup
@ -34,5 +171,8 @@ def setup(cbpi):
:param cbpi: the cbpi core
:return:
'''
cbpi.plugin.register("ActorStep", ActorStep)
cbpi.plugin.register("WaitStep", WaitStep)
cbpi.plugin.register("MashStep", MashStep)

View file

@ -2,7 +2,7 @@
"data": [
{
"id": "YwGzXvWMpmbLb6XobesL8n",
"name": "111",
"name": "Actor 1",
"props": {
"GPIO": 4,
"Inverted": "Yes"
@ -12,7 +12,7 @@
},
{
"id": "EsmZwWi9Qp3bzmXqq7N3Ly",
"name": "HALO",
"name": "Actor 2",
"props": {
"Frequency": "20",
"GPIO": 5

View file

@ -102,18 +102,6 @@
365,
160
],
[
285,
175
],
[
430,
420
],
[
240,
350
],
[
220,
160

View file

@ -3,7 +3,7 @@ version: 4.0
index_url: /cbpi_ui/static/index.html
plugins:
- cbpi4-ui
- cbpi4ui
port: 8080
# login data

View file

@ -8,7 +8,7 @@
"props": {},
"sensor": "8ohkXvFA9UrkHLsxQL38wu",
"state": {},
"target_temp": 25,
"target_temp": 45,
"type": "CustomKettleLogic"
}
]

View file

@ -11,6 +11,15 @@
"value": 0
},
"type": "OneWire"
},
{
"id": "JUGteK9KrSVPDxboWjBS4N",
"name": "Test2",
"props": {},
"state": {
"value": 0
},
"type": "CustomSensor"
}
]
}

View file

@ -4,15 +4,27 @@
},
"profile": [
{
"id": "Gkjdsu45XPcfJimz4yHc4w",
"name": "Test",
"id": "SeL6hT9WxvA5yTsTakZuu8",
"name": "Pump Left",
"props": {
"Kettle": "oHxKz3z5RjbsxfSz6KUgov",
"Temp": "2",
"Timer": "1"
"Actor": "YwGzXvWMpmbLb6XobesL8n",
"Timer": "5"
},
"status": "P",
"type": "MashStep"
"status": "D",
"type": "ActorStep"
},
{
"id": "YwyRyzA2ePiiXXnET5gEeH",
"name": "Pump Right",
"props": {
"Actor": "EsmZwWi9Qp3bzmXqq7N3Ly",
"Kettle": "oHxKz3z5RjbsxfSz6KUgov",
"Sensor": "JUGteK9KrSVPDxboWjBS4N",
"Temp": "2",
"Timer": "5"
},
"status": "D",
"type": "ActorStep"
}
]
}

8
sample.py Normal file
View file

@ -0,0 +1,8 @@
import math
timerTime = 3661
print(format_time(timerTime))