"added dataclassed"

This commit is contained in:
Manuel Fritsch 2021-02-16 20:37:51 +01:00
parent 2468dab6dd
commit 93aea0c063
90 changed files with 1162 additions and 2134 deletions

3
.gitignore vendored
View file

@ -12,3 +12,6 @@ node_modules
.DS_STORE
.vscode
.DS_Store
.vscode/
config/
logs/

View file

@ -1,114 +0,0 @@
# The default ``config.py``
# flake8: noqa
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
'.hg', '.svn', '_svn', '.git', '.tox']
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
# prefs['python_files'] = ['*.py']
# Custom source folders: By default rope searches the project
# for finding source folders (folders that should be searched
# for finding modules). You can add paths to that list. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
# prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
# prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs['save_objectdb'] = True
prefs['compress_objectdb'] = False
# If `True`, rope analyzes each module when it is being saved.
prefs['automatic_soa'] = True
# The depth of calls to follow in static object analysis
prefs['soa_followed_calls'] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs['perform_doa'] = True
# Rope can check the validity of its object DB when running.
prefs['validate_objectdb'] = True
# How many undos to hold?
prefs['max_history_items'] = 32
# Shows whether to save history across sessions.
prefs['save_history'] = True
prefs['compress_history'] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs['indent_size'] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs['extension_modules'] = []
# Add all standard c-extensions to extension_modules list.
prefs['import_dynload_stdmods'] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs['ignore_syntax_errors'] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs['ignore_bad_imports'] = False
# If `True`, rope will insert new module imports as
# `from <package> import <module>` by default.
prefs['prefer_module_from_imports'] = False
# If `True`, rope will transform a comma list of imports into
# multiple separate import statements when organizing
# imports.
prefs['split_imports'] = False
# If `True`, rope will remove all top-level import statements and
# reinsert them at the top of the module when making changes.
prefs['pull_imports_to_top'] = True
# If `True`, rope will sort imports alphabetically by module name instead
# of alphabetically by import statement, with from imports after normal
# imports.
prefs['sort_imports_alphabetically'] = False
# Location of implementation of
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
# case, you don't have to change this value, unless you're an rope expert.
# Change this value to inject you own implementations of interfaces
# listed in module rope.base.oi.type_hinting.providers.interfaces
# For example, you can add you own providers for Django Models, or disable
# the search type-hinting in a class hierarchy, etc.
prefs['type_hinting_factory'] = (
'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!

Binary file not shown.

BIN
cbpi/.DS_Store vendored

Binary file not shown.

View file

@ -1 +1 @@
__version__ = "4.0.0.20"
__version__ = "4.0.0.21"

View file

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

View file

@ -27,18 +27,35 @@ class CBPiActor(metaclass=ABCMeta):
def log_data(self, value):
self.cbpi.log.log_data(self.id, value)
async def run(self):
while self.running:
await asyncio.sleep(1)
def get_state(self):
return dict(state=self.state)
async def start(self):
self.running = True
pass
async def stop(self):
self.running = False
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_static_config_value(self,name,default):
return self.cbpi.static_config.get(name, default)

View file

@ -24,7 +24,7 @@ class CBPiBase(metaclass=ABCMeta):
return self.cbpi.kettle.find_by_id(id)
def get_kettle_target_temp(self,id):
return self.cbpi.kettle.find_by_id(id).get("target_temp")
return self.cbpi.kettle.find_by_id(id).target_temp
async def set_target_temp(self,id, temp):
await self.cbpi.kettle.set_target_temp(id, temp)
@ -33,6 +33,7 @@ class CBPiBase(metaclass=ABCMeta):
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):
@ -46,22 +47,17 @@ class CBPiBase(metaclass=ABCMeta):
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

144
cbpi/api/dataclasses.py Normal file
View file

@ -0,0 +1,144 @@
from cbpi.api.config import ConfigType
from enum import Enum
from typing import Any
from cbpi.api.step import StepState
from dataclasses import dataclass
class Props:
def __init__(self, data={}):
super(Props, self).__setattr__('__data__', {})
for key, value in data.items():
self.__setattr__(key, value)
def __getattr__(self, name):
return self.__data__.get(name)
def __setattr__(self, name, value):
self.__data__[name] = value
def __str__(self):
return self.__data__.__str__()
def __getitem__(self, key):
return self.__data__[key]
def __setitem__(self, key, value):
self.__data__[key] = value
def __contains__(self, key):
return key in self.__data__
def get(self, key, d=None):
if key in self.__data__:
return self.__data__[key]
else:
return d
def to_dict(self):
def parse_object(value):
if isinstance(value, Props):
return value.to_dict()
elif isinstance(value, list):
return list(map(parse_object, value))
else:
return value
return dict((key, parse_object(value)) for (key, value) in self.__data__.items())
@dataclass
class Actor:
id: str = None
name: str = None
props: Props = Props()
state: bool = False
type: str = None
instance: str = None
def __str__(self):
return "name={} props={}, state={}, type={}".format(self.name, self.props, self.state, self.type)
def to_dict(self):
return dict(id=self.id, name=self.name, type=self.type, props=self.props.to_dict(), state=self.instance.get_state())
@dataclass
class Sensor:
id: str = None
name: str = None
props: Props = Props()
state: bool = False
type: str = None
instance: str = None
def __str__(self):
return "name={} props={}, state={}".format(self.name, self.props, self.state)
def to_dict(self):
return dict(id=self.id, name=self.name, type=self.type, props=self.props.to_dict(), state=self.state)
@dataclass
class Kettle:
id: str = None
name: str = None
props: Props = Props()
instance: str = None
agitator: Actor = None
heater: Actor = None
sensor: Sensor = None
type: str = None
target_temp: int = 0
def __str__(self):
return "name={} props={} temp={}".format(self.name, self.props, self.target_temp)
def to_dict(self):
if self.instance is not None:
state = self.instance.state
print("READ STATE", state)
else:
state = False
return dict(id=self.id, name=self.name, state=state, target_temp=self.target_temp, heater=self.heater, agitator=self.agitator, sensor=self.sensor, type=self.type, props=self.props.to_dict())
@dataclass
class Step:
id: str = None
name: str = None
props: Props = Props()
type: str = None
status: StepState = StepState.INITIAL
instance: str = None
def __str__(self):
return "name={} props={}, type={}, instance={}".format(self.name, self.props, self.type, self.instance)
def to_dict(self):
msg = self.instance.summary if self.instance is not None else ""
return dict(id=self.id, name=self.name, state_text=msg, type=self.type, status=self.status.value, props=self.props.to_dict())
class ConfigType(Enum):
STRING="string"
ACTOR="actor"
SENSOR="sensor"
KETTLE="kettle"
NUMBER="number"
SELECT="select"
@dataclass
class Config:
name: str = None
value: Any = None
description: str = None
type: ConfigType = ConfigType.STRING
options: Any = None
def __str__(self):
return "....name={} value={}".format(self.name, self.value)
def to_dict(self):
return dict(name=self.name, value=self.value, type=self.type.value, description=self.description, options=self.options)

View file

@ -18,18 +18,34 @@ class CBPiKettleLogic(CBPiBase, metaclass=ABCMeta):
def init(self):
pass
async def on_start(self):
pass
async def on_stop(self):
pass
async def run(self):
self.state = True
while self.running:
await asyncio.sleep(1)
self.state = False
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.running)
return dict(running=self.state)
async def start(self):
self.running = True
self.state = True
async def stop(self):
self.task.cancel()
await self.task
self.state = False

View file

@ -1,8 +1,9 @@
import asyncio
import logging
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(CBPiBase, metaclass=ABCMeta):
@ -22,10 +23,6 @@ class CBPiSensor(CBPiBase, metaclass=ABCMeta):
def log_data(self, value):
self.cbpi.log.log_data(self.id, value)
@abstractmethod
async def run(self):
self.logger.warning("Sensor Init not implemented")
def get_state(self):
pass
@ -42,7 +39,26 @@ class CBPiSensor(CBPiBase, metaclass=ABCMeta):
logging.error("Faild to push sensor update")
async def start(self):
self.running = True
pass
async def stop(self):
self.running = False
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()

View file

@ -1,84 +1,99 @@
import json
import time
import asyncio
import json
import logging
from abc import abstractmethod, ABCMeta
import logging
from cbpi.api.config import ConfigType
from cbpi.api.base import CBPiBase
import time
from abc import ABCMeta, abstractmethod
from enum import Enum
__all__ = ["Stop_Reason", "CBPiStep"]
class Stop_Reason(Enum):
STOP = 1
NEXT = 2
from cbpi.api.base import CBPiBase
from cbpi.api.config import ConfigType
class CBPiStep(CBPiBase, metaclass=ABCMeta):
def __init__(self, cbpi, id, name, props) :
self.cbpi = cbpi
self.props = {**props}
self.id = id
__all__ = ["StepResult", "StepState", "StepMove", "CBPiStep"]
from enum import Enum
class StepResult(Enum):
STOP=1
NEXT=2
DONE=3
ERROR=4
class StepState(Enum):
INITIAL="I"
DONE="D"
ACTIVE="A"
ERROR="E"
STOP="S"
class StepMove(Enum):
UP=-1
DONW=1
class CBPiStep(CBPiBase):
def __init__(self, cbpi, id, name, props, on_done) -> None:
self.name = name
self.status = 0
self.running = False
self.stop_reason = None
self.pause = False
self.task = None
self._task = None
self._exception_count = 0
self._max_exceptions = 2
self.state_msg = ""
self.cbpi = cbpi
self.id = id
self.timer = None
self._done_callback = on_done
self.props = props
self.cancel_reason: StepResult = None
self.summary = ""
def get_state(self):
return self.state_msg
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
def _done(self, task):
self._done_callback(self, task.result())
async def start(self):
self.stop_reason = None
self._task = asyncio.create_task(self.run())
self._task.add_done_callback(self.cbpi.step.done)
self.task = asyncio.create_task(self._run())
self.task.add_done_callback(self._done)
async def next(self):
self.stop_reason = Stop_Reason.NEXT
self._task.cancel()
self.cancel_reason = StepResult.NEXT
self.task.cancel()
await self.task
async def stop(self):
try:
self.cancel_reason = StepResult.STOP
self.task.cancel()
await self.task
except:
pass
async def reset(self):
pass
async def on_props_update(self, props):
self.props = {**self.props, **props}
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):
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
@abstractmethod
async def execute(self):
async def save_props(self, props):
pass
async def push_update(self):
self.cbpi.step.push_udpate()
async def on_start(self):
pass
async def on_stop(self):
pass
async def _run(self):
try:
await self.on_start()
await self.run()
self.cancel_reason = StepResult.DONE
except asyncio.CancelledError as e:
pass
finally:
await self.on_stop()
return self.cancel_reason
@abstractmethod
async def run(self):
pass
def __str__(self):
return "name={} props={}, type={}".format(self.name, self.props, self.__class__.__name__)

View file

@ -5,35 +5,42 @@ import math
class Timer(object):
def __init__(self, timeout, callback, update = None) -> None:
def __init__(self, timeout, on_done = None, on_update = None) -> None:
super().__init__()
self.timeout = timeout
self._timemout = self.timeout
self._task = None
self._callback = callback
self._update = update
self._callback = on_done
self._update = on_update
self.start_time = None
def done(self, task):
if self._callback is not None:
asyncio.create_task(self._callback(self))
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):
for seconds in range(self.count, 0, -1):
if self._update is not None:
await self._update(seconds, self.format_time(seconds))
await self._update(self,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())
self._task.add_done_callback(self.done)
async def stop(self):
self._task.cancel()
await self._task
if self._task and self._task.done() is False:
self._task.cancel()
await self._task
def reset(self):
if self.is_running is True:
@ -51,7 +58,8 @@ class Timer(object):
def get_time(self):
return self.format_time(int(round(self._timemout,0)))
def format_time(self, time):
@classmethod
def format_time(cls, time):
pattern = '{0:02d}:{1:02d}:{2:02d}'
seconds = time % 60
minutes = math.floor(time / 60) % 60

View file

@ -24,7 +24,6 @@ def create_config_file():
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.yaml")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
print("Config Folder created")
if os.path.exists(os.path.join(".", 'config', "actor.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "actor.json")
@ -46,10 +45,16 @@ def create_config_file():
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
if os.path.exists(os.path.join(".", 'config', "config.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.json")
destfile = os.path.join(".", 'config')
shutil.copy(srcfile, destfile)
if os.path.exists(os.path.join(".", 'config', "dashboard", "cbpi_dashboard_1.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "dashboard", "cbpi_dashboard_1.json")
destfile = os.path.join(".", "config", "dashboard")
shutil.copy(srcfile, destfile)
print("Config Folder created")
def create_home_folder_structure():

View file

@ -1,27 +1,31 @@
from cbpi.controller.basic_controller import BasicController
from cbpi.api.dataclasses import Actor
from cbpi.controller.basic_controller2 import BasicController
import logging
from tabulate import tabulate
class ActorController(BasicController):
def __init__(self, cbpi):
super(ActorController, self).__init__(cbpi, "actor.json")
super(ActorController, self).__init__(cbpi, Actor,"actor.json")
self.update_key = "actorupdate"
async def on(self, id):
try:
item = self.find_by_id(id)
instance = item.get("instance")
await instance.on()
await self.push_udpate()
if item.instance.state is False:
await item.instance.on()
await self.push_udpate()
await self.cbpi.satellite.publish("cbpi/actor/on", "ACTOR ON")
except Exception as e:
logging.error("Faild to switch on Actor {} {}".format(id, e))
async def off(self, id):
try:
item = self.find_by_id(id)
instance = item.get("instance")
await instance.off()
await self.push_udpate()
if item.instance.state is True:
await item.instance.off()
await self.push_udpate()
except Exception as e:
logging.error("Faild to switch on Actor {} {}".format(id, e))
@ -33,12 +37,3 @@ class ActorController(BasicController):
except Exception as e:
logging.error("Faild to switch on Actor {} {}".format(id, e))
def create_dict(self, data):
try:
instance = data.get("instance")
state = state=instance.get_state()
except Exception as e:
logging.error("Faild to create actor 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", []))

View file

@ -2,6 +2,7 @@
import logging
import os.path
import json
from cbpi.api.dataclasses import Actor, Props
import sys, os
import shortuuid
import asyncio
@ -10,7 +11,8 @@ from tabulate import tabulate
class BasicController:
def __init__(self, cbpi, file):
def __init__(self, cbpi, resource, file):
self.resource = resource
self.update_key = ""
self.name = self.__class__.__name__
self.cbpi = cbpi
@ -27,56 +29,56 @@ class BasicController:
async def init(self):
await self.load()
def create(self, data):
return self.resource(data.get("id"), data.get("name"), type=data.get("type"), props=Props(data.get("props", {})) )
async def load(self):
logging.info("{} Load ".format(self.name))
with open(self.path) as json_file:
data = json.load(json_file)
self.data = data["data"]
for i in data["data"]:
self.data.append(self.create(i))
if self.autostart is True:
for d in self.data:
for item in self.data:
logging.info("{} Starting ".format(self.name))
await self.start(d.get("id"))
await self.start(item.id)
await self.push_udpate()
async def save(self):
logging.info("{} Save ".format(self.name))
data = dict(data=list(map(lambda x: self.create_dict(x), self.data)))
data = dict(data=list(map(lambda actor: actor.to_dict(), self.data)))
with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True)
await self.push_udpate()
async def push_udpate(self):
self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda x: self.create_dict(x), self.data))))
def create_dict(self, data):
return dict(name=data.get("name"), id=data.get("id"), type=data.get("type"), status=data.get("status"),props=data.get("props", []))
self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))))
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)
def get_index_by_id(self, id):
return next((i for i, item in enumerate(self.data) if item["id"] == id), None)
return next((i for i, item in enumerate(self.data) if item.id == id), None)
async def shutdown(self, app):
logging.info("{} Shutdown ".format(self.name))
tasks = []
for item in self.data:
if item.get("instance") is not None and item.get("instance").running is True:
await item.get("instance").stop()
tasks.append(item.get("instance").task)
if item.instance is not None and item.instance.running is True:
item.instance.task.cancel()
tasks.append(item.instance.task)
await asyncio.gather(*tasks)
await self.save()
async def stop(self, id):
logging.info("{} Stop Id {} ".format(self.name, id))
try:
print("STOP NOW")
item = self.find_by_id(id)
instance = item.get("instance")
await instance.stop()
print("STOP ACTION")
await instance.task
print("STOP ACTION", instance)
await item.instance.stop()
await self.push_udpate()
except Exception as e:
logging.error("{} Cant stop {} - {}".format(self.name, id, e))
@ -85,18 +87,21 @@ class BasicController:
logging.info("{} Start Id {} ".format(self.name, id))
try:
item = self.find_by_id(id)
instance = item.get("instance")
if instance is not None and instance.running is True:
if item.instance is not None and item.instance.state is True:
logging.warning("{} already running {}".format(self.name, id))
return
if item.type is None:
logging.warning("{} No Type {}".format(self.name, id))
return
clazz = self.types[item.type]["class"]
item.instance = clazz(self.cbpi, item.id, item.props)
await item.instance.start()
item.instance.task = self._loop.create_task(item.instance._run())
type = item["type"]
clazz = self.types[type]["class"]
item["instance"] = clazz(self.cbpi, item["id"], item["props"])
await item["instance"].start()
item["instance"].task = self._loop.create_task(item["instance"].run())
logging.info("{} started {}".format(self.name, id))
await self.push_udpate()
except Exception as e:
logging.error("{} Cant start {} - {}".format(self.name, id, e))
@ -109,40 +114,37 @@ class BasicController:
def get_state(self):
logging.info("{} Get State".format(self.name))
return {"data": list(map(lambda x: self.create_dict(x), self.data)), "types":self.get_types()}
return {"data": list(map(lambda x: x.to_dict(), self.data)), "types":self.get_types()}
async def add(self, data):
async def add(self, item):
logging.info("{} Add".format(self.name))
id = shortuuid.uuid()
item = {**data, "id": id, "instance": None , "name": data.get("name"), "props": data.get("props", {})}
item.id = shortuuid.uuid()
self.data.append(item)
if self.autostart is True:
await self.start(id)
await self.save()
return item
async def update(self, id, data) -> dict:
async def update(self, item):
logging.info("{} Get Update".format(self.name))
await self.stop(id)
self.data = list(map(lambda old: {**old, **data} if old["id"] == id else old, self.data))
await self.stop(item.id)
self.data = list(map(lambda old_item: item if old_item.id == item.id else old_item, self.data))
if self.autostart is True:
await self.start(id)
await self.start(item.id)
await self.save()
return self.find_by_id(id)
return self.find_by_id(item.id)
async def delete(self, id) -> None:
logging.info("{} Delete".format(self.name))
await self.stop(id)
self.data = list(filter(lambda x: x["id"] != id, self.data))
self.data = list(filter(lambda x: x.id != id, self.data))
await self.save()
async def call_action(self, id, action, parameter) -> None:
logging.info("{} call all Action {} {}".format(self.name, id, action))
try:
item = self.find_by_id(id)
instance = item.get("instance")
await instance.__getattribute__(action)(**parameter)
await item.instance.__getattribute__(action)(**parameter)
except Exception as e:
logging.error("{} Faild to call action on {} {} {}".format(self.name, id, action, e))

View file

@ -1,50 +1,58 @@
from cbpi.api.dataclasses import Config
import logging
import os
from cbpi.api.config import ConfigType
from cbpi.database.model import ConfigModel
from cbpi.utils import load_config
import json
class ConfigController:
'''
The main actor controller
'''
model = ConfigModel
def __init__(self, cbpi):
self.cache = {}
self.logger = logging.getLogger(__name__)
self.cbpi = cbpi
self.cbpi.register(self)
self.path = os.path.join(".", 'config', "config.json")
def get_state(self):
return self.cache
result = {}
for key, value in self.cache.items():
result[key] = value.to_dict()
return result
async def init(self):
this_directory = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-2])
self.static = load_config("{}/config/config.yaml".format(this_directory))
items = await self.model.get_all()
for key, value in items.items():
self.cache[value.name] = value
with open(self.path) as json_file:
data = json.load(json_file)
for key, value in data.items():
self.cache[key] = Config(name=value.get("name"), value=value.get("value"), description=value.get("description"), type=ConfigType(value.get("type", "string")), options=value.get("options", None) )
def get(self, name, default=None):
self.logger.debug("GET CONFIG VALUE %s (default %s)" % (name, default))
if name in self.cache and self.cache[name].value is not None:
print("name", self.cache[name].value)
return self.cache[name].value
else:
return default
async def set(self, name, value):
self.logger.debug("SET %s = %s" % (name, value))
if name in self.cache:
self.cache[name].value = value
await self.model.update(**self.cache[name].__dict__)
await self.cbpi.bus.fire(topic="config/%s/update" % name, name=name, value=value)
data = {}
for key, value in self.cache.items():
data[key] = value.to_dict()
with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True)
async def add(self, name, value, type: ConfigType, description, options=None):
await self.model.insert(name=name, value=value, type=type.value, description=description, options=options)
await self.cbpi.bus.fire(topic="config/%s/add" % name, name=name, value=value)
self.cache[name] = Config(name,value,description,type,options)
data = {}
for key, value in self.cache.items():
data[key] = value.to_dict()
with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True)

View file

@ -4,7 +4,7 @@ import os
from os import listdir
from os.path import isfile, join
class DashboardController():
class DashboardController:
def __init__(self, cbpi):

View file

@ -1,39 +1,26 @@
from cbpi.controller.basic_controller import BasicController
from cbpi.api.dataclasses import Kettle, Props
from cbpi.controller.basic_controller2 import BasicController
import logging
from tabulate import tabulate
class KettleController(BasicController):
def __init__(self, cbpi):
super(KettleController, self).__init__(cbpi, "kettle.json")
super(KettleController, self).__init__(cbpi, Kettle, "kettle.json")
self.update_key = "kettleupdate"
self.autostart = False
async def on(self, id):
try:
item = self.find_by_id(id)
instance = item.get("instance")
await instance.start()
await self.push_udpate()
except Exception as e:
logging.error("Faild to switch on KettleLogic {} {}".format(id, e))
async def off(self, id):
try:
item = self.find_by_id(id)
instance = item.get("instance")
await instance.stop()
await self.push_udpate()
except Exception as e:
logging.error("Faild to switch on KettleLogic {} {}".format(id, e))
def create(self, data):
return Kettle(data.get("id"), data.get("name"), type=data.get("type"), props=Props(data.get("props", {})), sensor=data.get("sensor"), heater=data.get("heater"), agitator=data.get("agitator"))
async def toggle(self, id):
try:
item = self.find_by_id(id)
instance = item.get("instance")
if instance is None or instance.running == False:
if item.instance is None or item.instance.state == False:
await self.start(id)
else:
await instance.stop()
await item.instance.stop()
await self.push_udpate()
except Exception as e:
logging.error("Faild to switch on KettleLogic {} {}".format(id, e))
@ -41,16 +28,8 @@ class KettleController(BasicController):
async def set_target_temp(self, id, target_temp):
try:
item = self.find_by_id(id)
item["target_temp"] = target_temp
item.target_temp = target_temp
await self.save()
except Exception as e:
logging.error("Faild to set Target Temp {} {}".format(id, e))
def create_dict(self, data):
try:
instance = data.get("instance")
state = instance.get_state()
except Exception as e:
logging.error("Faild to create KettleLogic dict {} ".format(e))
state = dict()
return dict(name=data.get("name"), id=data.get("id"), type=data.get("type"), sensor=data.get("sensor"), heater=data.get("heater"), agitator=data.get("agitator"), target_temp=data.get("target_temp"), state=state,props=data.get("props", []))

View file

@ -51,7 +51,7 @@ class LogController:
# remove duplicates
names = set(names)
print(names)
result = None
def dateparse(time_in_secs):
@ -92,7 +92,7 @@ class LogController:
else:
data[name] = result.interpolate().tolist()
print(data)
return data

View file

@ -1,21 +0,0 @@
from cbpi.api import *
class NotificationController(object):
'''
This the notification controller
'''
def __init__(self, cbpi):
'''
Initializer
:param cbpi: the cbpi server object
'''
self.cbpi = cbpi
self.cbpi.register(self)
async def notify(self, message, type=None):
self.cbpi.ws.send(dict(topic="notifiaction", type=type, message=message))

View file

@ -41,7 +41,7 @@ class PluginController():
"Plugin %s is not supporting version 4" % filename)
except Exception as e:
print(e)
logger.error(e)
def load_plugins_from_evn(self):

View file

@ -0,0 +1,74 @@
import asyncio
from asyncio_mqtt import Client, MqttError, Will
from contextlib import AsyncExitStack, asynccontextmanager
class SatelliteController:
def __init__(self, cbpi):
self.cbpi = cbpi
self.client = None
async def init(self):
asyncio.create_task(self.init_client(self.cbpi))
async def publish(self, topic, message):
print("MQTT ON")
await self.client.publish(topic, message, qos=1)
async def handle_message(self, messages):
async for message in messages:
print("FILTERED", message.payload.decode())
async def handle_unfilterd_message(self, messages):
async for message in messages:
print("UNFILTERED", message.payload.decode())
async def init_client(self, cbpi):
async def log_messages(messages, template):
async for message in messages:
print(template.format(message.payload.decode()))
async def cancel_tasks(tasks):
for task in tasks:
if task.done():
continue
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
async with AsyncExitStack() as stack:
tasks = set()
stack.push_async_callback(cancel_tasks, tasks)
self.client = Client("localhost", will=Will(topic="cbpi/diconnect", payload="CBPi Server Disconnected"))
await stack.enter_async_context(self.client)
topic_filters = (
"cbpi/sensor/#",
"cbpi/actor/#"
)
for topic_filter in topic_filters:
# Log all messages that matches the filter
manager = self.client.filtered_messages(topic_filter)
messages = await stack.enter_async_context(manager)
task = asyncio.create_task(self.handle_message(messages))
tasks.add(task)
messages = await stack.enter_async_context(self.client.unfiltered_messages())
task = asyncio.create_task(self.handle_unfilterd_message(messages))
tasks.add(task)
await self.client.subscribe("cbpi/#")
await asyncio.gather(*tasks)

View file

@ -1,9 +1,10 @@
from cbpi.controller.basic_controller import BasicController
from cbpi.api.dataclasses import Sensor
from cbpi.controller.basic_controller2 import BasicController
import logging
class SensorController(BasicController):
def __init__(self, cbpi):
super(SensorController, self).__init__(cbpi, "sensor.json")
super(SensorController, self).__init__(cbpi, Sensor, "sensor.json")
self.update_key = "sensorupdate"
def create_dict(self, data):
@ -18,7 +19,7 @@ class SensorController(BasicController):
def get_sensor_value(self, id):
try:
return self.find_by_id(id).get("instance").get_state()
return self.find_by_id(id).instance.get_state()
except Exception as e:
logging.error("Faild read sensor value {} {} ".format(id, e))
return None

View file

@ -1,21 +1,20 @@
import asyncio
from tabulate import tabulate
import json
import copy
import shortuuid
import json
import logging
import os.path
from ..api.step import CBPiStep, Stop_Reason
import shortuuid
from cbpi.api.dataclasses import Props, Step
from tabulate import tabulate
from ..api.step import StepMove, StepResult, StepState
class StepController:
def __init__(self, cbpi):
self.cbpi = cbpi
self.woohoo = "HALLLO"
self.logger = logging.getLogger(__name__)
self.path = os.path.join(".", 'config', "step_data.json")
self._loop = asyncio.get_event_loop()
@ -29,6 +28,27 @@ class StepController:
logging.info("INIT STEP Controller")
self.load(startActive=True)
def create(self, data):
id = data.get("id")
name = data.get("name")
type = data.get("type")
status = StepState(data.get("status", "I"))
props = data.get("props", {})
try:
type_cfg = self.types.get(type)
clazz = type_cfg.get("class")
instance = clazz(self.cbpi, id, name, Props(props), self.done)
except Exception as e:
logging.warning("Failed to create step instance %s - %s" % (id, e))
instance = None
return Step(id, name, type=type, status=status, instance=instance, props=Props(props) )
def load(self, startActive=False):
# create file if not exists
@ -43,78 +63,86 @@ class StepController:
self.profile = data["profile"]
# Start step after start up
self.profile = list(map(lambda item: {**item, "instance": self.create_step(item.get("id"), item.get("type"), item.get("name"), item.get("props", {}))}, self.profile))
self.profile = list(map(lambda item: self.create(item), self.profile))
if startActive is True:
active_step = self.find_by_status("A")
if active_step is not None:
self._loop.create_task(self.start_step(active_step))
async def add(self, data):
logging.info("Add step")
id = shortuuid.uuid()
item = {**{"status": "I", "props": {}}, **data, "id": id, "instance": self.create_step(id, data.get("type"), data.get("name"), data.get("props", {}))}
async def add(self, item: Step):
logging.debug("Add step")
item.id = shortuuid.uuid()
item.status = StepState.INITIAL
print(item)
try:
type_cfg = self.types.get(item.type)
clazz = type_cfg.get("class")
print("CLASS", clazz)
item.instance = clazz(self.cbpi, item.id, item.name, item.props, self.done)
except Exception as e:
logging.warning("Failed to create step instance %s - %s " % (id, e))
item.instance = None
self.profile.append(item)
await self.save()
return item
async def update(self, id, data):
logging.info("update step")
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
async def update(self, item: Step):
self.profile = list(map(lambda old: {**merge_data(id, old, data)} if old["id"] == id else old, self.profile))
logging.info("update step")
try:
type_cfg = self.types.get(item.type)
clazz = type_cfg.get("class")
item.instance = clazz(self.cbpi, item.id, item.name, item.props, self.done)
except Exception as e:
logging.warning("Failed to create step instance %s - %s " % (item.id, e))
item.instance = None
self.profile = list(map(lambda old: item if old.id == item.id else old, self.profile))
await self.save()
return self.find_by_id(id)
return item
async def save(self):
logging.debug("save profile")
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)))
data = dict(basic=self.basic_data, profile=list(map(lambda item: item.to_dict(), self.profile)))
with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True)
self.push_udpate()
async def start(self):
# already running
if self.find_by_status("A") is not None:
if self.find_by_status(StepState.ACTIVE) is not None:
logging.error("Steps already running")
return
# Find next inactive step
step = self.find_by_status("P")
if step is not None:
step = self.find_by_status(StepState.STOP)
if step is not None:
logging.info("Resume step")
await self.start_step(step)
await self.save()
return
step = self.find_by_status("I")
step = self.find_by_status(StepState.INITIAL)
if step is not None:
logging.info("####### Start Step")
logging.info("Start Step")
await self.start_step(step)
await self.save()
return
await self.cbpi.notification.notify(message="HALLO")
self.cbpi.notify(message="BREWING COMPLETE")
logging.info("BREWING COMPLETE")
async def next(self):
logging.info("Trigger Next")
step = self.find_by_status("A")
step = self.find_by_status(StepState.ACTIVE)
if step is not None:
instance = step.get("instance")
if instance is not None:
await instance.next()
step = self.find_by_status("P")
if step.instance is not None:
await step.instance.next()
step = self.find_by_status(StepState.STOP)
if step is not None:
instance = step.get("instance")
if instance is not None:
step["status"] = "D"
if step.instance is not None:
step.status = StepState.DONE
await self.save()
await self.start()
else:
@ -130,49 +158,41 @@ class StepController:
logging.info("Nothing to resume")
async def stop(self):
logging.info("STOP STEP")
step = self.find_by_status("A")
if step != None and step.get("instance") is not None:
step = self.find_by_status(StepState.ACTIVE)
if step != None:
logging.info("CALLING STOP STEP")
instance = step.get("instance")
await instance.stop()
logging.info("STEP STOPPED")
step["status"] = "P"
await self.save()
try:
await step.instance.stop()
step.status = StepState.STOP
await self.save()
except Exception as e:
logging.error("Failed to stop step - Id: %s" % step.id)
async def reset_all(self):
step = self.find_by_status("A")
if step is not None:
if self.find_by_status(StepState.ACTIVE) is not None:
logging.error("Please stop before reset")
return
for item in self.profile:
logging.info("Reset %s" % item.get("name"))
item["status"] = "I"
await item["instance"].reset()
logging.info("Reset %s" % item)
item.status = StepState.INITIAL
try:
await item.instance.reset()
except:
logging.warning("No Step Instance - Id: %s", item.id)
await self.save()
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")
return clazz(self.cbpi, id, name, {**props})
except:
pass
def create_dict(self, data):
return dict(name=data["name"], id=data["id"], type=data.get("type"), status=data["status"],props=data["props"], state_text=data["instance"].get_state())
def get_types2(self):
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_state(self):
return {"basic": self.basic_data, "profile": list(map(lambda x: self.create_dict(x), self.profile)), "types":self.get_types2()}
return {"basic": self.basic_data, "profile": list(map(lambda item: item.to_dict(), self.profile)), "types":self.get_types()}
async def move(self, id, direction):
async def move(self, id, direction: StepMove):
index = self.get_index_by_id(id)
if direction not in [-1, 1]:
self.logger.error("Cant move. Direction 1 and -1 allowed")
@ -183,18 +203,22 @@ class StepController:
async def delete(self, id):
step = self.find_by_id(id)
if step.get("status") == "A":
if step is None:
logging.error("Cant find step - Nothing deleted - Id: %s", id)
return
if step.status == StepState.ACTIVE:
logging.error("Cant delete active Step %s", id)
return
self.profile = list(filter(lambda x: x["id"] != id, self.profile))
self.profile = list(filter(lambda item: item.id != id, self.profile))
await self.save()
async def shutdown(self, app):
logging.info("Mash Profile Shutdonw")
for p in self.profile:
instance = p.get("instance")
instance = p.instance
# Stopping all running task
if instance.task != None and instance.task.done() is False:
logging.info("Stop Step")
@ -202,54 +226,35 @@ class StepController:
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 == 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
await self.save()
await self.start()
self._loop.create_task(wrapper())
def done(self, step, result):
if result == StepResult.NEXT:
step_current = self.find_by_id(step.id)
step_current.status = StepState.DONE
async def wrapper():
await self.save()
await self.start()
asyncio.create_task(wrapper())
def find_by_status(self, status):
return next((item for item in self.profile if item["status"] == status), None)
return next((item for item in self.profile if item.status == status), None)
def find_by_id(self, id):
return next((item for item in self.profile if item["id"] == id), None)
return next((item for item in self.profile if item.id == id), None)
def get_index_by_id(self, id):
return next((i for i, item in enumerate(self.profile) if item["id"] == id), None)
return next((i for i, item in enumerate(self.profile) if item.id == id), None)
def push_udpate(self):
self.cbpi.ws.send(dict(topic="step_update", data=list(map(lambda x: self.create_dict(x), self.profile))))
self.cbpi.ws.send(dict(topic="step_update", data=list(map(lambda item: item.to_dict(), self.profile))))
async def start_step(self,step):
logging.info("Start Step")
try:
await step["instance"].start()
logging.info("Try to start step %s" % step)
await step.instance.start()
step.status = StepState.ACTIVE
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()
self.push_udpate()
logging.error("Faild to start step %s" % step)
async def save_basic(self, data):
logging.info("SAVE Basic Data")

View file

@ -15,18 +15,7 @@ class SystemController:
async def check_for_update(self, app):
try:
timeout = aiohttp.ClientTimeout(total=1)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post('http://localhost:2202/check', json=dict(version=app["cbpi"].version)) as resp:
if (resp.status == 200):
data = await resp.json()
if data.get("version") != self.cbpi.version:
self.logger.info("Version Check: Newer Version exists")
else:
self.logger.info("Version Check: You are up to date")
except:
self.logger.warning("Version Check: Can't check for update")
pass

View file

@ -1,3 +1,4 @@
import logging
from os import urandom
import os
@ -15,7 +16,6 @@ from cbpi.controller.job_controller import JobController
from cbpi.controller.actor_controller import ActorController
from cbpi.controller.config_controller import ConfigController
from cbpi.controller.kettle_controller import KettleController
from cbpi.controller.notification_controller import NotificationController
from cbpi.controller.plugin_controller import PluginController
from cbpi.controller.sensor_controller import SensorController
from cbpi.controller.step_controller import StepController
@ -23,7 +23,7 @@ from cbpi.controller.step_controller import StepController
from cbpi.controller.system_controller import SystemController
from cbpi.controller.log_file_controller import LogController
from cbpi.database.model import DBModel
from cbpi.eventbus import CBPiEventBus
from cbpi.http_endpoints.http_login import Login
from cbpi.utils import *
@ -73,7 +73,7 @@ class CraftBeerPi:
self.version = __version__
self.static_config = load_config(os.path.join(".", 'config', "config.yaml"))
print(self.path, self.static_config)
self.database_file = os.path.join(".", 'config', "craftbeerpi.db")
logger.info("Init CraftBeerPI")
@ -96,8 +96,8 @@ class CraftBeerPi:
self.log = LogController(self)
self.system = SystemController(self)
self.kettle = KettleController(self)
self.step = StepController(self)
self.step : StepController = StepController(self)
#self.satellite: SatelliteController = SatelliteController(self)
self.dashboard = DashboardController(self)
self.http_step = StepHttpEndpoints(self)
@ -108,7 +108,6 @@ class CraftBeerPi:
self.http_dashboard = DashBoardHttpEndpoints(self)
self.http_plugin = PluginHttpEndpoints(self)
self.http_system = SystemHttpEndpoints(self)
self.notification = NotificationController(self)
self.http_log = LogHttpEndpoints(self)
self.login = Login(self)
@ -205,7 +204,10 @@ class CraftBeerPi:
api_version=self.version,
contact="info@craftbeerpi.com")
def notify(self, key: str, message: str, type: str = "info") -> None:
def notify(self, message: str, type: str = "info") -> None:
'''
This is a convinience method to send notification to the client
@ -214,7 +216,7 @@ class CraftBeerPi:
:param type: notification type (info,warning,danger,successs)
:return:
'''
self.bus.sync_fire(topic="notification/%s" % key, key=key, message=message, type=type)
self.ws.send(dict(topic="notifiaction", type=type, message=message))
async def call_initializer(self, app):
self.initializer = sorted(self.initializer, key=lambda k: k['order'])
@ -247,17 +249,19 @@ class CraftBeerPi:
self._print_logo()
await self.job.init()
await DBModel.setup()
await self.config.init()
self._setup_http_index()
self.plugin.load_plugins()
self.plugin.load_plugins_from_evn()
await self.sensor.init()
await self.step.init()
await self.actor.init()
await self.kettle.init()
await self.call_initializer(self.app)
await self.dashboard.init()
#await self.satellite.init()
self._swagger_setup()
return self.app

View file

@ -1,219 +0,0 @@
import json
import aiosqlite
from cbpi.database.orm_framework import DBModel
DATABASE_FILE = "./craftbeerpi.db"
class ActorModel(DBModel):
__fields__ = ["name", "type", "config"]
__table_name__ = "actor"
__json_fields__ = ["config"]
__validation_schema__ = {
'id': int,
'name': str,
'type': str,
'config': dict
}
def to_json(self):
data = dict(**self.__dict__)
if hasattr(self,"instance"):
data["state"] = self.instance.get_state()
del data["instance"]
return data
class SensorModel(DBModel):
__fields__ = ["name", "type", "config"]
__table_name__ = "sensor"
__json_fields__ = ["config"]
__validation_schema__ = {
'id': int,
'name': str,
'type': str,
'config': dict
}
def to_json(self):
data = dict(**self.__dict__)
if hasattr(self,"instance"):
data["value"] = self.instance.get_value()
data["unit"] = self.instance.get_unit()
data["state"] = self.instance.get_state()
del data["instance"]
return data
class ConfigModel(DBModel):
__fields__ = ["type", "value", "description", "options"]
__table_name__ = "config"
__json_fields__ = ["options"]
__priamry_key__ = "name"
__order_by__ = "name"
class KettleModel(DBModel):
__fields__ = ["name", "sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"]
__table_name__ = "kettle"
__json_fields__ = ["config"]
class StepModel(DBModel):
__fields__ = ["order", "name", "type", "stepstate", "state", "start", "end", "config", "kettleid"]
__table_name__ = "step"
__json_fields__ = ["config", "stepstate"]
@classmethod
async def update_step_state(cls, step_id, state):
async with aiosqlite.connect(DATABASE_FILE) as db:
cursor = await db.execute("UPDATE %s SET stepstate = ? WHERE id = ?" % cls.__table_name__, (json.dumps(state), step_id))
await db.commit()
@classmethod
async def update_state(cls, step_id, state, end=None):
async with aiosqlite.connect(DATABASE_FILE) as db:
if end is not None:
await db.execute("UPDATE %s SET state = ?, end = ? WHERE id = ?" % cls.__table_name__, (state, end, step_id))
else:
await db.execute("UPDATE %s SET state = ? WHERE id = ?" % cls.__table_name__, (state, step_id))
await db.commit()
@classmethod
async def get_by_state(cls, state, order=True):
async with aiosqlite.connect(DATABASE_FILE) as db:
db.row_factory = aiosqlite.Row
db.row_factory = DBModel.dict_factory
async with db.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__, cls.__table_name__,), state) as cursor:
row = await cursor.fetchone()
if row is not None:
return cls(row)
else:
return None
@classmethod
async def reset_all_steps(cls):
async with aiosqlite.connect(DATABASE_FILE) as db:
await db.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__)
await db.commit()
@classmethod
async def sort(cls, new_order):
async with aiosqlite.connect(DATABASE_FILE) as db:
for key, value in new_order.items():
await db.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (value, key))
await db.commit()
@classmethod
async def get_max_order(cls):
async with aiosqlite.connect(DATABASE_FILE) as db:
db.row_factory = aiosqlite.Row
db.row_factory = DBModel.dict_factory
async with db.execute("SELECT max(step.'order') as 'order' FROM %s" % cls.__table_name__) as cursor:
row = await cursor.fetchone()
if row is not None:
return row.get("order")
else:
return 0
def to_json(self):
data = dict(**self.__dict__)
if hasattr(self,"instance"):
data["state_msg"] = self.instance.get_status()
del data["instance"]
return data
def __str__(self):
return "%s, %s, %s, %s, %s" % (self.name, self.start, self.end, self.state, self.order)
def __repr__(self) -> str:
return "Steps(%s, %s, %s, %s, %s)" % (self.name, self.start, self.end, self.state, self.order)
class DashboardModel(DBModel):
__fields__ = ["name"]
__table_name__ = "dashboard"
__json_fields__ = []
class DashboardContentModel(DBModel):
__fields__ = ["dbid", "type", "element_id", "x", "y","config"]
__table_name__ = "dashboard_content"
__json_fields__ = ["config"]
__validation_schema__ = {
'dbid': int,
'element_id': int,
'type': str,
'x': int,
'y': int,
'config': dict
}
@classmethod
async def get_by_dashboard_id(cls, id, as_array=False):
result = []
async with aiosqlite.connect(DATABASE_FILE) as db:
db.row_factory = DBModel.dict_factory
async with db.execute("SELECT * FROM %s WHERE dbid = ?" % (cls.__table_name__), (id,)) as cursor:
async for row in cursor:
result.append(cls(row))
await cursor.close()
return result
@classmethod
async def update_coordinates(cls, id, x, y):
async with aiosqlite.connect(DATABASE_FILE) as db:
await db.execute("UPDATE %s SET x = ?, y = ? WHERE id = ?" % (cls.__table_name__), (x, y, id,))
await db.commit()
@classmethod
async def delete_by_dashboard_id(cls, id):
async with aiosqlite.connect(DATABASE_FILE) as db:
await db.execute("DELETE FROM %s WHERE dbid = ?" % (cls.__table_name__), (id,))
await db.commit()
class TranslationModel(DBModel):
__fields__ = ["key", "text", "language_code"]
__table_name__ = "translation"
__json_fields__ = []
__priamry_key__ = "key"
@classmethod
async def get_all(cls):
result = {}
async with aiosqlite.connect(DATABASE_FILE) as db:
sql = "SELECT * FROM %s" % cls.__table_name__
db.row_factory = DBModel.dict_factory
async with db.execute(sql) as cursor:
async for row in cursor:
code = row.get("language_code")
key = row.get("key")
text = row.get("text")
if code not in result:
result[code] = {}
result[code][key] = text
await cursor.close()
return result
@classmethod
async def add_key(cls, locale, key):
async with aiosqlite.connect(DATABASE_FILE) as db:
await db.execute("INSERT INTO %s (language_code, key, text) VALUES (?,?, ' ')" % (cls.__table_name__), (locale, key))
await db.commit()

View file

@ -1,174 +0,0 @@
import json
import aiosqlite
import os
from cbpi.api import *
from voluptuous import MultipleInvalid, Schema
DATABASE_FILE = "./craftbeerpi.db"
class DBModel(object):
__priamry_key__ = "id"
__as_array__ = False
__order_by__ = None
__json_fields__ = []
__validation_schema__ = None
def __init__(self, args):
self.__setattr__(self.__priamry_key__, args[self.__priamry_key__])
for f in self.__fields__:
if f in self.__json_fields__:
if args.get(f) is not None:
if isinstance(args[f], dict) or isinstance(args[f], list):
self.__setattr__(f, args.get(f))
else:
self.__setattr__(f, json.loads(args.get(f, "{}")))
else:
self.__setattr__(f, None)
else:
self.__setattr__(f, args.get(f))
@classmethod
async def setup(self):
async with aiosqlite.connect(DATABASE_FILE) as db:
assert isinstance(db, aiosqlite.Connection)
this_directory = os.path.dirname(__file__)
qry = open(os.path.join(this_directory, "../config/create_database.sql"), 'r').read()
cursor = await db.executescript(qry)
@classmethod
def validate(cls, data):
if cls.__validation_schema__ is not None:
try:
schema = Schema(cls.__validation_schema__)
schema(data)
except MultipleInvalid as e:
raise CBPiException(str(e))
@classmethod
async def get_all(cls):
if cls.__as_array__ is True:
result = []
else:
result = {}
async with aiosqlite.connect(DATABASE_FILE) as db:
if cls.__order_by__ is not None:
sql = "SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__, cls.__table_name__, cls.__order_by__)
else:
sql = "SELECT * FROM %s" % cls.__table_name__
db.row_factory = DBModel.dict_factory
async with db.execute(sql) as cursor:
async for row in cursor:
if cls.__as_array__ is True:
result.append(cls(row))
else:
result[row.get(cls.__priamry_key__)] = cls(row)
await cursor.close()
return result
@classmethod
async def get_one(cls, id):
async with aiosqlite.connect(DATABASE_FILE) as db:
db.row_factory = aiosqlite.Row
db.row_factory = DBModel.dict_factory
async with db.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,)) as cursor:
row = await cursor.fetchone()
if row is not None:
return cls(row)
else:
return None
@classmethod
async def delete(cls, id):
async with aiosqlite.connect(DATABASE_FILE) as db:
await db.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,))
await db.commit()
@classmethod
async def delete_all(cls):
async with aiosqlite.connect(DATABASE_FILE) as db:
await db.execute("DELETE FROM %s" % cls.__table_name__)
await db.commit()
@classmethod
async def insert(cls, **kwargs):
cls.validate(kwargs)
async with aiosqlite.connect(DATABASE_FILE) as db:
if cls.__priamry_key__ is not None and cls.__priamry_key__ in kwargs:
query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % (
cls.__table_name__,
cls.__priamry_key__,
', '.join("'%s'" % str(x) for x in cls.__fields__),
', '.join(['?'] * len(cls.__fields__)))
data = ()
data = data + (kwargs.get(cls.__priamry_key__),)
for f in cls.__fields__:
if f in cls.__json_fields__:
data = data + (json.dumps(kwargs.get(f)),)
else:
data = data + (kwargs.get(f),)
else:
query = 'INSERT INTO %s (%s) VALUES (%s)' % (
cls.__table_name__,
', '.join("'%s'" % str(x) for x in cls.__fields__),
', '.join(['?'] * len(cls.__fields__)))
data = ()
for f in cls.__fields__:
if f in cls.__json_fields__:
data = data + (json.dumps(kwargs.get(f)),)
else:
data = data + (kwargs.get(f),)
cursor = await db.execute(query, data)
await db.commit()
i = cursor.lastrowid
kwargs["id"] = i
return cls(kwargs)
@classmethod
async def update(cls, **kwargs):
print("UPDATE")
async with aiosqlite.connect(DATABASE_FILE) as db:
query = 'UPDATE %s SET %s WHERE %s = ?' % (cls.__table_name__, ', '.join("'%s' = ?" % str(x) for x in cls.__fields__), cls.__priamry_key__)
data = ()
for f in cls.__fields__:
if f in cls.__json_fields__:
data = data + (json.dumps(kwargs.get(f)),)
else:
data = data + (kwargs.get(f),)
data = data + (kwargs.get(cls.__priamry_key__),)
print(query)
cursor = await db.execute(query, data)
await db.commit()
return cls(kwargs)
@classmethod
def dict_factory(cls, cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def to_json(self):
return self.__dict__

View file

@ -1,51 +0,0 @@
import os
from cbpi.api import *
from cbpi.controller.crud_controller import CRUDController
from cbpi.database.orm_framework import DBModel
from cbpi.http_endpoints.http_curd_endpoints import HttpCrudEndpoints
class DummyModel(DBModel):
'''
Cumstom Data Model which will is stored in the database
'''
__fields__ = ["name"]
__table_name__ = "dummy"
class MyComp(CBPiExtension, CRUDController, HttpCrudEndpoints):
model = DummyModel
def __init__(self, cbpi):
'''
Initializer
:param cbpi:
'''
self.cbpi = cbpi
# register component for http, events
# In addtion the sub folder static is exposed to access static content via http
self.cbpi.register(self, "/dummy", static=os.path.join(os.path.dirname(__file__), "static"))
@on_event(topic="actor/#")
async def listen(self, **kwargs):
# Listen for all actor events
pass
@on_event(topic="kettle/+/automatic")
async def listen2(self, **kwargs):
# listen for all kettle events which are switching the automatic logic
pass
def setup(cbpi):
'''
Setup method is invoked during startup
:param cbpi: the cbpi core object
:return:
'''
# regsiter the component to the core
cbpi.plugin.register("MyComp", MyComp)
pass

View file

@ -1,2 +0,0 @@
name: DummyComponent
version: 4

View file

@ -1 +0,0 @@
HALLO WELT INDEX

View file

@ -1 +0,0 @@
HALLO WELT INDEX2

View file

@ -1,120 +0,0 @@
# -*- coding: utf-8 -*-
import asyncio
import threading
import time
from aiohttp import web
from cbpi.api import *
import re
import random
def getSensors():
try:
arr = []
for dirname in os.listdir('/sys/bus/w1/devices'):
if (dirname.startswith("28") or dirname.startswith("10")):
cbpi.app.logger.info("Device %s Found (Family: 28/10, Thermometer on GPIO4 (w1))" % dirname)
arr.append(dirname)
return arr
except:
return []
class myThread (threading.Thread):
value = 0
def __init__(self, sensor_name):
threading.Thread.__init__(self)
self.value = 0
self.sensor_name = sensor_name
self.runnig = True
def shutdown(self):
pass
def stop(self):
self.runnig = False
def run(self):
while self.runnig:
try:
app.logger.info("READ TEMP")
## Test Mode
if self.sensor_name is None:
return
with open('/sys/bus/w1/devices/w1_bus_master1/%s/w1_slave' % self.sensor_name, 'r') as content_file:
content = content_file.read()
if (content.split('\n')[0].split(' ')[11] == "YES"):
temp = float(content.split("=")[-1]) / 1000 # temp in Celcius
self.value = temp
except:
pass
self.value = random.randint(1,100)
time.sleep(4)
class DS18B20(CBPiSensor):
sensor_name = Property.Select("Sensor", getSensors(), description="The OneWire sensor address.")
offset = Property.Number("Offset", True, 0, description="Offset which is added to the received sensor data. Positive and negative values are both allowed.")
interval = Property.Number(label="interval", configurable=True)
# Internal runtime variable
value = 0
def init(self):
super().init()
self.state = True
self.t = myThread(self.sensor_name)
def shudown():
shudown.cb.shutdown()
shudown.cb = self.t
self.t.start()
def get_state(self):
return self.state
def get_value(self):
return self.value
def get_unit(self):
return "°%s" % self.get_parameter("TEMP_UNIT", "C")
def stop(self):
try:
self.t.stop()
except:
pass
async def run(self, cbpi):
self.value = 0
while True:
await asyncio.sleep(self.interval)
self.value = random.randint(1,101)
self.log_data(self.value)
await cbpi.bus.fire("sensor/%s/data" % self.id, value=self.value)
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("DS18B20", DS18B20)

View file

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

View file

@ -12,7 +12,7 @@ class DummyActor(CBPiActor):
# Custom property which can be configured by the user
@action("test", parameters={})
async def action1(self, **kwargs):
print("ACTION !", kwargs)
self.my_name = kwargs.get("name")
pass

View file

@ -1,3 +1,4 @@
import asyncio
import logging
from unittest.mock import MagicMock, patch
@ -26,7 +27,6 @@ if (mode == None):
@parameters([Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]), Property.Select(label="Inverted", options=["Yes", "No"],description="No: Active on high; Yes: Active on low")])
class GPIOActor(CBPiActor):
def get_GPIO_state(self, state):
# ON
if state == 1:
@ -35,14 +35,12 @@ class GPIOActor(CBPiActor):
if state == 0:
return 0 if self.inverted == False else 1
async def start(self):
await super().start()
self.gpio = self.props.get("GPIO")
async def on_start(self):
self.gpio = self.props.GPIO
self.inverted = True if self.props.get("Inverted", "No") == "Yes" else False
GPIO.setup(self.gpio, GPIO.OUT)
GPIO.output(self.gpio, self.get_GPIO_state(0))
self.state = False
pass
async def on(self, power=0):
logger.info("ACTOR %s ON - GPIO %s " % (self.id, self.gpio))
@ -55,11 +53,12 @@ class GPIOActor(CBPiActor):
self.state = False
def get_state(self):
return self.state
async def run(self):
pass
while True:
await asyncio.sleep(1)
@parameters([Property.Select(label="GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]), Property.Number("Frequency", configurable=True)])
class GPIOPWMActor(CBPiActor):
@ -69,7 +68,6 @@ class GPIOPWMActor(CBPiActor):
async def power(self, **kwargs):
self.p.ChangeDutyCycle(1)
async def start(self):
await super().start()
self.gpio = self.props.get("GPIO")
@ -97,7 +95,9 @@ class GPIOPWMActor(CBPiActor):
return self.state
async def run(self):
pass
while True:
await asyncio.sleep(1)
def setup(cbpi):

View file

@ -43,7 +43,7 @@ class HTTPSensor(CBPiSensor):
self.log_data(value)
await cbpi.bus.fire("sensor/%s/data" % self.id, value=value)
except Exception as e:
print(e)
pass
class HTTPSensorEndpoint(CBPiExtension):
@ -96,7 +96,7 @@ class HTTPSensorEndpoint(CBPiExtension):
if self.pattern_check.match(value) is None:
return web.json_response(status=422, data={'error': "Data not matching pattern ^[a-zA-Z0-9,.]{0,10}$"})
print("HTTP SENSOR ", key, value)
cache[key] = value
return web.Response(status=204)

View file

@ -7,25 +7,24 @@ from cbpi.api import *
Property.Number(label="OffsetOff", configurable=True, description="Offset below target temp when heater should switched off")])
class Hysteresis(CBPiKettleLogic):
async def stop(self):
self.task.cancel()
await self.task
async def run(self):
try:
self.offset_on = float(self.props.get("OffsetOn", 0))
self.offset_off = float(self.props.get("OffsetOff", 0))
self.kettle = self.get_kettle(self.id)
self.heater = self.kettle.get("heater")
self.heater = self.kettle.heater
logging.info("CustomLogic {} {} {} {}".format(self.offset_on, self.offset_off, self.id, self.heater))
while True:
sensor_value = self.get_sensor_value(self.kettle.get("sensor")).get("value")
target_temp = self.get_kettle_target_temp("oHxKz3z5RjbsxfSz6KUgov")
await self.actor_on(self.heater)
'''
sensor_value = self.get_sensor_value(self.kettle.sensor).get("value")
target_temp = self.get_kettle_target_temp(self.id)
if sensor_value < target_temp - self.offset_on:
await self.actor_on(self.heater)
elif sensor_value >= target_temp - self.offset_off:
await self.actor_off(self.heater)
'''
await asyncio.sleep(1)
except asyncio.CancelledError as e:

View file

@ -1,167 +1,108 @@
import asyncio
from cbpi.api.step import CBPiStep, StepResult
from cbpi.api.timer import Timer
from cbpi.api import *
import logging
@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
async def on_timer_done(self,timer):
self.summary = ""
await self.next()
def timer_done(self):
self.state_msg = "Done"
asyncio.create_task(self.next())
async def on_timer_update(self,timer, seconds):
self.summary = Timer.format_time(seconds)
await self.push_update()
async def timer_update(self, seconds, time):
self.state_msg = "{}".format(time)
self.push_update()
def start_timer(self):
async def on_start(self):
if self.timer is None:
self.time = int(self.props.get("Timer", 0)) * 60
self.timer = Timer(self.time, self.timer_done, self.timer_update)
self.timer.start()
self.timer = Timer(10,on_update=self.on_timer_update, on_done=self.on_timer_done)
async def stop_timer(self):
if self.timer is not None:
await self.timer.stop()
self.state_msg = "{}".format(self.timer.get_time())
self.summary = "Waiting for Target Temp"
await self.push_update()
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 on_stop(self):
await self.timer.stop()
self.summary = ""
await self.push_update()
async def reset(self):
self.state_msg = ""
self.timer = None
await super().reset()
async def execute(self):
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
self.timer = Timer(10,on_update=self.on_timer_update, on_done=self.on_timer_done)
async def run(self):
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:
sensor_value = self.get_sensor_value(self.props.Sensor)
if sensor_value.get("value") >= int(self.props.Temp) and self.timer == None:
self.start_timer()
return StepResult.DONE
@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
async def on_timer_done(self,timer):
self.summary = ""
await self.next()
def timer_done(self):
self.state_msg = "Done"
async def on_timer_update(self,timer, seconds):
self.summary = Timer.format_time(seconds)
await self.push_update()
asyncio.create_task(self.next())
async def timer_update(self, seconds, time):
self.state_msg = "{}".format(time)
self.push_update()
def start_timer(self):
async def on_start(self):
if self.timer is None:
self.time = int(self.props.get("Timer", 0)) * 60
self.timer = Timer(self.time, self.timer_done, self.timer_update)
self.timer = Timer(int(self.props.Timer),on_update=self.on_timer_update, on_done=self.on_timer_done)
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 on_stop(self):
await self.timer.stop()
self.summary = ""
await self.push_update()
async def reset(self):
self.state_msg = ""
self.timer = None
await super().reset()
self.timer = Timer(int(self.props.Timer),on_update=self.on_timer_update, on_done=self.on_timer_done)
async def execute(self):
self.start_timer()
async def run(self):
while True:
await asyncio.sleep(1)
return StepResult.DONE
@parameters([Property.Number(label="Timer", description="Time in Minutes", configurable=True),
Property.Actor(label="Actor")])
class ActorStep(CBPiStep):
async def on_timer_done(self,timer):
self.summary = ""
await self.next()
def __init__(self, cbpi, id, name, props):
super().__init__(cbpi, id, name, props)
self.timer = None
async def on_timer_update(self,timer, seconds):
self.summary = Timer.format_time(seconds)
await self.push_update()
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):
async def on_start(self):
if self.timer is None:
self.time = int(self.props.get("Timer", 0)) * 60
self.timer = Timer(self.time, self.timer_done, self.timer_update)
self.timer = Timer(int(self.props.Timer),on_update=self.on_timer_update, on_done=self.on_timer_done)
self.timer.start()
await self.actor_on(self.props.Actor)
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 on_stop(self):
await self.actor_off(self.props.Actor)
await self.timer.stop()
self.summary = ""
await self.push_update()
async def reset(self):
self.state_msg = ""
self.timer = None
await super().reset()
self.timer = Timer(int(self.props.Timer),on_update=self.on_timer_update, on_done=self.on_timer_done)
async def run(self):
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)
return StepResult.DONE
def setup(cbpi):
'''
@ -172,7 +113,11 @@ def setup(cbpi):
:return:
'''
cbpi.plugin.register("ActorStep", ActorStep)
cbpi.plugin.register("WaitStep", WaitStep)
cbpi.plugin.register("MashStep", MashStep)
cbpi.plugin.register("ActorStep", ActorStep)

View file

@ -0,0 +1,176 @@
import asyncio
from cbpi.api.timer import Timer
from cbpi.api import *
import logging
@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)) * 60
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):
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()
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)) * 60
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 Minutes", 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)) * 60
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.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
Here you need to register your plugins at the server
:param cbpi: the cbpi core
:return:
'''
cbpi.plugin.register("ActorStep", ActorStep)
cbpi.plugin.register("WaitStep", WaitStep)
cbpi.plugin.register("MashStep", MashStep)

View file

@ -22,7 +22,7 @@ class CBPiMqttClient:
await client.subscribe("cbpi/#")
async for message in messages:
await self.cbpi.actor.on("YwGzXvWMpmbLb6XobesL8n")
print(message.topic, message.payload.decode())
async def listen(self, topic, **kwargs):

View file

@ -1,3 +1,3 @@
name: MQTT
version: 4.0
version: 4
active: false

View file

@ -48,6 +48,7 @@ class ReadThread (threading.Thread):
self.value = temp
except:
pass
time.sleep(1)
@parameters([Property.Select(label="Sensor", options=getSensors()), Property.Select(label="Interval", options=[1,5,10,30,60], description="Interval in Seconds")])
@ -70,15 +71,15 @@ class OneWire(CBPiSensor):
async def stop(self):
try:
print("STOP THE SENSOR")
self.t.stop()
self.running = False
except:
pass
async def run(self):
while self.running is True:
while True:
self.value = self.t.value
self.log_data(self.value)
self.push_update(self.value)
await asyncio.sleep(self.interval)

View file

@ -1,3 +1,4 @@
from cbpi.api.dataclasses import Actor, Props
from aiohttp import web
from cbpi.api import *
auth = False
@ -57,9 +58,10 @@ class ActorHttpEndpoints():
description: successful operation
"""
data = await request.json()
response_data = await self.controller.add(data)
actor = Actor(name=data.get("name"), props=Props(data.get("props", {})), type=data.get("type"))
response_data = await self.controller.add(actor)
return web.json_response(data=self.controller.create_dict(response_data))
return web.json_response(data=response_data.to_dict())
@request_mapping(path="/{id}", method="PUT", auth_required=False)
@ -95,7 +97,8 @@ class ActorHttpEndpoints():
"""
id = request.match_info['id']
data = await request.json()
return web.json_response(data=self.controller.create_dict(await self.controller.update(id, data)))
actor = Actor(id=id, name=data.get("name"), props=Props(data.get("props", {})), type=data.get("type"))
return web.json_response(data=(await self.controller.update(actor)).to_dict())
@request_mapping(path="/{id}", method="DELETE", auth_required=False)
async def http_delete_one(self, request):

View file

@ -2,13 +2,13 @@ from aiohttp import web
from cbpi.api import *
from cbpi.utils import json_dumps
from cbpi.http_endpoints.http_curd_endpoints import HttpCrudEndpoints
class ConfigHttpEndpoints(HttpCrudEndpoints):
class ConfigHttpEndpoints:
def __init__(self, cbpi):
super().__init__(cbpi)
self.cbpi = cbpi
self.controller = cbpi.config
self.cbpi.register(self, "/config")

View file

@ -1,49 +0,0 @@
import logging
from aiohttp import web
from cbpi.api import *
from cbpi.utils.utils import json_dumps
class HttpCrudEndpoints():
def __init__(self, cbpi):
self.logger = logging.getLogger(__name__)
self.cbpi = cbpi
@request_mapping(path="/types", auth_required=False)
async def get_types(self, request):
if self.controller.types is not None:
return web.json_response(data=self.controller.types, dumps=json_dumps)
else:
return web.Response(status=404, text="Types not supported by endpoint")
@request_mapping(path="/", auth_required=False)
async def http_get_all(self, request):
return web.json_response(await self.controller.get_all(), dumps=json_dumps)
@request_mapping(path="/{id:\d+}", auth_required=False)
async def http_get_one(self, request):
id = int(request.match_info['id'])
return web.json_response(await self.controller.get_one(id), dumps=json_dumps)
@request_mapping(path="/", method="POST", auth_required=False)
async def http_add(self, request):
data = await request.json()
obj = await self.controller.add(**data)
return web.json_response(obj, dumps=json_dumps)
@request_mapping(path="/{id}", method="PUT", auth_required=False)
async def http_update(self, request):
id = int(request.match_info['id'])
data = await request.json()
obj = await self.controller.update(id, data)
return web.json_response(obj, dumps=json_dumps)
@request_mapping(path="/{id}", method="DELETE", auth_required=False)
async def http_delete_one(self, request):
id = request.match_info['id']
await self.controller.delete(int(id))
return web.Response(status=204)

View file

@ -1,13 +1,13 @@
from aiohttp import web
from cbpi.api import *
from voluptuous import Schema
from cbpi.http_endpoints.http_curd_endpoints import HttpCrudEndpoints
from cbpi.utils import json_dumps
import os
from aiohttp import web
from cbpi.api import *
class DashBoardHttpEndpoints(HttpCrudEndpoints):
from cbpi.utils import json_dumps
from voluptuous import Schema
class DashBoardHttpEndpoints:
def __init__(self, cbpi):
self.cbpi = cbpi

View file

@ -1,3 +1,5 @@
from cbpi.controller.kettle_controller import KettleController
from cbpi.api.dataclasses import Kettle, Props
from aiohttp import web
from cbpi.api import *
@ -7,7 +9,7 @@ class KettleHttpEndpoints():
def __init__(self, cbpi):
self.cbpi = cbpi
self.controller = cbpi.kettle
self.controller : KettleController = cbpi.kettle
self.cbpi.register(self, "/kettle")
@request_mapping(path="/", auth_required=False)
@ -70,9 +72,10 @@ class KettleHttpEndpoints():
description: successful operation
"""
data = await request.json()
response_data = await self.controller.add(data)
return web.json_response(data=self.controller.create_dict(response_data))
kettle = Kettle(name=data.get("name"), sensor=data.get("sensor"), heater=data.get("heater"), agitator=data.get("agitator"), props=Props(data.get("props", {})), type=data.get("type"))
response_data = await self.controller.add(kettle)
return web.json_response(data=response_data.to_dict())
@request_mapping(path="/{id}", method="PUT", auth_required=False)
@ -108,7 +111,8 @@ class KettleHttpEndpoints():
"""
id = request.match_info['id']
data = await request.json()
return web.json_response(data=self.controller.create_dict(await self.controller.update(id, data)))
kettle = Kettle(id=id, name=data.get("name"), sensor=data.get("sensor"), heater=data.get("heater"), agitator=data.get("agitator"), props=Props(data.get("props", {})), type=data.get("type"))
return web.json_response(data=(await self.controller.update(kettle)).to_dict())
@request_mapping(path="/{id}", method="DELETE", auth_required=False)
async def http_delete_one(self, request):

View file

@ -135,7 +135,7 @@ class LogHttpEndpoints:
description: successful operation.
"""
log_name = request.match_info['name']
print(log_name)
data = self.cbpi.log.get_logfile_names(log_name)
return web.json_response(data, dumps=json_dumps)

View file

@ -1,3 +1,4 @@
from cbpi.api.dataclasses import Props, Sensor
from aiohttp import web
from cbpi.api import *
auth = False
@ -57,9 +58,10 @@ class SensorHttpEndpoints():
description: successful operation
"""
data = await request.json()
response_data = await self.controller.add(data)
sensor = Sensor(name=data.get("name"), props=Props(data.get("props", {})), type=data.get("type"))
response_data = await self.controller.add(sensor)
return web.json_response(data=self.controller.create_dict(response_data))
return web.json_response(data=response_data.to_dict())
@request_mapping(path="/{id}", method="PUT", auth_required=False)
@ -95,7 +97,8 @@ class SensorHttpEndpoints():
"""
id = request.match_info['id']
data = await request.json()
return web.json_response(data=self.controller.create_dict(await self.controller.update(id, data)))
sensor = Sensor(id=id, name=data.get("name"), props=Props(data.get("props", {})), type=data.get("type"))
return web.json_response(data=(await self.controller.update(sensor)).to_dict())
@request_mapping(path="/{id}", method="DELETE", auth_required=False)
async def http_delete_one(self, request):

View file

@ -1,3 +1,5 @@
from cbpi.controller.step_controller import StepController
from cbpi.api.dataclasses import Props, Step
from aiohttp import web
from cbpi.api import *
@ -5,7 +7,7 @@ class StepHttpEndpoints():
def __init__(self, cbpi):
self.cbpi = cbpi
self.controller = cbpi.step
self.controller : StepController = cbpi.step
self.cbpi.register(self, "/step2")
def create_dict(self, data):
@ -47,9 +49,14 @@ class StepHttpEndpoints():
description: successful operation
"""
data = await request.json()
result = await self.controller.add(data)
return web.json_response(self.create_dict(result))
step = Step(name=data.get("name"), props=Props(data.get("props", {})), type=data.get("type"))
response_data = await self.controller.add(step)
return web.json_response(data=response_data.to_dict())
@request_mapping(path="/{id}", method="PUT", auth_required=False)
async def http_update(self, request):
@ -73,9 +80,8 @@ class StepHttpEndpoints():
data = await request.json()
id = request.match_info['id']
result = await self.controller.update(id, data)
print("RESULT", result)
return web.json_response(self.create_dict(result))
step = Step(id, data.get("name"), Props(data.get("props", {})), data.get("type"))
return web.json_response((await self.controller.update(step)).to_dict())
@request_mapping(path="/{id}", method="DELETE", auth_required=False)
async def http_delete(self, request):

View file

@ -38,21 +38,21 @@ class CBPiSatellite:
async def websocket_handler(self, request):
print("HALLO SATELLITE")
ws = web.WebSocketResponse()
await ws.prepare(request)
self._clients.add(ws)
try:
peername = request.transport.get_extra_info('peername')
if peername is not None:
print(peername)
host = peername[0]
port = peername[1]
else:
host, port = "Unknowen"
self.logger.info("Client Connected - Host: %s Port: %s - client count: %s " % (host, port, len(self._clients)))
except Exception as e:
print(e)
pass
try:

View file

@ -14,6 +14,6 @@ class ComplexEncoder(JSONEncoder):
else:
raise TypeError()
except Exception as e:
print(e)
pass
return None

View file

@ -13,7 +13,7 @@ def load_config(fname):
data = yaml.load(f, Loader=yaml.FullLoader)
return data
except Exception as e:
print(e)
pass
def json_dumps(obj):

View file

@ -45,14 +45,14 @@ class CBPiWebSocket:
try:
peername = request.transport.get_extra_info('peername')
if peername is not None:
print(peername)
host = peername[0]
port = peername[1]
else:
host, port = "Unknowen"
self.logger.info("Client Connected - Host: %s Port: %s - client count: %s " % (host, port, len(self._clients)))
except Exception as e:
print(e)
pass
try:

View file

@ -1,24 +0,0 @@
{
"data": [
{
"id": "YwGzXvWMpmbLb6XobesL8n",
"name": "Actor 1",
"props": {
"GPIO": 4,
"Inverted": "Yes"
},
"state": false,
"type": "GPIOActor"
},
{
"id": "EsmZwWi9Qp3bzmXqq7N3Ly",
"name": "Actor 2",
"props": {
"Frequency": "20",
"GPIO": 5
},
"state": false,
"type": "GPIOPWMActor"
}
]
}

View file

@ -1,124 +0,0 @@
{
"elements": [
{
"id": "84e6afbd-7ed0-4135-b64e-4ce89568946d",
"name": "Kettle",
"props": {
"heigth": "150",
"width": "100"
},
"type": "Kettle",
"x": 105,
"y": 55
},
{
"id": "bec11478-8056-4577-a095-a5909282b0ac",
"name": "Kettle",
"props": {
"heigth": "150",
"width": "100"
},
"type": "Kettle",
"x": 360,
"y": 60
},
{
"id": "d07d7f84-5bc8-42d5-9ef5-9d5be1ff4584",
"name": "Left",
"props": {
"actor": "YwGzXvWMpmbLb6XobesL8n"
},
"type": "ActorButton",
"x": 510,
"y": 60
},
{
"id": "310d78c3-b3c0-40dc-b2a1-42488787fd46",
"name": "Right",
"props": {
"actor": "EsmZwWi9Qp3bzmXqq7N3Ly"
},
"type": "ActorButton",
"x": 505,
"y": 140
},
{
"id": "3cae292a-f12d-4c9e-8e0e-2fd93a9b253e",
"name": "TargetTemp",
"props": {
"color": "#fff",
"kettle": "oHxKz3z5RjbsxfSz6KUgov",
"size": "30",
"unit": "\u00b0"
},
"type": "TargetTemp",
"x": 135,
"y": 75
},
{
"id": "bb90e1ab-7b2d-4623-8df3-3139f91b7087",
"name": "Steps",
"props": {
"width": "200"
},
"type": "Steps",
"x": 595,
"y": 50
},
{
"id": "9dfc216e-21d7-44af-b10f-cf4158144134",
"name": "KettleControl",
"props": {
"kettle": "oHxKz3z5RjbsxfSz6KUgov",
"orientation": "horizontal"
},
"type": "KettleControl",
"x": 100,
"y": 20
}
],
"pathes": [
{
"condition": {
"left": [
"YwGzXvWMpmbLb6XobesL8n"
],
"right": [
"EsmZwWi9Qp3bzmXqq7N3Ly"
]
},
"coordinates": [
[
215,
90
],
[
360,
90
]
],
"id": "559fb368-bce9-4f9b-a25c-c468ae0cac88"
},
{
"condition": {
"left": [
"YwGzXvWMpmbLb6XobesL8n"
],
"right": [
"EsmZwWi9Qp3bzmXqq7N3Ly"
]
},
"coordinates": [
[
365,
160
],
[
220,
160
]
],
"id": "f0b05e9f-132b-4797-9fb0-1431c0579733"
}
]
}

View file

@ -1,13 +0,0 @@
name: CraftBeerPi
version: 4.0
index_url: /cbpi_ui/static/index.html
plugins:
- cbpi4ui
port: 8080
# login data
username: cbpi
password: 123
ws_push_all: true

View file

@ -1,362 +0,0 @@
{
"elements": [
{
"id": "db0c8199-6935-4c77-989a-28528b6743d7",
"name": "Kettle",
"props": {
"heigth": "150",
"width": "100"
},
"type": "Kettle",
"x": 205,
"y": 155
},
{
"id": "35f8c20b-c801-4cf5-946c-29bcf88a989b",
"name": "Kettle",
"props": {
"heigth": "150",
"width": "100"
},
"type": "Kettle",
"x": 400,
"y": 155
},
{
"id": "e62714ea-52c8-4544-af53-e7711fa3a087",
"name": "Kettle",
"props": {
"heigth": "150",
"width": "100"
},
"type": "Kettle",
"x": 585,
"y": 155
},
{
"id": "d7f576b7-7fa7-4be7-8b31-68fef3b65777",
"name": "Mash",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 210,
"y": 135
},
{
"id": "36db0df9-922c-4cf6-8222-63f1eb34e22a",
"name": "HLT",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 405,
"y": 135
},
{
"id": "7ae6a76b-712f-4d54-a661-7285b8f6d47b",
"name": "Boil",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 590,
"y": 140
},
{
"id": "dfaad0f6-455c-4da6-9c82-789c6b36e046",
"name": "CraftBeerPi Brewery",
"props": {
"color": "#fff",
"size": "26"
},
"type": "Text",
"x": 205,
"y": 75
},
{
"id": "4d2c8dfe-61a9-433d-83a8-72f74d17e7e5",
"name": "Sensor Data",
"props": {
"sensor": "8ohkXvFA9UrkHLsxQL38wu",
"unit": "\u00b0"
},
"type": "Sensor",
"x": 245,
"y": 260
},
{
"id": "13a6b89d-50c7-4efb-b940-ec174e522314",
"name": "Sensor Data",
"props": {
"sensor": "8ohkXvFA9UrkHLsxQL38wu",
"unit": "\u00b0"
},
"type": "Sensor",
"x": 445,
"y": 260
},
{
"id": "8d171952-791d-4f72-bfc9-dac8714b839f",
"name": "Sensor Data",
"props": {
"sensor": "8ohkXvFA9UrkHLsxQL38wu",
"unit": "\u00b0"
},
"type": "Sensor",
"x": 630,
"y": 260
},
{
"id": "3963a344-8223-471f-aee6-5119e69f007f",
"name": "TargetTemp",
"props": {
"color": "#fff",
"kettle": "1",
"size": "12",
"unit": "\u00b0"
},
"type": "TargetTemp",
"x": 215,
"y": 175
},
{
"id": "50333692-e956-4a8e-830f-934cd1d037c4",
"name": "TargetTemp",
"props": {
"color": "#fff",
"kettle": "1",
"size": "12",
"unit": "\u00b0"
},
"type": "TargetTemp",
"x": 410,
"y": 175
},
{
"id": "28860d2d-f326-4375-a972-4e40a07bcf29",
"name": "Target Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 215,
"y": 160
},
{
"id": "2f6129ab-61a5-4080-95d3-8832f3f8d57e",
"name": "Target Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 410,
"y": 160
},
{
"id": "4b3f0ef9-61a8-4be2-9f6f-f954a04a77ce",
"name": "TargetTemp",
"props": {
"color": "#fff",
"kettle": "1",
"size": "12",
"unit": "\u00b0"
},
"type": "TargetTemp",
"x": 595,
"y": 175
},
{
"id": "9fc5f252-7f83-4eb6-89f6-99fd343502b8",
"name": "Target temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 595,
"y": 160
},
{
"id": "3ec3e5d8-f82e-40c1-8c41-cb8286659d3b",
"name": "Led",
"props": {
"actor": "8BLRqagLicCdEBDdc77Sgr"
},
"type": "Led",
"x": 240,
"y": 210
},
{
"id": "2e325539-6ed9-4e0d-b1dc-de860c47a1be",
"name": "Heater",
"props": {
"actor": "8BLRqagLicCdEBDdc77Sgr"
},
"type": "ActorButton",
"x": 120,
"y": 255
},
{
"id": "3c3f81d0-cdfd-4521-a2fe-2f039f17b583",
"name": "Current Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 235,
"y": 280
},
{
"id": "0ac051db-5550-4dac-a8ba-e3c1f131704b",
"name": "Current Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 430,
"y": 280
},
{
"id": "e9c833c2-6c87-4849-9ada-479ec95e79da",
"name": "Current Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 615,
"y": 280
},
{
"id": "3be00e94-4e06-4a6b-9b8d-c832be73386a",
"name": "Led",
"props": {
"actor": "Aifjxmw4QdPfU3XbR6iyis"
},
"type": "Led",
"x": 435,
"y": 210
},
{
"id": "d896b230-8dab-4c33-b73a-1dd74e6de906",
"name": "Led",
"props": {
"actor": 1
},
"type": "Led",
"x": 625,
"y": 215
},
{
"id": "adfe673c-1778-4980-b751-5c613c5c5b76",
"name": "Pump1",
"props": {
"actor": "Aifjxmw4QdPfU3XbR6iyis"
},
"type": "ActorButton",
"x": 360,
"y": 360
},
{
"id": "0e1ea214-9ae0-47c2-902d-cae0947ba8a1",
"name": "Pump2",
"props": {
"actor": "HX2bKdobuANehPggYcynnj"
},
"type": "ActorButton",
"x": 810,
"y": 320
}
],
"pathes": [
{
"condition": [
"8BLRqagLicCdEBDdc77Sgr",
"Aifjxmw4QdPfU3XbR6iyis"
],
"coordinates": [
[
305,
185
],
[
405,
185
]
],
"id": "49e7684e-21a3-4e0b-8e94-60f95abee80f"
},
{
"condition": [
"8BLRqagLicCdEBDdc77Sgr"
],
"coordinates": [
[
400,
275
],
[
305,
275
]
],
"id": "5ba909c1-49a9-46e5-a6d0-1d0350c37aa4"
},
{
"condition": [
"Aifjxmw4QdPfU3XbR6iyis"
],
"coordinates": [
[
255,
300
],
[
255,
350
],
[
555,
350
],
[
555,
200
],
[
585,
200
]
],
"id": "aed2d4d3-b99e-4af5-b8cf-d92d47721be4"
},
{
"condition": [
"HX2bKdobuANehPggYcynnj"
],
"coordinates": [
[
685,
275
],
[
795,
275
],
[
795,
375
]
],
"id": "176fed29-56c2-4534-9cab-8c328d0e138c"
}
]
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.4 KiB

View file

@ -1,75 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<style type="text/css">
.st0{fill:#C1C8D1;}
.st1{fill:#758190;}
.st2{fill:#A8B0BC;}
.st3{fill:#E7E4DD;}
.st4{fill:#8892A0;}
.st5{fill:#4482C3;}
.st6{fill:#95A5A5;}
.st7{fill:#BDC3C7;}
.st8{fill:#285680;}
.st9{fill:none;}
</style>
<g>
<g>
<path class="st0" d="M404.7,322.7c-11.4,0-20.7-9.3-20.7-20.7v-32.5h11.8V302c0,4.9,4,8.9,8.9,8.9s8.9-4,8.9-8.9v-38.5
c0-3.3-2.7-5.9-5.9-5.9h-11.8v-11.8h11.8c9.8,0,17.7,8,17.7,17.7V302C425.4,313.4,416.1,322.7,404.7,322.7z"/>
</g>
<g>
<path class="st0" d="M103,322.7c-11.4,0-20.7-9.3-20.7-20.7v-38.5c0-4.7,1.8-9.2,5.2-12.6c3.4-3.3,7.8-5.2,12.6-5.2h11.8v11.8
h-11.8c-1.6,0-3.1,0.6-4.2,1.7c-1.1,1.1-1.7,2.6-1.7,4.2V302c0,4.9,4,8.9,8.9,8.9c4.9,0,8.9-4,8.9-8.9v-32.5h11.8V302
C123.7,313.4,114.5,322.7,103,322.7z"/>
</g>
<path class="st1" d="M366.3,234H384c6.5,0,11.8,5.3,11.8,11.8v23.7c0,6.5-5.3,11.8-11.8,11.8h-17.7"/>
<path class="st1" d="M141.5,234h-17.7c-6.5,0-11.8,5.3-11.8,11.8v23.7c0,6.5,5.3,11.8,11.8,11.8h17.7"/>
<path class="st0" d="M360.4,216.2c7.8,15.6,11.8,32.7,11.8,50.1v198.3c0,26.1-21.2,47.3-47.3,47.3h-142
c-26.1,0-47.3-21.2-47.3-47.3V266.3c0-17.4,4.1-34.6,11.8-50.1"/>
<path class="st2" d="M372.2,293.1H135.6v-26.8c0-17.4,4.1-34.6,11.8-50.1h213c7.8,15.6,11.8,32.7,11.8,50.1V293.1z"/>
<path class="st1" d="M354.5,216.2H153.3c-9.8,0-17.7-7.9-17.7-17.7c0-9.8,7.9-17.7,17.7-17.7h201.1c9.8,0,17.7,7.9,17.7,17.7
C372.2,208.3,364.3,216.2,354.5,216.2z"/>
<path class="st2" d="M153.3,180.7v-11.8c0-6.5,5.3-11.8,11.8-11.8h177.5c6.5,0,11.8,5.3,11.8,11.8v11.8"/>
<circle class="st1" cx="253.9" cy="281.3" r="29.6"/>
<circle class="st3" cx="253.9" cy="281.3" r="11.8"/>
<path class="st2" d="M372.2,446.9H135.6c-3.3,0-5.9-2.7-5.9-5.9v-23.7c0-3.3,2.7-5.9,5.9-5.9h236.6c3.3,0,5.9,2.7,5.9,5.9V441
C378.1,444.3,375.5,446.9,372.2,446.9z"/>
<path class="st0" d="M316.3,157.1H165.2c-6.5,0-11.8,5.3-11.8,11.8v11.8h167.2C319.6,172.7,318.2,164.8,316.3,157.1z"/>
<path class="st4" d="M135.6,198.5c0,9.8,7.9,17.7,17.7,17.7h168.2c0.2-3.9,0.4-7.9,0.4-11.8c0-8-0.5-15.9-1.4-23.7H153.3
C143.5,180.7,135.6,188.7,135.6,198.5L135.6,198.5z"/>
</g>
<g>
<desc>Created with Sketch.</desc>
<g id="Page-1">
<g id="_x30_23---Valve">
<path id="Shape" class="st5" d="M252.6,133l0-9.6c0,2.3-0.8,4.5-2.3,6.2v0c-1.8,2.1-4.5,3.3-7.3,3.3l-9.6-0.1
c-2.8,0-5.4-1.3-7.2-3.4c-1-1.1-1.7-2.5-2-4c-0.2-0.7-0.3-1.5-0.3-2.3c0,0.8-0.1,1.5-0.3,2.3c-0.3,1.5-1,2.8-2.1,3.9
c-1.8,2.1-4.5,3.3-7.3,3.3l-9.6-0.1c-5.3-0.1-9.6-4.4-9.5-9.7l0.2-43.1c0-2.5,1-5,2.8-6.7c1.8-1.8,4.2-2.8,6.8-2.8l2.4,0l11.3,1
c2.6,1.3,4.5,3.6,5.2,6.4c0.2,0.7,0.3,1.5,0.3,2.2c0-2.5,1-5,2.8-6.7c1.8-1.8,4.2-2.8,6.8-2.7L241,71l3.2,0.2
c6.1,0.3,10.9,5.4,10.9,11.6l-0.2,47.9C254.9,132,253.8,133,252.6,133z"/>
<path id="Shape_1_" class="st6" d="M252.8,82.7c-0.5,0-0.9-0.2-1.3-0.4c-0.6-0.4-0.9-1.1-1-1.8l0-0.1l0-7.3v-2.3
c0-0.9-0.4-1.7-1.2-2.2s-1.7-0.5-2.5,0c-0.8,0.4-1.3,1.3-1.3,2.2c0,0.2,0,0.5-0.1,0.7L241,71c0-0.1-0.1-0.3-0.1-0.4
c0-3.9,3.2-7.1,7.1-7c3.9,0.1,7.1,3.3,7.1,7.1V73l0,7.3C255.1,81.6,254.1,82.7,252.8,82.7z"/>
<path id="Shape_2_" class="st7" d="M257.2,161.7c-1,0-1.9-0.3-2.7-0.9c-1.3-0.9-2-2.4-2.1-4l0.1-23.9c1.3,0,2.3-1,2.3-2.3
l0.2-50.3l0-7.3v-2.3c0-3.9-3.2-7.1-7.1-7.1c-3.9,0-7.1,3.1-7.1,7c0,0.1,0,0.3,0.1,0.4l-7.3-0.5c0-4.1,1.8-8,5-10.8
c3.1-2.7,7.3-3.9,11.4-3.3c7.2,1.2,12.6,7.5,12.4,14.8L262,157c0,1.3-0.5,2.5-1.4,3.4C259.7,161.3,258.5,161.7,257.2,161.7z"/>
<path id="Shape_3_" class="st7" d="M223.7,151.8c-10.6-0.1-19.1-8.7-19.1-19.3l9.6,0.1c0,5.3,4.2,9.6,9.6,9.6
c2.5,0,5-0.9,6.8-2.7c1.8-1.8,2.8-4.2,2.8-6.7l9.6,0.1c0,5.1-2.1,9.9-5.7,13.5C233.7,149.9,228.8,151.9,223.7,151.8z"/>
<path id="Shape_4_" class="st7" d="M228,59.1l-9.4,12.3l-11.3-1l12.1-15.8l0.1-12.7l9.6,0.1l-0.1,14.3
C228.9,57.2,228.6,58.2,228,59.1z"/>
<path id="Shape_5_" class="st6" d="M252.6,133c-0.6,0-1.2-0.2-1.6-0.7s-0.7-1-0.7-1.6v-1.1v0l0.2-49.2l0-0.1l0-7.3
c0-1.3,1-2.3,2.3-2.3c1.3,0,2.3,1,2.3,2.3l0,7.3l-0.2,50.3C254.9,132,253.8,133,252.6,133z"/>
<path id="Shape_6_" class="st8" d="M232.2,41.9l-3.2,0l-9.6-0.1l-3.2,0c-1,0-1.8-0.6-2.2-1.5l-5.1-12c-0.3-0.7-0.3-1.6,0.2-2.2
c0.4-0.7,1.2-1.1,2-1l26.3,0.3c0.8,0,1.6,0.4,2,1.1c0.4,0.7,0.5,1.5,0.2,2.2l-5.2,11.9C234,41.3,233.2,41.9,232.2,41.9z"/>
<path id="Shape_7_" class="st5" d="M241.2,25.2l-3.7,0L211.2,25l-3.7-0.1c-0.6,0-1.2-0.3-1.6-0.7s-0.7-1-0.7-1.6l0.1-12.3
c0-2.5,1-4.8,2.8-6.5s4.1-2.7,6.6-2.6l19.8,0.2c5.1,0.1,9.2,4.2,9.2,9.4L243.5,23C243.5,24.2,242.4,25.2,241.2,25.2z"/>
<path id="Shape_8_" class="st8" d="M222.5,125c-0.6-0.4-1-1.1-1-1.9l0.2-43.1c0-0.9,0.4-1.6,1.1-2.1c0.7-0.4,1.6-0.4,2.3,0
c0.7,0.4,1.1,1.2,1.1,2.1l-0.2,43.1c0,0.9-0.5,1.6-1.3,2.1C224.1,125.6,223.2,125.5,222.5,125z"/>
<path id="Shape_9_" class="st9" d="M244.2,71.3l-3.2-0.2"/>
<polyline id="Shape_10_" class="st9" points="245.7,71.5 245.5,71.4 241,71.1 240.4,71 "/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -1 +0,0 @@
<svg id="Layer_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><switch><foreignObject height="1" requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/" width="1"/><g><g><g><path d="m256 160v-32h-64v32c0 26.51-21.49 48-48 48-26.51 0-48 21.49-48 48v224c0 8.837 7.163 16 16 16h224c8.837 0 16-7.163 16-16v-224c0-26.51-21.49-48-48-48-26.51 0-48-21.49-48-48z" fill="#c8c8c3"/></g><g><path d="m304 208c-26.51 0-48-21.49-48-48v-32h-32v352c0 8.837-7.163 16-16 16h128c8.837 0 16-7.163 16-16v-224c0-26.51-21.49-48-48-48z" fill="#a0a5aa"/></g><g><path d="m96 288h128v192h-128z" fill="#facc0a"/></g><g><path d="m224 288h128v192h-128z" fill="#faa01e"/></g><g><path d="m96 256h128v32h-128z" fill="#f0d7b9"/></g><g><path d="m224 256h128v32h-128z" fill="#f0be8c"/></g><g><path d="m256 112h-64c-4.418 0-8 3.582-8 8 0 4.418 3.582 8 8 8h64c4.418 0 8-3.582 8-8 0-4.418-3.582-8-8-8z" fill="#c8c8c3"/></g><g><path d="m256 112h-32v16h32c4.418 0 8-3.582 8-8s-3.582-8-8-8z" fill="#a0a5aa"/></g><g><path d="m224 144h-24c-4.418 0-8-3.582-8-8v-24h32z" fill="#f0d7b9"/></g><g><path d="m248 144h-24v-32h32v24c0 4.418-3.582 8-8 8z" fill="#f0be8c"/></g><g><path d="m224 96h-24c-4.418 0-8 3.582-8 8v8h32z" fill="#f0be8c"/></g><g><path d="m248 96h-24v16h32v-8c0-4.418-3.582-8-8-8z" fill="#faa01e"/></g><g><circle cx="162.667" cy="450.667" fill="#faa01e" r="16"/></g><g><circle cx="174.667" cy="329.333" fill="#faa01e" r="12"/></g><g><circle cx="200" cy="410.667" fill="#faa01e" r="8"/></g><g><circle cx="301.333" cy="312" fill="#facc0a" r="16"/></g><g><circle cx="289.333" cy="454.667" fill="#facc0a" r="12"/></g><g><circle cx="264" cy="370.667" fill="#facc0a" r="8"/></g><g><path d="m224 48h-24v-24c0-4.418 3.582-8 8-8h16z" fill="#c8c8c3"/></g><g><path d="m248 48h-24v-32h16c4.418 0 8 3.582 8 8z" fill="#a0a5aa"/></g><g><path d="m224 80h-16c-4.418 0-8-3.582-8-8v-24h24z" fill="#facc0a"/></g><g><path d="m240 80h-16v-32h24v24c0 4.418-3.582 8-8 8z" fill="#faa01e"/></g><g><path d="m216 80h16v16h-16z" fill="#737378"/></g><g><path d="m212 22.667c2.209 0 4 1.791 4 4v41.333c0 2.209-1.791 4-4 4-2.209 0-4-1.791-4-4v-41.333c0-2.209 1.791-4 4-4z" fill="#fff"/></g><g><path d="m112 486c-3.313 0-6-2.687-6-6v-224c0-20.953 17.047-38 38-38 31.981 0 58-26.019 58-58v-32c0-3.313 2.687-6 6-6s6 2.687 6 6v32c0 38.598-31.402 70-70 70-14.336 0-26 11.664-26 26v224c0 3.313-2.687 6-6 6z" fill="#fff"/></g><g><circle cx="384" cy="384" fill="#3c915a" r="96"/></g><g><circle cx="384" cy="384" fill="#73c369" r="72"/></g><g><path d="m384 448c-2.946 0-5.333-2.388-5.333-5.333v-5.333c0-2.945 2.388-5.333 5.333-5.333 2.946 0 5.333 2.388 5.333 5.333v5.333c0 2.945-2.387 5.333-5.333 5.333z" fill="#fff"/></g><g><path d="m320 384c0-2.946 2.388-5.333 5.333-5.333h5.333c2.945 0 5.333 2.388 5.333 5.333 0 2.946-2.388 5.333-5.333 5.333h-5.333c-2.945 0-5.333-2.387-5.333-5.333z" fill="#fff"/></g><g><path d="m432 384c0-2.946 2.388-5.333 5.333-5.333h5.333c2.945 0 5.333 2.388 5.333 5.333 0 2.946-2.388 5.333-5.333 5.333h-5.333c-2.945 0-5.333-2.387-5.333-5.333z" fill="#fff"/></g><g><path d="m417.333 421.333c-1.024 0-2.047-.391-2.829-1.171l-34.504-34.505v-60.323c0-2.209 1.791-4 4-4s4 1.791 4 4v57.01l32.162 32.162c1.562 1.563 1.562 4.095 0 5.657-.781.78-1.805 1.17-2.829 1.17z" fill="#dc6e1e"/></g><g><circle cx="384" cy="384" fill="#fff" r="16"/></g><g><circle cx="384" cy="384" fill="#3c915a" r="8"/></g></g></g></switch></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 832.8" style="enable-background:new 0 0 512 832.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#E7D2CB;}
.st1{fill:#C25100;}
.st2{fill:#D8B6A9;}
.st3{fill:#A04708;}
.st4{fill:#7F4216;}
.st5{fill:#F6F1EF;}
</style>
<g>
<g>
<path class="st0" d="M434.7,12.7H308V44h126.6c25.4,0,46,20.6,46,46v63.1c0,2.2-1.8,4-4.1,4H35.4c-2.2,0-4.1-1.8-4.1-4V90
c0-25.4,20.7-46,46-46H204V12.7H77.3C34.7,12.7,0,47.4,0,90v63.1c0,19.5,15.9,35.4,35.4,35.4h441.2c19.5,0,35.4-15.9,35.4-35.4V90
C512,47.4,477.3,12.7,434.7,12.7L434.7,12.7z"/>
</g>
<g>
<path class="st1" d="M193.6,12.7h124.7V44H193.6V12.7z"/>
</g>
<g>
<path class="st2" d="M512,90v63.1c0,19.5-15.9,35.4-35.4,35.4h-31.3c19.5,0,35.4-15.9,35.4-35.4V90c0-0.4,0-0.8,0-1.2
c-0.7-42.1-35.1-76.1-77.3-76.1h31.3C477.3,12.7,512,47.4,512,90z"/>
</g>
<path class="st1" d="M486.4,225.6l-13.7,574.2c-0.9,16.6-14.7,29.6-31.3,29.6H70.5c-16.6,0-30.3-13-31.3-29.6L25.6,225.6
c-0.3-6,4.4-11,10.4-11h175l3.6,15.6c2.2,9.5,10.6,16.2,20.3,16.2h42.2c9.7,0,18.2-6.7,20.3-16.2l3.6-15.6h175
C482,214.5,486.8,219.6,486.4,225.6L486.4,225.6z"/>
<path class="st3" d="M486.4,225.6l-13.7,574.2c-0.9,16.6-14.7,29.6-31.3,29.6h-43.1c16.6,0,30.3-13,31.3-29.6l13.7-574.2
c0.3-6-4.4-11-10.4-11H476C482,214.5,486.8,219.6,486.4,225.6L486.4,225.6z"/>
<path class="st3" d="M307.7,240.4l6-25.9H198.3l6,25.9c2.2,9.5,10.6,16.2,20.3,16.2h62.8C297.1,256.6,305.6,249.9,307.7,240.4
L307.7,240.4z"/>
<path class="st4" d="M313.7,214.5l-6,25.9c-2.2,9.5-10.6,16.2-20.3,16.2h-36.3c9.7,0,18.2-6.7,20.3-16.2l6-25.9L313.7,214.5z"/>
<path class="st5" d="M34.4,170.4l-6.9,33.7c-1.3,6.5,3.6,12.5,10.2,12.5h436.5c6.6,0,11.6-6.1,10.2-12.5l-6.9-33.7
c-3-14.6-15.8-25-30.7-25H65.1C50.3,145.4,37.4,155.8,34.4,170.4L34.4,170.4z"/>
<path class="st0" d="M474.3,216.6h-43.1c6.6,0,11.6-6.1,10.2-12.5l-6.9-33.7c-3-14.6-15.8-25-30.7-25h43.1
c14.9,0,27.7,10.5,30.7,25l6.9,33.7C485.8,210.5,480.9,216.6,474.3,216.6L474.3,216.6z"/>
<g>
<path class="st3" d="M357.6,774.4H154.4c-5.8,0-10.4-4.7-10.4-10.4V369.5c0-5.8,4.7-10.4,10.4-10.4h203.1
c5.8,0,10.4,4.7,10.4,10.4V764C368,769.7,363.3,774.4,357.6,774.4z"/>
<path class="st4" d="M368,369.4v394.5c0,5.8-4.7,10.4-10.4,10.4h-36.3c5.8,0,10.4-4.7,10.4-10.4V369.4c0-5.8-4.7-10.4-10.4-10.4
h36.3C363.3,359,368,363.7,368,369.4L368,369.4z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

View file

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<path style="fill:#566E7F;" d="M496,248H16c-8.836,0-16,7.163-16,16v48c0,8.836,7.164,16,16,16h480c8.837,0,16-7.164,16-16v-48
C512,255.163,504.837,248,496,248z M328,304H184v-32h144V304z"/>
<path style="fill:#B1C3D0;" d="M344,106.174V24c0-8.837-7.163-16-16-16H184c-8.837,0-16,7.163-16,16v82.174
c0,8.823-3.643,17.255-10.068,23.302L32,248h448L354.069,129.476C347.643,123.429,344,114.997,344,106.174z"/>
<g>
<path style="fill:#D0D7DA;" d="M196.696,377.524c-3.586-2.602-8.586-1.797-11.172,1.781c-2.594,3.578-1.797,8.578,1.781,11.172
C195.492,396.414,200,404.055,200,412s-4.508,15.586-12.695,21.523C174.852,442.547,168,454.789,168,468
s6.852,25.453,19.305,34.477c1.422,1.031,3.063,1.523,4.688,1.523c2.477,0,4.922-1.148,6.484-3.305
c2.594-3.578,1.797-8.578-1.781-11.172C188.508,483.586,184,475.945,184,468s4.508-15.586,12.695-21.523
C209.149,437.453,216,425.211,216,412S209.149,386.547,196.696,377.524z"/>
<path style="fill:#D0D7DA;" d="M260.695,377.524c-3.594-2.602-8.586-1.797-11.172,1.781c-2.594,3.578-1.797,8.578,1.781,11.172
C259.492,396.414,264,404.055,264,412s-4.508,15.586-12.695,21.523C238.852,442.547,232,454.789,232,468
s6.852,25.453,19.305,34.477c1.422,1.031,3.063,1.523,4.688,1.523c2.477,0,4.922-1.148,6.484-3.305
c2.594-3.578,1.797-8.578-1.781-11.172C252.508,483.586,248,475.945,248,468s4.508-15.586,12.695-21.523
C273.149,437.453,280,425.211,280,412S273.149,386.547,260.695,377.524z"/>
<path style="fill:#D0D7DA;" d="M324.695,446.477C337.149,437.453,344,425.211,344,412s-6.852-25.453-19.305-34.477
c-3.594-2.602-8.594-1.797-11.172,1.781c-2.594,3.578-1.797,8.578,1.781,11.172C323.492,396.414,328,404.055,328,412
s-4.508,15.586-12.695,21.523C302.852,442.547,296,454.789,296,468s6.852,25.453,19.305,34.477
c1.422,1.031,3.063,1.523,4.688,1.523c2.477,0,4.922-1.148,6.484-3.305c2.594-3.578,1.797-8.578-1.781-11.172
C316.508,483.586,312,475.945,312,468S316.508,452.414,324.695,446.477z"/>
</g>
<path style="fill:#3F556B;" d="M48,304c-7.447,0-13.651-5.107-15.435-12c-0.332,1.286-0.565,2.61-0.565,4c0,8.844,7.164,16,16,16
c8.836,0,16-7.156,16-16c0-1.39-0.233-2.714-0.565-4C61.651,298.893,55.447,304,48,304z"/>
<path style="fill:#B1C3D0;" d="M64,288c0,8.844-7.164,16-16,16c-8.836,0-16-7.156-16-16c0-8.843,7.164-16,16-16
C56.836,272,64,279.157,64,288z"/>
<path style="fill:#3F556B;" d="M96,304c-7.447,0-13.651-5.107-15.435-12c-0.332,1.286-0.565,2.61-0.565,4c0,8.844,7.164,16,16,16
c8.836,0,16-7.156,16-16c0-1.39-0.233-2.714-0.565-4C109.651,298.893,103.447,304,96,304z"/>
<path style="fill:#B1C3D0;" d="M112,288c0,8.844-7.164,16-16,16c-8.836,0-16-7.156-16-16c0-8.843,7.164-16,16-16
C104.836,272,112,279.157,112,288z"/>
<rect x="184" y="280" style="fill:#3CB44A;" width="144" height="24"/>
<path style="fill:#8EADBE;" d="M344,106.174V24c0-8.837-7.163-16-16-16h-16v104.446c0,4.968,1.157,9.867,3.378,14.311l35.255,70.51
c7.979,15.958-3.625,34.733-21.466,34.733H49l-17,16h344h24h80L354.069,129.476C347.643,123.429,344,114.997,344,106.174z"/>
<path style="fill:#3F556B;" d="M496,328c8.837,0,16-7.163,16-16v-48c-4.418,0-8,3.582-8,8v32c0,8.837-7.163,16-16,16H24
c-4.418,0-8,3.582-8,8H496z"/>
<rect x="184" y="272" style="fill:#0E9247;" width="144" height="8"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="220"><defs><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="3.5" y1="110.5" x2="147.5" y2="110.5"><stop offset="0" stop-color="#323232"/><stop offset=".357" stop-color="#FFF"/><stop offset=".571" stop-color="#919191"/><stop offset="1" stop-color="#4A4A4A"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="73.868" y1="3.277" x2="77.132" y2="217.723"><stop offset="0" stop-color="#5D5D5D"/><stop offset="1" stop-opacity=".959"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="3.5" y1="101.083" x2="147.5" y2="101.083"><stop offset="0" stop-color="#323232"/><stop offset=".357" stop-color="#FFF"/><stop offset=".571" stop-color="#919191"/><stop offset="1" stop-color="#4A4A4A"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="2.75" y1="110.5" x2="148.25" y2="110.5"><stop offset="0" stop-color="#232323"/><stop offset=".357" stop-color="#5B5B5B"/><stop offset=".571" stop-color="#474747"/><stop offset="1" stop-color="#282828"/></linearGradient><linearGradient id="e" gradientUnits="userSpaceOnUse" x1="219.5" y1="110" x2="223.5" y2="110"><stop offset="0" stop-color="#232323"/><stop offset=".357" stop-color="#5B5B5B"/><stop offset=".571" stop-color="#474747"/><stop offset="1" stop-color="#282828"/></linearGradient></defs><path d="M2.25 159.208c0 4.626 32.571 8.375 72.75 8.375s72.75-3.749 72.75-8.375v49.667c0 4.625-32.571 8.375-72.75 8.375s-72.75-3.75-72.75-8.375v-49.667z" fill="#3B2CD5"/><path d="M75 167.333c-40.179 0-72.75-3.749-72.75-8.375 0-4.625 32.571-8.375 72.75-8.375s72.75 3.75 72.75 8.375c0 4.626-32.571 8.375-72.75 8.375z" fill="#2193FF"/><path d="M75.5 20c-40.179 0-72.75-3.75-72.75-8.375S35.321 3.25 75.5 3.25s72.75 3.75 72.75 8.375S115.679 20 75.5 20zM75.5 217.75c-40.179 0-72.75-3.75-72.75-8.375S35.321 201 75.5 201s72.75 3.75 72.75 8.375-32.571 8.375-72.75 8.375zM2.75 208.604V12.396M148.25 209.375V11.625" stroke="#CDCDCD" fill="none"/><g><path d="M2.75 3.25h145.5v214.5H2.75V3.25z" fill="url(#d)"/><path d="M2.75 3.25h145.5v214.5H2.75V3.25z" stroke="#000" fill="none"/><g><path d="M219.5 108.5h4v3h-4v-3z" fill="url(#e)"/><path d="M219.5 108.5h4v3h-4v-3z" stroke="#000" fill="none"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

View file

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 58 58" version="1.1"><!-- Generator: Sketch 51.3 (57544) - http://www.bohemiancoding.com/sketch --><title>016 - Macerator</title><desc>Created with Sketch.</desc><defs/><g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="016---Macerator" fill-rule="nonzero"><path d="M49,54 C48.4477153,54 48,53.5522847 48,53 L48,39 C48,37.3431458 49.3431458,36 51,36 L54,36 C54.5522847,36 55,36.4477153 55,37 C55,37.5522847 54.5522847,38 54,38 L51,38 C50.4477153,38 50,38.4477153 50,39 L50,53 C50,53.5522847 49.5522847,54 49,54 Z" id="Shape" fill="#606060"/><path d="M55.757,39.689 L53.757,39.189 C53.3120689,39.0775409 53.0000121,38.6776794 53,38.219 L53,35.781 C53.0000121,35.3223206 53.3120689,34.9224591 53.757,34.811 L55.757,34.311 C56.0557824,34.2361524 56.3723864,34.3031708 56.6152132,34.4926655 C56.8580399,34.6821602 56.9999919,34.9729853 57,35.281 L57,38.719 C56.9999919,39.0270147 56.8580399,39.3178398 56.6152132,39.5073345 C56.3723864,39.6968292 56.0557824,39.7638476 55.757,39.689 Z" id="Shape" fill="#285680"/><path d="M44,51 L50,51 L50,54 C50,54.5522847 49.5522847,55 49,55 L44,55 L44,51 Z" id="Shape" fill="#4482C3"/><path d="M40,50 L45,50 C45.5522847,50 46,50.4477153 46,51 L46,55 C46,55.5522847 45.5522847,56 45,56 L40,56 L40,50 Z" id="Shape" fill="#285680"/><polygon id="Shape" fill="#3B97D3" points="56 4 42 47 28.83 49 27.35 48.77 16 47 2 4 27.5 2.11 29 2"/><path d="M42,47 L42,57 C42,57.5522847 41.5522847,58 41,58 L17,58 C16.4477153,58 16,57.5522847 16,57 L16,47 L42,47 Z" id="Shape" fill="#BDC3C7"/><rect id="Rectangle-path" fill="#95A5A5" x="0" y="0" width="58" height="4" rx="1"/><rect id="Rectangle-path" fill="#BDC3C7" x="0" y="0" width="55" height="4" rx="1"/><path d="M20,44 L19,44 C18.4477153,44 18,43.5522847 18,43 C18,42.4477153 18.4477153,42 19,42 L20,42 C20.5522847,42 21,42.4477153 21,43 C21,43.5522847 20.5522847,44 20,44 Z" id="Shape" fill="#67B9CC"/><path d="M39,44 L24,44 C23.4477153,44 23,43.5522847 23,43 C23,42.4477153 23.4477153,42 24,42 L39,42 C39.5522847,42 40,42.4477153 40,43 C40,43.5522847 39.5522847,44 39,44 Z" id="Shape" fill="#67B9CC"/><path d="M39,52 L38,52 C37.4477153,52 37,51.5522847 37,51 C37,50.4477153 37.4477153,50 38,50 L39,50 C39.5522847,50 40,50.4477153 40,51 C40,51.5522847 39.5522847,52 39,52 Z" id="Shape" fill="#ECF0F1"/><path d="M34,52 L19,52 C18.4477153,52 18,51.5522847 18,51 C18,50.4477153 18.4477153,50 19,50 L34,50 C34.5522847,50 35,50.4477153 35,51 C35,51.5522847 34.5522847,52 34,52 Z" id="Shape" fill="#ECF0F1"/><path d="M10,9 L9,9 C8.44771525,9 8,8.55228475 8,8 C8,7.44771525 8.44771525,7 9,7 L10,7 C10.5522847,7 11,7.44771525 11,8 C11,8.55228475 10.5522847,9 10,9 Z" id="Shape" fill="#67B9CC"/><path d="M49,9 L14,9 C13.4477153,9 13,8.55228475 13,8 C13,7.44771525 13.4477153,7 14,7 L49,7 C49.5522847,7 50,7.44771525 50,8 C50,8.55228475 49.5522847,9 49,9 Z" id="Shape" fill="#67B9CC"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 3 KiB

View file

@ -1 +0,0 @@
<svg id="Capa_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g><path d="m76 15h120v241h-120z" fill="#47568c"/><path d="m338.192 420.751h-67.192-67.192l-36.065 69.247h103.257 103.257z" fill="#47568c"/><path d="m338.192 420.751h-67.192v69.247h103.257z" fill="#29376d"/><circle cx="271" cy="256" fill="#61729b" r="195"/><path d="m466 256c0-107.52-87.48-195-195-195v390c107.52 0 195-87.48 195-195z" fill="#47568c"/><circle cx="271" cy="256" fill="#47568c" r="165"/><path d="m436 256c0-90.981-74.019-165-165-165v330c90.981 0 165-74.019 165-165z" fill="#29376d"/><path d="m211 30h-150c-8.291 0-15-6.709-15-15s6.709-15 15-15h150c8.291 0 15 6.709 15 15s-6.709 15-15 15z" fill="#61729b"/><g><path d="m253.832 390.847c-10.474-1.523-21.548-4.482-31.275-8.789-16.699-8.438-26.118-22.837-26.543-39.844-.41-16.875 8.027-31.948 22.573-40.342l29.912-16.802c9.943-5.727 22.5 1.398 22.5 12.993v77.944c.001 8.948-7.874 16.141-17.167 14.84z" fill="#e6f2ff"/></g><g><path d="m288.168 121.153c10.474 1.523 21.548 4.482 31.275 8.789 16.699 8.438 26.118 22.837 26.543 39.844.41 16.875-8.027 31.948-22.573 40.342l-29.913 16.801c-9.943 5.727-22.5-1.398-22.5-12.993v-77.944c0-8.947 7.875-16.14 17.168-14.839z" fill="#cce6ff"/></g><g><path d="m379.769 338.292c-6.556 8.309-14.656 16.42-23.249 22.69-15.657 10.243-32.836 11.201-47.777 3.065-14.819-8.082-23.654-22.926-23.65-39.72l.405-34.306c.012-11.474 12.461-18.787 22.502-12.989l67.502 38.972c7.748 4.474 10.041 14.89 4.267 22.288z" fill="#cce6ff"/></g><g><path d="m162.231 173.708c6.556-8.309 14.656-16.42 23.249-22.69 15.657-10.243 32.836-11.201 47.777-3.065 14.819 8.082 23.654 22.926 23.65 39.72l-.405 34.306c-.012 11.474-12.461 18.787-22.502 12.989l-67.502-38.972c-7.748-4.474-10.041-14.89-4.267-22.288z" fill="#e6f2ff"/></g><g><path d="m396.365 203.444c3.917 9.832 6.892 20.903 8.026 31.479 1.043 18.681-6.718 34.037-21.234 42.909-14.409 8.793-31.682 9.022-46.224.622l-29.507-17.504c-9.931-5.747-10.039-20.185.002-25.982l67.502-38.972c7.748-4.474 17.915-1.251 21.435 7.448z" fill="#cce6ff"/></g><g><path d="m145.635 308.556c-3.917-9.832-6.892-20.902-8.026-31.479-1.043-18.681 6.718-34.037 21.234-42.909 14.409-8.793 31.682-9.022 46.224-.622l29.507 17.504c9.931 5.747 10.039 20.185-.002 25.982l-67.502 38.972c-7.748 4.474-17.915 1.251-21.435-7.448z" fill="#e6f2ff"/></g><path d="m391 482h-120-120c-8.291 0-15 6.709-15 15s6.709 15 15 15h120 120c8.291 0 15-6.709 15-15s-6.709-15-15-15z" fill="#61729b"/><path d="m406 497c0-8.291-6.709-15-15-15h-120v30h120c8.291 0 15-6.709 15-15z" fill="#47568c"/><circle cx="271" cy="256" fill="#9cf" r="45"/><path d="m316 256c0-24.814-20.186-45-45-45v90c24.814 0 45-20.186 45-45z" fill="#80aaff"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="220"><defs><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="3.5" y1="110.5" x2="147.5" y2="110.5"><stop offset="0" stop-color="#232323"/><stop offset=".357" stop-color="#5B5B5B"/><stop offset=".571" stop-color="#474747"/><stop offset="1" stop-color="#282828"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="-38.5" y1="-219.554" x2="-135" y2="-189.801" gradientTransform="rotate(180)"><stop offset="0" stop-color="#232323"/><stop offset=".357" stop-color="#5B5B5B"/><stop offset=".571" stop-color="#474747"/><stop offset="1" stop-color="#282828"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="64" y1="211.25" x2="86.5" y2="211.25"><stop offset="0" stop-color="#232323"/><stop offset=".357" stop-color="#5B5B5B"/><stop offset=".571" stop-color="#474747"/><stop offset="1" stop-color="#282828"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="73.868" y1="3.277" x2="77.132" y2="217.723"><stop offset="0" stop-color="#5D5D5D"/><stop offset="1" stop-opacity=".959"/></linearGradient><linearGradient id="e" gradientUnits="userSpaceOnUse" x1="3.5" y1="101.083" x2="147.5" y2="101.083"><stop offset="0" stop-color="#323232"/><stop offset=".357" stop-color="#FFF"/><stop offset=".571" stop-color="#919191"/><stop offset="1" stop-color="#4A4A4A"/></linearGradient><linearGradient id="f" gradientUnits="userSpaceOnUse" x1="6.25" y1="110" x2="144.75" y2="110"><stop offset="0" stop-color="#232323"/><stop offset=".357" stop-color="#5B5B5B"/><stop offset=".571" stop-color="#474747"/><stop offset="1" stop-color="#282828"/></linearGradient><linearGradient id="g" gradientUnits="userSpaceOnUse" x1="219.5" y1="110" x2="223.5" y2="110"><stop offset="0" stop-color="#232323"/><stop offset=".357" stop-color="#5B5B5B"/><stop offset=".571" stop-color="#474747"/><stop offset="1" stop-color="#282828"/></linearGradient></defs><path d="M135.5 3c6.274.18 10.586 4.113 11.848 10.254h.152v142.873l-36 29.307c-9.065 7.39-18.13 14.78-27.2 22.164V218H66.7v-10.672c-9.028-7.343-18.106-14.627-27.2-21.894l-36-29.307V13.254h.152C4.623 7.127 9.57 3.297 15.5 3h120z" fill="url(#a)"/><path d="M135.5 3c6.274.18 10.586 4.113 11.848 10.254h.152v142.873l-36 29.307c-9.065 7.39-18.13 14.78-27.2 22.164V218H66.7v-10.672c-9.028-7.343-18.106-14.627-27.2-21.894l-36-29.307V13.254h.152C4.623 7.127 9.57 3.297 15.5 3h120z" stroke="#272727" fill="none"/><path d="M147.5 154.579l-36 29.298-36 29.298-36-29.298-36-29.298h72z" fill="url(#b)"/><path d="M147.5 154.579l-36 29.298-36 29.298-36-29.298-36-29.298h72z" stroke="#272727" fill="none"/><g><path d="M64 204.5h22.5V218H64v-13.5z" fill="url(#c)"/><path d="M64 204.5h22.5V218H64v-13.5z" stroke="#272727" fill="none"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1,20 +0,0 @@
{
"data": [
{
"agitator": "YwGzXvWMpmbLb6XobesL8n",
"heater": "EsmZwWi9Qp3bzmXqq7N3Ly",
"id": "oHxKz3z5RjbsxfSz6KUgov",
"name": "MashTun",
"props": {
"OffsetOff": "23",
"OffsetOn": "22"
},
"sensor": "RedQfuxfy4mYe6PwioY95y",
"state": {
"running": false
},
"target_temp": 80,
"type": "Hysteresis"
}
]
}

View file

@ -1,6 +0,0 @@
cbpi4-ui-plugin:
installation_date: '2021-01-06 16:03:31'
version: '0.0.2'
cbpi4-ui:
installation_date: '2021-01-06 16:03:31'
version: '0.0.1'

View file

@ -1,25 +0,0 @@
{
"data": [
{
"id": "RedQfuxfy4mYe6PwioY95y",
"name": "Test",
"props": {
"Interval": 5,
"Sensor": "DEF"
},
"state": {
"value": 0
},
"type": "OneWire"
},
{
"id": "JUGteK9KrSVPDxboWjBS4N",
"name": "Test2",
"props": {},
"state": {
"value": 0
},
"type": "CustomSensor"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

View file

@ -1,30 +0,0 @@
{
"basic": {
"name": "PALE ALE"
},
"profile": [
{
"id": "SeL6hT9WxvA5yTsTakZuu8",
"name": "Pump Left",
"props": {
"Actor": "YwGzXvWMpmbLb6XobesL8n",
"Timer": "5"
},
"status": "P",
"type": "ActorStep"
},
{
"id": "YwyRyzA2ePiiXXnET5gEeH",
"name": "Pump Right",
"props": {
"Actor": "EsmZwWi9Qp3bzmXqq7N3Ly",
"Kettle": "oHxKz3z5RjbsxfSz6KUgov",
"Sensor": "JUGteK9KrSVPDxboWjBS4N",
"Temp": "2",
"Timer": "5"
},
"status": "I",
"type": "ActorStep"
}
]
}

208
sample.py
View file

@ -0,0 +1,208 @@
from abc import abstractmethod
import asyncio
from asyncio import tasks
from cbpi.extension.mashstep import MyStep
from cbpi.controller.step_controller import StepController
from cbpi.extension.gpioactor import GPIOActor
from cbpi.api.dataclasses import Actor, Props, Step
from cbpi.controller.basic_controller2 import BasicController
import time
import math
import json
from dataclasses import dataclass
from unittest.mock import MagicMock, patch
async def main():
cbpi = MagicMock()
cbpi.sensor.get_value.return_value = 99
app = MagicMock()
types = {"GPIOActor":{"name": "GPIOActor", "class": GPIOActor, "properties": [], "actions": []}}
controller = StepController(cbpi)
controller.types = types = {"MyStep":{"name": "MyStep", "class": MyStep, "properties": [], "actions": []}}
controller.load()
await controller.stop()
await controller.reset_all()
await controller.start()
#await controller.start()
await asyncio.sleep(2)
await controller.next()
await asyncio.sleep(2)
if __name__ == "__main__":
asyncio.run(main())
'''
class Timer(object):
def __init__(self, timeout, on_done = None, on_update = None) -> None:
super().__init__()
self.timeout = timeout
self._timemout = self.timeout
self._task = None
self._callback = on_done
self._update = on_update
self.start_time = None
def done(self, task):
if self._callback is not None:
asyncio.create_task(self._callback(self))
async def _job(self):
self.start_time = time.time()
self.count = int(round(self._timemout, 0))
try:
for seconds in range(self.count, 0, -1):
if self._update is not None:
await self._update(self,seconds)
await asyncio.sleep(1)
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())
self._task.add_done_callback(self.done)
async def stop(self):
print(self._task.done())
if self._task.done() is False:
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)))
@classmethod
def format_time(cls, 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)
from enum import Enum
class StepResult(Enum):
STOP=1
NEXT=2
DONE=3
class Step():
def __init__(self, name, props, on_done) -> None:
self.name = name
self.timer = None
self._done_callback = on_done
self.props = props
self.cancel_reason: StepResult = None
def _done(self, task):
print("HALLO")
self._done_callback(self, task.result())
async def start(self):
self.task = asyncio.create_task(self._run())
self.task.add_done_callback(self._done)
async def next(self):
self.cancel_reason = StepResult.NEXT
self.task.cancel()
await self.task
async def stop(self):
self.cancel_reason = StepResult.STOP
self.task.cancel()
await self.task
async def reset(self):
pass
async def on_props_update(self, props):
self.props = {**self.props, **props}
async def save_props(self, props):
pass
async def push_state(self, msg):
pass
async def on_start(self):
pass
async def on_stop(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()
return self.cancel_reason
@abstractmethod
async def run(self):
pass
class MyStep(Step):
async def timer_update(self, timer, seconds):
print(Timer.format_time(seconds))
async def timer_done(self, timer):
print("TIMER DONE")
await self.next()
async def on_start(self):
if self.timer is None:
self.timer = Timer(20, on_done=self.timer_done, on_update=self.timer_update)
self.timer.start()
async def on_stop(self):
await self.timer.stop()
async def run(self):
for i in range(10):
print("RUNNING")
await asyncio.sleep(1)
await self.timer.stop()
return StepResult.DONE
'''