diff --git a/.gitignore b/.gitignore index 11059d9..ccc2f90 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,7 @@ cbpi/extension/ui node_modules .DS_STORE .vscode -.DS_Store \ No newline at end of file +.DS_Store +.vscode/ +config/ +logs/ \ No newline at end of file diff --git a/.vscode/.ropeproject/config.py b/.vscode/.ropeproject/config.py deleted file mode 100644 index dee2d1a..0000000 --- a/.vscode/.ropeproject/config.py +++ /dev/null @@ -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 import ` 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! diff --git a/.vscode/.ropeproject/objectdb b/.vscode/.ropeproject/objectdb deleted file mode 100644 index 0a47446..0000000 Binary files a/.vscode/.ropeproject/objectdb and /dev/null differ diff --git a/cbpi/.DS_Store b/cbpi/.DS_Store index b4f47b7..13c719b 100644 Binary files a/cbpi/.DS_Store and b/cbpi/.DS_Store differ diff --git a/cbpi/__init__.py b/cbpi/__init__.py index ec21018..35d7960 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1 +1 @@ -__version__ = "4.0.0.20" \ No newline at end of file +__version__ = "4.0.0.21" \ No newline at end of file diff --git a/cbpi/api/__init__.py b/cbpi/api/__init__.py index d66629b..c265b9a 100644 --- a/cbpi/api/__init__.py +++ b/cbpi/api/__init__.py @@ -14,8 +14,7 @@ __all__ = ["CBPiActor", "SensorException", "ActorException", "CBPiSensor", - "CBPiStep", - "Stop_Reason"] + "CBPiStep"] from cbpi.api.actor import * from cbpi.api.sensor import * diff --git a/cbpi/api/actor.py b/cbpi/api/actor.py index a26619c..158477a 100644 --- a/cbpi/api/actor.py +++ b/cbpi/api/actor.py @@ -26,19 +26,36 @@ 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) diff --git a/cbpi/api/base.py b/cbpi/api/base.py index d56ef43..6b30398 100644 --- a/cbpi/api/base.py +++ b/cbpi/api/base.py @@ -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 diff --git a/cbpi/api/dataclasses.py b/cbpi/api/dataclasses.py new file mode 100644 index 0000000..ebba864 --- /dev/null +++ b/cbpi/api/dataclasses.py @@ -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) \ No newline at end of file diff --git a/cbpi/api/kettle_logic.py b/cbpi/api/kettle_logic.py index be183a1..7a71568 100644 --- a/cbpi/api/kettle_logic.py +++ b/cbpi/api/kettle_logic.py @@ -17,19 +17,35 @@ 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 diff --git a/cbpi/api/sensor.py b/cbpi/api/sensor.py index 604c9d1..b1211c0 100644 --- a/cbpi/api/sensor.py +++ b/cbpi/api/sensor.py @@ -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 \ No newline at end of file + 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() diff --git a/cbpi/api/step.py b/cbpi/api/step.py index 823b244..e42a456 100644 --- a/cbpi/api/step.py +++ b/cbpi/api/step.py @@ -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) - - async def next(self): - self.stop_reason = Stop_Reason.NEXT - self._task.cancel() + self.task = asyncio.create_task(self._run()) + self.task.add_done_callback(self._done) - async def reset(self): - pass - - + async def next(self): + self.cancel_reason = StepResult.NEXT + self.task.cancel() + await self.task - 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): + async def stop(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): - pass - + 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} + + 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__) diff --git a/cbpi/api/timer.py b/cbpi/api/timer.py index 9929a61..0d3bc21 100644 --- a/cbpi/api/timer.py +++ b/cbpi/api/timer.py @@ -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,9 +58,10 @@ 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 hours = math.floor(time / 3600) - return pattern.format(hours, minutes, seconds) + return pattern.format(hours, minutes, seconds) \ No newline at end of file diff --git a/cbpi/cli.py b/cbpi/cli.py index 6af1a11..f9851b2 100644 --- a/cbpi/cli.py +++ b/cbpi/cli.py @@ -24,8 +24,7 @@ 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") destfile = os.path.join(".", 'config') @@ -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(): diff --git a/cbpi/controller/actor_controller.py b/cbpi/controller/actor_controller.py index f5752f4..fe5abba 100644 --- a/cbpi/controller/actor_controller.py +++ b/cbpi/controller/actor_controller.py @@ -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)) @@ -32,13 +36,4 @@ class ActorController(BasicController): await instance.toggle() 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", [])) \ No newline at end of file + \ No newline at end of file diff --git a/cbpi/controller/basic_controller.py b/cbpi/controller/basic_controller2.py similarity index 63% rename from cbpi/controller/basic_controller.py rename to cbpi/controller/basic_controller2.py index 89a0273..ba65c0ed 100644 --- a/cbpi/controller/basic_controller.py +++ b/cbpi/controller/basic_controller2.py @@ -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 @@ -26,57 +28,57 @@ 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 - - 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()) + 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()) + 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)) diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index fb638db..1db9608 100644 --- a/cbpi/controller/config_controller.py +++ b/cbpi/controller/config_controller.py @@ -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) diff --git a/cbpi/controller/dashboard_controller.py b/cbpi/controller/dashboard_controller.py index ec14950..f17f97a 100644 --- a/cbpi/controller/dashboard_controller.py +++ b/cbpi/controller/dashboard_controller.py @@ -4,7 +4,7 @@ import os from os import listdir from os.path import isfile, join -class DashboardController(): +class DashboardController: def __init__(self, cbpi): diff --git a/cbpi/controller/kettle_controller.py b/cbpi/controller/kettle_controller.py index c30f0f1..c00b790 100644 --- a/cbpi/controller/kettle_controller.py +++ b/cbpi/controller/kettle_controller.py @@ -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", [])) \ No newline at end of file diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index 9f3456d..0033c3f 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -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 diff --git a/cbpi/controller/notification_controller.py b/cbpi/controller/notification_controller.py deleted file mode 100644 index 8686e3a..0000000 --- a/cbpi/controller/notification_controller.py +++ /dev/null @@ -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)) - diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index 409d169..60742ad 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -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): diff --git a/cbpi/controller/satellite_controller.py b/cbpi/controller/satellite_controller.py new file mode 100644 index 0000000..48c2214 --- /dev/null +++ b/cbpi/controller/satellite_controller.py @@ -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) + diff --git a/cbpi/controller/sensor_controller.py b/cbpi/controller/sensor_controller.py index 65bcbac..fad7152 100644 --- a/cbpi/controller/sensor_controller.py +++ b/cbpi/controller/sensor_controller.py @@ -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 \ No newline at end of file diff --git a/cbpi/controller/step_controller.py b/cbpi/controller/step_controller.py index 5d05f99..7789751 100644 --- a/cbpi/controller/step_controller.py +++ b/cbpi/controller/step_controller.py @@ -1,21 +1,20 @@ import asyncio - -from tabulate import tabulate +import copy import json -import copy -import shortuuid 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: + 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): + async def update(self, item: Step): + 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 + try: + type_cfg = self.types.get(item.type) + clazz = type_cfg.get("class") - self.profile = list(map(lambda old: {**merge_data(id, old, data)} if old["id"] == id else old, self.profile)) + 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") + + 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 + 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()) - 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 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") diff --git a/cbpi/controller/system_controller.py b/cbpi/controller/system_controller.py index 0db4861..2ac6383 100644 --- a/cbpi/controller/system_controller.py +++ b/cbpi/controller/system_controller.py @@ -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 diff --git a/cbpi/craftbeerpi.py b/cbpi/craftbeerpi.py index 9c17785..9ef089f 100644 --- a/cbpi/craftbeerpi.py +++ b/cbpi/craftbeerpi.py @@ -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,8 +216,8 @@ 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']) for i in self.initializer: @@ -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 diff --git a/cbpi/database/__init__.py b/cbpi/database/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/cbpi/database/model.py b/cbpi/database/model.py deleted file mode 100644 index e7a62cc..0000000 --- a/cbpi/database/model.py +++ /dev/null @@ -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() diff --git a/cbpi/database/orm_framework.py b/cbpi/database/orm_framework.py deleted file mode 100644 index 42072c3..0000000 --- a/cbpi/database/orm_framework.py +++ /dev/null @@ -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__ diff --git a/cbpi/extension/comp/__init__.py b/cbpi/extension/comp/__init__.py deleted file mode 100644 index e9ae1c2..0000000 --- a/cbpi/extension/comp/__init__.py +++ /dev/null @@ -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 diff --git a/cbpi/extension/comp/config.yaml b/cbpi/extension/comp/config.yaml deleted file mode 100644 index 0f48c6b..0000000 --- a/cbpi/extension/comp/config.yaml +++ /dev/null @@ -1,2 +0,0 @@ -name: DummyComponent -version: 4 \ No newline at end of file diff --git a/cbpi/extension/comp/static/index.html b/cbpi/extension/comp/static/index.html deleted file mode 100644 index ab1afad..0000000 --- a/cbpi/extension/comp/static/index.html +++ /dev/null @@ -1 +0,0 @@ -HALLO WELT INDEX \ No newline at end of file diff --git a/cbpi/extension/comp/static/index2.html b/cbpi/extension/comp/static/index2.html deleted file mode 100644 index 4f1175b..0000000 --- a/cbpi/extension/comp/static/index2.html +++ /dev/null @@ -1 +0,0 @@ -HALLO WELT INDEX2 \ No newline at end of file diff --git a/cbpi/extension/ds18b20/__init__.py b/cbpi/extension/ds18b20/__init__.py deleted file mode 100644 index 55e9a9a..0000000 --- a/cbpi/extension/ds18b20/__init__.py +++ /dev/null @@ -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) diff --git a/cbpi/extension/ds18b20/config.yaml b/cbpi/extension/ds18b20/config.yaml deleted file mode 100644 index 7f0aa15..0000000 --- a/cbpi/extension/ds18b20/config.yaml +++ /dev/null @@ -1,3 +0,0 @@ -name: DummySensor -version: 4 -active: true \ No newline at end of file diff --git a/cbpi/extension/dummyactor/__init__.py b/cbpi/extension/dummyactor/__init__.py index d27d216..88ac340 100644 --- a/cbpi/extension/dummyactor/__init__.py +++ b/cbpi/extension/dummyactor/__init__.py @@ -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 diff --git a/cbpi/extension/gpioactor/__init__.py b/cbpi/extension/gpioactor/__init__.py index 88a3663..bb470fa 100644 --- a/cbpi/extension/gpioactor/__init__.py +++ b/cbpi/extension/gpioactor/__init__.py @@ -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): diff --git a/cbpi/extension/httpsensor/__init__.py b/cbpi/extension/httpsensor/__init__.py index 19c65ba..b5a758c 100644 --- a/cbpi/extension/httpsensor/__init__.py +++ b/cbpi/extension/httpsensor/__init__.py @@ -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) diff --git a/cbpi/extension/hysteresis/__init__.py b/cbpi/extension/hysteresis/__init__.py index 613f410..3ac2ea6 100644 --- a/cbpi/extension/hysteresis/__init__.py +++ b/cbpi/extension/hysteresis/__init__.py @@ -6,26 +6,25 @@ from cbpi.api import * @parameters([Property.Number(label="OffsetOn", configurable=True, description="Offset below target temp when heater should switched on"), 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: diff --git a/cbpi/extension/mashstep/__init__.py b/cbpi/extension/mashstep/__init__.py index 564a191..8843b7b 100644 --- a/cbpi/extension/mashstep/__init__.py +++ b/cbpi/extension/mashstep/__init__.py @@ -1,168 +1,109 @@ 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() + self.timer = Timer(10,on_update=self.on_timer_update, on_done=self.on_timer_done) - 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 - + 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" - - 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 = 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): ''' This method is called by the server during startup @@ -170,9 +111,13 @@ def setup(cbpi): :param cbpi: the cbpi core :return: - ''' + ''' - cbpi.plugin.register("ActorStep", ActorStep) cbpi.plugin.register("WaitStep", WaitStep) cbpi.plugin.register("MashStep", MashStep) + cbpi.plugin.register("ActorStep", ActorStep) + + + + diff --git a/cbpi/extension/mashstep/__init__.py.old b/cbpi/extension/mashstep/__init__.py.old new file mode 100644 index 0000000..8b2b53c --- /dev/null +++ b/cbpi/extension/mashstep/__init__.py.old @@ -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) + diff --git a/cbpi/extension/mqtt/__init__.py b/cbpi/extension/mqtt/__init__.py index 2f73c1b..f050344 100644 --- a/cbpi/extension/mqtt/__init__.py +++ b/cbpi/extension/mqtt/__init__.py @@ -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): diff --git a/cbpi/extension/mqtt/config.yaml b/cbpi/extension/mqtt/config.yaml index e24ed7d..b10dc5d 100644 --- a/cbpi/extension/mqtt/config.yaml +++ b/cbpi/extension/mqtt/config.yaml @@ -1,3 +1,3 @@ name: MQTT -version: 4.0 +version: 4 active: false \ No newline at end of file diff --git a/cbpi/extension/onewire/__init__.py b/cbpi/extension/onewire/__init__.py index da1653c..777d21b 100644 --- a/cbpi/extension/onewire/__init__.py +++ b/cbpi/extension/onewire/__init__.py @@ -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) diff --git a/cbpi/http_endpoints/http_actor.py b/cbpi/http_endpoints/http_actor.py index 9f3a8cf..0471912 100644 --- a/cbpi/http_endpoints/http_actor.py +++ b/cbpi/http_endpoints/http_actor.py @@ -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): diff --git a/cbpi/http_endpoints/http_config.py b/cbpi/http_endpoints/http_config.py index 19a2c0a..2379b64 100644 --- a/cbpi/http_endpoints/http_config.py +++ b/cbpi/http_endpoints/http_config.py @@ -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") diff --git a/cbpi/http_endpoints/http_curd_endpoints.py b/cbpi/http_endpoints/http_curd_endpoints.py deleted file mode 100644 index 9dfee58..0000000 --- a/cbpi/http_endpoints/http_curd_endpoints.py +++ /dev/null @@ -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) diff --git a/cbpi/http_endpoints/http_dashboard.py b/cbpi/http_endpoints/http_dashboard.py index 1b785b9..891fd0d 100644 --- a/cbpi/http_endpoints/http_dashboard.py +++ b/cbpi/http_endpoints/http_dashboard.py @@ -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 diff --git a/cbpi/http_endpoints/http_kettle.py b/cbpi/http_endpoints/http_kettle.py index 6b4cead..16494c5 100644 --- a/cbpi/http_endpoints/http_kettle.py +++ b/cbpi/http_endpoints/http_kettle.py @@ -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): diff --git a/cbpi/http_endpoints/http_log.py b/cbpi/http_endpoints/http_log.py index 9ac13f0..f6de7a9 100644 --- a/cbpi/http_endpoints/http_log.py +++ b/cbpi/http_endpoints/http_log.py @@ -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) diff --git a/cbpi/http_endpoints/http_sensor.py b/cbpi/http_endpoints/http_sensor.py index ac1d7d9..7ab9a3e 100644 --- a/cbpi/http_endpoints/http_sensor.py +++ b/cbpi/http_endpoints/http_sensor.py @@ -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): diff --git a/cbpi/http_endpoints/http_step.py b/cbpi/http_endpoints/http_step.py index b3c4673..00094e4 100644 --- a/cbpi/http_endpoints/http_step.py +++ b/cbpi/http_endpoints/http_step.py @@ -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,10 +49,15 @@ 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): diff --git a/cbpi/satellite.py b/cbpi/satellite.py index ea20ffe..11ae127 100644 --- a/cbpi/satellite.py +++ b/cbpi/satellite.py @@ -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: diff --git a/cbpi/utils/encoder.py b/cbpi/utils/encoder.py index 08a2e15..136cb82 100644 --- a/cbpi/utils/encoder.py +++ b/cbpi/utils/encoder.py @@ -14,6 +14,6 @@ class ComplexEncoder(JSONEncoder): else: raise TypeError() except Exception as e: - print(e) + pass return None diff --git a/cbpi/utils/utils.py b/cbpi/utils/utils.py index 8673080..a41d8d6 100644 --- a/cbpi/utils/utils.py +++ b/cbpi/utils/utils.py @@ -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): diff --git a/cbpi/websocket.py b/cbpi/websocket.py index 482a90d..253567a 100644 --- a/cbpi/websocket.py +++ b/cbpi/websocket.py @@ -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: diff --git a/config/actor.json b/config/actor.json deleted file mode 100644 index 1ff2d62..0000000 --- a/config/actor.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/config/cbpi_dashboard_1.json b/config/cbpi_dashboard_1.json deleted file mode 100644 index 568e6fe..0000000 --- a/config/cbpi_dashboard_1.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/config/config.yaml b/config/config.yaml deleted file mode 100644 index ba87c9e..0000000 --- a/config/config.yaml +++ /dev/null @@ -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 - diff --git a/config/dashboard/cbpi_dashboard_1.json b/config/dashboard/cbpi_dashboard_1.json deleted file mode 100644 index 647b400..0000000 --- a/config/dashboard/cbpi_dashboard_1.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/config/dashboard/widgets/boil.svg b/config/dashboard/widgets/boil.svg deleted file mode 100644 index bd27639..0000000 --- a/config/dashboard/widgets/boil.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/brewery.svg b/config/dashboard/widgets/brewery.svg deleted file mode 100644 index c874e00..0000000 --- a/config/dashboard/widgets/brewery.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/config/dashboard/widgets/bright.svg b/config/dashboard/widgets/bright.svg deleted file mode 100644 index 22db5cb..0000000 --- a/config/dashboard/widgets/bright.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/bucket.svg b/config/dashboard/widgets/bucket.svg deleted file mode 100644 index 7e68c61..0000000 --- a/config/dashboard/widgets/bucket.svg +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Created with Sketch. - - - - - - - - - - - - - - - - - diff --git a/config/dashboard/widgets/carboy.svg b/config/dashboard/widgets/carboy.svg deleted file mode 100644 index 4bfe824..0000000 --- a/config/dashboard/widgets/carboy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/cbpi_svg.svg b/config/dashboard/widgets/cbpi_svg.svg deleted file mode 100644 index 6375a00..0000000 --- a/config/dashboard/widgets/cbpi_svg.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/config/dashboard/widgets/chiller.svg b/config/dashboard/widgets/chiller.svg deleted file mode 100644 index 975f63d..0000000 --- a/config/dashboard/widgets/chiller.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/coleman.svg b/config/dashboard/widgets/coleman.svg deleted file mode 100644 index 3dd1b87..0000000 --- a/config/dashboard/widgets/coleman.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/config/dashboard/widgets/decor.svg b/config/dashboard/widgets/decor.svg deleted file mode 100644 index 8443ede..0000000 --- a/config/dashboard/widgets/decor.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/decor2.svg b/config/dashboard/widgets/decor2.svg deleted file mode 100644 index 71fc944..0000000 --- a/config/dashboard/widgets/decor2.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/fermentation.svg b/config/dashboard/widgets/fermentation.svg deleted file mode 100644 index 394342b..0000000 --- a/config/dashboard/widgets/fermentation.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/fermentor.svg b/config/dashboard/widgets/fermentor.svg deleted file mode 100644 index 7cad3a8..0000000 --- a/config/dashboard/widgets/fermentor.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/hood.svg b/config/dashboard/widgets/hood.svg deleted file mode 100644 index df48e0b..0000000 --- a/config/dashboard/widgets/hood.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/config/dashboard/widgets/kettle.svg b/config/dashboard/widgets/kettle.svg deleted file mode 100644 index eb947d2..0000000 --- a/config/dashboard/widgets/kettle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/kettle1.svg b/config/dashboard/widgets/kettle1.svg deleted file mode 100644 index c4739dd..0000000 --- a/config/dashboard/widgets/kettle1.svg +++ /dev/null @@ -1,2 +0,0 @@ - -015 - StillCreated with Sketch. diff --git a/config/dashboard/widgets/kettle2.svg b/config/dashboard/widgets/kettle2.svg deleted file mode 100644 index 8590454..0000000 --- a/config/dashboard/widgets/kettle2.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/config/dashboard/widgets/logo.svg b/config/dashboard/widgets/logo.svg deleted file mode 100644 index 2b68abd..0000000 --- a/config/dashboard/widgets/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/mash1.svg b/config/dashboard/widgets/mash1.svg deleted file mode 100644 index a6edb71..0000000 --- a/config/dashboard/widgets/mash1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/mash2.svg b/config/dashboard/widgets/mash2.svg deleted file mode 100644 index bd7234c..0000000 --- a/config/dashboard/widgets/mash2.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/mill.svg b/config/dashboard/widgets/mill.svg deleted file mode 100644 index b14d15c..0000000 --- a/config/dashboard/widgets/mill.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/mill2.svg b/config/dashboard/widgets/mill2.svg deleted file mode 100644 index f57c094..0000000 --- a/config/dashboard/widgets/mill2.svg +++ /dev/null @@ -1,2 +0,0 @@ - -016 - MaceratorCreated with Sketch. diff --git a/config/dashboard/widgets/pump2.svg b/config/dashboard/widgets/pump2.svg deleted file mode 100644 index 177c4a1..0000000 --- a/config/dashboard/widgets/pump2.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/dashboard/widgets/tank.svg b/config/dashboard/widgets/tank.svg deleted file mode 100644 index 37c687b..0000000 --- a/config/dashboard/widgets/tank.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/kettle.json b/config/kettle.json deleted file mode 100644 index ebd8d26..0000000 --- a/config/kettle.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/config/plugin_list.txt b/config/plugin_list.txt deleted file mode 100644 index 04a4a8b..0000000 --- a/config/plugin_list.txt +++ /dev/null @@ -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' \ No newline at end of file diff --git a/config/sensor.json b/config/sensor.json deleted file mode 100644 index cc6bb2d..0000000 --- a/config/sensor.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/config/splash.png b/config/splash.png deleted file mode 100644 index 68086b5..0000000 Binary files a/config/splash.png and /dev/null differ diff --git a/config/step_data.json b/config/step_data.json deleted file mode 100644 index 7e96c12..0000000 --- a/config/step_data.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/sample.py b/sample.py index e69de29..8d2a30d 100644 --- a/sample.py +++ b/sample.py @@ -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 + + +'''