diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 779ef2a..c3f9428 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,16 +2,22 @@ - + + + + - + + + + - - - + + + - - - - + - - + + + + + @@ -998,7 +1725,6 @@ - @@ -1040,154 +1766,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1219,16 +1797,6 @@ - - - - - - - - - - @@ -1236,14 +1804,6 @@ - - - - - - - - @@ -1251,26 +1811,11 @@ - - - - - - - - - - - - - - - - + @@ -1282,24 +1827,6 @@ - - - - - - - - - - - - - - - - - - @@ -1316,22 +1843,6 @@ - - - - - - - - - - - - - - - - @@ -1345,7 +1856,7 @@ - + @@ -1355,25 +1866,17 @@ - + - - - - - - - - - + @@ -1386,36 +1889,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1424,14 +1897,6 @@ - - - - - - - - @@ -1440,20 +1905,304 @@ - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/api/step.py b/core/api/step.py new file mode 100644 index 0000000..8c29c35 --- /dev/null +++ b/core/api/step.py @@ -0,0 +1,11 @@ +class Step(object): + + def __init__(self, key=None, cbpi=None): + self.cbpi = cbpi + self.id = key + + async def run(self): + pass + + def stop(self): + pass \ No newline at end of file diff --git a/core/controller/actor_controller.py b/core/controller/actor_controller.py index 7da1cc0..ab67996 100644 --- a/core/controller/actor_controller.py +++ b/core/controller/actor_controller.py @@ -17,7 +17,7 @@ class ActorHttp(HttpAPI): :return: """ id = int(request.match_info['id']) - self.cbpi.bus.fire(topic="actor/%s/on" % id, id=id, power=99) + self.cbpi.bus.fire(topic="actor/%s/switch/on" % id, id=id, power=99) return web.Response(status=204) @@ -90,16 +90,10 @@ class ActorController(ActorHttp, CRUDController): self.cache[id].instance = clazz(**cfg) print("gpIO", self.cache[id].instance, self.cache[id].instance.gpio) - @on_event(topic="actor/1/on") - def on1(self, **kwargs) -> None: - print("WOOOOHOOO111111") - - @on_event(topic="actor/1/on") - def on3(self, **kwargs) -> None: - print("WOOOOHOOO22222") - @on_event(topic="actor/+/on") + + @on_event(topic="actor/+/switch/on") def on(self, id , power=100, **kwargs) -> None: ''' Method to switch an actor on. @@ -115,7 +109,7 @@ class ActorController(ActorHttp, CRUDController): if id in self.cache: print("POWER ON") actor = self.cache[id ].instance - print("ONNNNN", actor) + self.cbpi.bus.fire("actor/%s/on/ok" % id) actor.on(power) @on_event(topic="actor/+/toggle") diff --git a/core/controller/kettle_controller.py b/core/controller/kettle_controller.py index 9817e4e..b9d0e7a 100644 --- a/core/controller/kettle_controller.py +++ b/core/controller/kettle_controller.py @@ -1,3 +1,5 @@ +import re + from aiohttp import web from core.api import request_mapping, on_event @@ -76,11 +78,13 @@ class KettleController(CRUDController): @on_event(topic="job/done") def job_stop(self, key, **kwargs) -> None: - name = key.split("_") - kettle = self.cache[int(name[2])] - kettle.instance = None + match = re.match("kettle_logic_(\d+)", key) + if match is not None: + kid = match.group(1) + kettle = self.cache[int(kid)] + kettle.instance = None - print("STOP KETTLE LOGIC", int(name[2])) + print("STOP KETTLE LOGIC", kid) @on_event(topic="kettle/+/automatic") async def handle_automtic_event(self, id, **kwargs): diff --git a/core/controller/plugin_controller.py b/core/controller/plugin_controller.py index 574d838..e5f3aa4 100644 --- a/core/controller/plugin_controller.py +++ b/core/controller/plugin_controller.py @@ -13,6 +13,7 @@ from core.api.extension import CBPiExtension from core.api.kettle_logic import CBPiKettleLogic from core.api.property import Property from core.api.sensor import CBPiSensor +from core.api.step import Step from core.utils.utils import load_config, json_dumps logger = logging.getLogger(__file__) @@ -101,13 +102,16 @@ class PluginController(): if issubclass(clazz, CBPiKettleLogic): self.cbpi.kettle.types[name] = {"class": clazz, "config": self._parse_props(clazz)} + if issubclass(clazz, Step): + print("NAME", name) + self.cbpi.step.types[name] = {"class": clazz, "config": self._parse_props(clazz)} if issubclass(clazz, CBPiExtension): self.c = clazz(self.cbpi) def _parse_props(self, cls): - + print("PARSE", cls) name = cls.__name__ result = {"name": name, "class": cls, "properties": [], "actions": []} diff --git a/core/controller/step_controller.py b/core/controller/step_controller.py index d7a1a56..c71490d 100644 --- a/core/controller/step_controller.py +++ b/core/controller/step_controller.py @@ -1,8 +1,69 @@ +import asyncio +from aiohttp import web +from core.api import on_event, request_mapping + class StepController(): + def __init__(self, cbpi): + self.cbpi = cbpi + self.types = {} + self.steps = { + 1: dict(name="S1", config=dict(time=1), type="CustomStep", state=None), + 2: dict(name="S2", config=dict(time=1), type="CustomStep", state=None), + 3: dict(name="S3", config=dict(time=1), type="CustomStep", state=None) + } + self.current_step = None + self.cbpi.register(self, "/step") - async def start(self): + async def init(self): + #self.start() pass + @request_mapping(path="/start", auth_required=False) + async def http_start(self, request): + self.cbpi.bus.fire("step/start") + return web.Response(text="OK") + + @request_mapping(path="/reset", auth_required=False) + async def http_reset(self, request): + self.cbpi.bus.fire("step/reset") + return web.Response(text="OK") + + @on_event("step/start") + def handle_start(self, topic, **kwargs): + self.start() + + @on_event("step/reset") + def handle_rest(self, topic, **kwargs): + for key, step in self.steps.items(): + step["state"] = None + + self.current_step = Nonecd + + @on_event("step/+/done") + def handle(self, topic, **kwargs): + self.start() + + def _step_done(self, task): + self.steps[self.current_step.id]["state"] = "D" + step_id = self.current_step.id + self.current_step = None + self.cbpi.bus.fire("step/%s/done" % step_id) + + def start(self): + + if self.current_step is None: + loop = asyncio.get_event_loop() + open_step = False + for key, step in self.steps.items(): + if step["state"] is None: + type = self.types.get(step["type"]) + self.current_step = type["class"](key, self.cbpi) + task = loop.create_task(self.current_step.run()) + task.add_done_callback(self._step_done) + open_step = True + break + if open_step == False: + self.cbpi.bus.fire("step/berwing/finished") async def stop(self): pass diff --git a/core/craftbeerpi.py b/core/craftbeerpi.py index 661c91f..1b01073 100644 --- a/core/craftbeerpi.py +++ b/core/craftbeerpi.py @@ -12,6 +12,7 @@ from aiohttp_swagger import setup_swagger from core.controller.config_controller import ConfigController from core.controller.kettle_controller import KettleController +from core.controller.step_controller import StepController from core.job.aiohttp import setup, get_scheduler_from_app from core.controller.actor_controller import ActorController @@ -58,10 +59,13 @@ class CraftBeerPi(): self.system = SystemController(self) self.config2 = ConfigController(self) self.kettle = KettleController(self) + self.step = StepController(self) self.notification = NotificationController(self) self.login = Login(self) + self.register_events(self.ws) + def register_events(self, obj): @@ -173,6 +177,7 @@ class CraftBeerPi(): switcher[http_method]() if url_prefix is not None: + print("Prefx", url_prefix) sub = web.Application() sub.add_routes(routes) self.app.add_subapp(url_prefix, sub) @@ -230,11 +235,14 @@ class CraftBeerPi(): async def init_controller(app): await self.sensor.init() + await self.step.init() await self.actor.init() await self.kettle.init() + import pprint pprint.pprint(self.bus.dump()) + async def load_plugins(app): #await PluginController.load_plugin_list() await self.plugin.load_plugins() diff --git a/core/eventbus.py b/core/eventbus.py index b9cb89e..09f360d 100644 --- a/core/eventbus.py +++ b/core/eventbus.py @@ -71,7 +71,7 @@ class EventBus(object): print(self.loop) def fire(self, topic: str, **kwargs) -> None: - self.logger.info("EMIT EVENT %s", topic) + self.logger.info("EMIT EVENT %s Data: %s", topic, kwargs) trx = dict(i=0) for e in self.iter_match(topic): @@ -91,10 +91,7 @@ class EventBus(object): else: content_obj.method(**kwargs, topic = topic) - #if inspect.iscoroutinefunction(content_obj.method): - # self.loop.create_task(content_obj.method(**kwargs, trx=trx, topic=topic)) - #else: - # content_obj.method(**kwargs, trx=trx, topic=topic) + if content_obj.once is False: keep_idx.append(idx) @@ -102,7 +99,7 @@ class EventBus(object): if len(keep_idx) < len(e): e[0].parent._content = [e[0].parent._content[i] for i in keep_idx] - print("DONE", trx) + def dump(self): def rec(node, i=0): diff --git a/core/eventbus3.py b/core/eventbus3.py deleted file mode 100644 index d6b0cfd..0000000 --- a/core/eventbus3.py +++ /dev/null @@ -1,132 +0,0 @@ -import inspect -import logging - -class EventBus(object): - - def __init__(self, cbpi): - self.logger = logging.getLogger(__name__) - self._root = self.Node() - self.registry = {} - self.docs = {} - self.cbpi = cbpi - - class Node(object): - __slots__ = '_children', '_content' - - def __init__(self): - self._children = {} - self._content = None - - - class Content(object): - def __init__(self, parent, topic, method, once): - - self.parent = parent - self.method = method - self.name = method.__name__ - self.once = once - self.topic = topic - - def register(self, topic, method, once=False): - print("REGISTER", topic, method) - if method in self.registry: - raise RuntimeError("Method %s already registerd. Please unregister first!" % method.__name__) - self.logger.info("Topic %s", topic) - - node = self._root - for sym in topic.split('/'): - node = node._children.setdefault(sym, self.Node()) - - if not isinstance(node._content, list): - node._content = [] - - - c = self.Content(node, topic, method, once) - - - node._content.append(c) - print(c, node._content, topic) - self.registry[method] = c - - - - def get_callbacks(self, key): - try: - node = self._root - for sym in key.split('/'): - node = node._children[sym] - if node._content is None: - raise KeyError(key) - return node._content - except KeyError: - raise KeyError(key) - - - def unregister(self, method): - self.logger.info("Unregister %s", method.__name__) - if method in self.registry: - content = self.registry[method] - clean_idx = None - for idx, content_obj in enumerate(content.parent._content): - if method == content_obj.method: - clean_idx = idx - break - if clean_idx is not None: - del content.parent._content[clean_idx] - - - - def fire(self, topic: str, **kwargs) -> None: - - self.logger.info("EMIT EVENT %s", topic) - - cleanup_methods = [] - for content_array in self.iter_match(topic): - - print(content_array) - cleanup = [] - for idx, content_obj in enumerate(content_array): - - if inspect.iscoroutinefunction(content_obj.method): - if hasattr(content_obj.method, "future"): - - self.cbpi.app.loop.create_task(content_obj.method(**kwargs, future=content_obj.method.future, topic=topic)) - else: - self.cbpi.app.loop.create_task(content_obj.method(**kwargs, topic = topic)) - else: - if hasattr(content_obj.method, "future"): - content_obj.method(**kwargs, future=content_obj.method.future, topic=topic) - else: - content_obj.method(**kwargs, topic = topic) - - if content_obj.once is True: - cleanup.append(idx) - for idx in cleanup: - del content_array[idx] - - - - - def iter_match(self, topic): - - lst = topic.split('/') - normal = not topic.startswith('$') - - def rec(node, i=0): - if i == len(lst): - if node._content is not None: - yield node._content - else: - part = lst[i] - if part in node._children: - for content in rec(node._children[part], i + 1): - yield content - if '+' in node._children and (normal or i > 0): - for content in rec(node._children['+'], i + 1): - yield content - if '#' in node._children and (normal or i > 0): - content = node._children['#']._content - if content is not None: - yield content - - return rec(self._root) diff --git a/core/extension/dummyactor/__init__.py b/core/extension/dummyactor/__init__.py index 779c21d..a1a701c 100644 --- a/core/extension/dummyactor/__init__.py +++ b/core/extension/dummyactor/__init__.py @@ -23,7 +23,7 @@ class CustomActor(CBPiActor): def on(self, power=100): - print("###### ON", self.gpio) + self.state = True diff --git a/core/extension/dummylogic/__init__.py b/core/extension/dummylogic/__init__.py index 6448635..fe300ea 100644 --- a/core/extension/dummylogic/__init__.py +++ b/core/extension/dummylogic/__init__.py @@ -48,12 +48,15 @@ class CustomLogic(CBPiKettleLogic): async def run(self): - async def my_callback(id, **kwargs): - self.cbpi.bus.unregister(my_callback) - kwargs["future"].set_result("AMAZING") - return "OK" + async def my_callback(value, **kwargs): - result = await self.wait_for_event("actor/+/on", callback=my_callback) + if value == 5: + self.cbpi.bus.unregister(my_callback) + kwargs["future"].set_result("AMAZING") + else: + print("OTHER VALUE", value) + + result = await self.wait_for_event("sensor/1", callback=my_callback) print("THE RESULT", result) @@ -68,7 +71,7 @@ class CustomLogic(CBPiKettleLogic): break await asyncio.sleep(1) ''' - print("STOP LOGIC") + print("YES IM FINISHED STOP LOGIC") def setup(cbpi): diff --git a/core/extension/dummysensor/__init__.py b/core/extension/dummysensor/__init__.py index b67df42..fa43818 100644 --- a/core/extension/dummysensor/__init__.py +++ b/core/extension/dummysensor/__init__.py @@ -1,5 +1,6 @@ import asyncio import logging +import random from core.api import CBPiActor, Property, action, background_task from core.api.sensor import CBPiSensor @@ -27,10 +28,12 @@ class CustomSensor(CBPiSensor): async def run(self, cbpi): self.value = 0 while True: - await asyncio.sleep(self.interval) + #await asyncio.sleep(self.interval) + await asyncio.sleep(random.uniform(0, 1)) self.value = self.value + 1 - print("SENSOR IS RUNNING") + cbpi.bus.fire("sensor/%s" % self.id, value=self.value) + print("SENSOR IS RUNNING", self.value) diff --git a/core/extension/dummystep/__init__.py b/core/extension/dummystep/__init__.py new file mode 100644 index 0000000..5849202 --- /dev/null +++ b/core/extension/dummystep/__init__.py @@ -0,0 +1,25 @@ +import asyncio + +from core.api.step import Step + + +class CustomStep(Step): + + async def run(self): + i = 0 + while i < 3: + await asyncio.sleep(1) + print("RUN STEP") + i = i + 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("CustomStep", CustomStep) diff --git a/core/extension/dummystep/config.yaml b/core/extension/dummystep/config.yaml new file mode 100644 index 0000000..0029445 --- /dev/null +++ b/core/extension/dummystep/config.yaml @@ -0,0 +1,2 @@ +name: Manuel +version: 4 \ No newline at end of file diff --git a/core/websocket.py b/core/websocket.py index 9fa9cc3..5b6f3d5 100644 --- a/core/websocket.py +++ b/core/websocket.py @@ -1,11 +1,13 @@ import logging import weakref from collections import defaultdict - +import json import aiohttp from aiohttp import web from typing import Iterable, Callable +from core.api import on_event + class WebSocket: def __init__(self, cbpi) -> None: @@ -15,6 +17,13 @@ class WebSocket: self.logger = logging.getLogger(__name__) self.cbpi.app.add_routes([web.get('/ws', self.websocket_handler)]) + @on_event(topic="#") + async def listen(self, topic, **kwargs): + print("WS", topic) + + self.send(json.dumps(dict(topic=topic, data=dict(**kwargs)))) + + def send(self, data): for ws in self._clients: diff --git a/craftbeerpi.db b/craftbeerpi.db index b1b4b4e..18711b1 100644 Binary files a/craftbeerpi.db and b/craftbeerpi.db differ diff --git a/hello.mp3 b/hello.mp3 new file mode 100644 index 0000000..85f633e Binary files /dev/null and b/hello.mp3 differ diff --git a/hello3.mp3 b/hello3.mp3 new file mode 100644 index 0000000..1de7cd6 Binary files /dev/null and b/hello3.mp3 differ diff --git a/main2.py b/main2.py index 023194e..d64f18f 100644 --- a/main2.py +++ b/main2.py @@ -1,4 +1,5 @@ import asyncio +import re from core.eventbus3 import EventBus @@ -66,6 +67,9 @@ id2 = bus.register("test/name", test2) print(id1, id2) +from gtts import gTTS + + print(hex(id(test2))) @@ -77,4 +81,10 @@ bus.fire("test/name") bus.unregister(test2) -bus.fire("test/name") \ No newline at end of file +bus.fire("test/name") + + +m0 = re.match("kettle_logic_(\d+)", "kettle_1logic_22") +print(m0) +#print(m0.group(1)) + diff --git a/tests/test_kettle.py b/tests/test_kettle.py index 8ee8703..48af9d2 100644 --- a/tests/test_kettle.py +++ b/tests/test_kettle.py @@ -14,6 +14,17 @@ class KettleTestCase(AioHTTPTestCase): @unittest_run_loop async def test_example(self): - assert await self.cbpi.kettle.toggle_automtic(1) is True - assert await self.cbpi.kettle.toggle_automtic(1) is True - assert await self.cbpi.kettle.toggle_automtic(99) is False + result = await self.cbpi.kettle.toggle_automtic(1) + print("#### RESULT", result) + assert result[0] is True + print("FIRE") + + + await asyncio.sleep(1) + + self.cbpi.bus.fire("actor/1/on", id=1) + + + await asyncio.sleep(5) + #assert await self.cbpi.kettle.toggle_automtic(1) is True + #assert await self.cbpi.kettle.toggle_automtic(99) is False