diff --git a/.idea/craftbeerpi4.iml b/.idea/craftbeerpi4.iml index b327ac2..325407e 100644 --- a/.idea/craftbeerpi4.iml +++ b/.idea/craftbeerpi4.iml @@ -2,7 +2,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index f20c4f1..f83f698 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 80e317f..15cbccd 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,28 +1,24 @@ - - - - - - - + + + - + + + - - + - - - - - + + + + - + + + - + + - + - - + + + @@ -1681,27 +1293,26 @@ - - + + + - - - - - + + - + + @@ -1726,146 +1337,32 @@ file:///usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/logging/__init__.py 25 + + file://$PROJECT_DIR$/core/eventbus.py + 101 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1873,7 +1370,6 @@ - @@ -1891,9 +1387,6 @@ - - - @@ -1901,17 +1394,6 @@ - - - - - - - - - - - @@ -1919,18 +1401,10 @@ - - - - - - - - - + @@ -1943,15 +1417,6 @@ - - - - - - - - - @@ -1960,19 +1425,11 @@ - + - - - - - - - - @@ -1981,21 +1438,10 @@ - - - - - - - - - - - @@ -2003,7 +1449,6 @@ - @@ -2011,7 +1456,6 @@ - @@ -2019,9 +1463,6 @@ - - - @@ -2029,43 +1470,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2081,9 +1485,6 @@ - - - @@ -2091,7 +1492,6 @@ - @@ -2100,49 +1500,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2151,43 +1512,252 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/api/decorator.py b/core/api/decorator.py index e829b53..83a02c8 100644 --- a/core/api/decorator.py +++ b/core/api/decorator.py @@ -44,6 +44,7 @@ def on_event(topic): def real_decorator(func): func.eventbus = True func.topic = topic + func.c = None return func return real_decorator @@ -51,6 +52,7 @@ def on_event(topic): def action(key, parameters): def real_decorator(func): func.action = True + func.key = key func.parameters = parameters return func diff --git a/core/api/kettle_logic.py b/core/api/kettle_logic.py index 0225edc..0bead46 100644 --- a/core/api/kettle_logic.py +++ b/core/api/kettle_logic.py @@ -3,4 +3,29 @@ from core.api.extension import CBPiExtension class CBPiKettleLogic(CBPiExtension): - pass \ No newline at end of file + ''' + Base Class for a Kettle logic. + ''' + + + def stop(self): + ''' + test + + + :return: + ''' + pass + + def run(self): + ''' + This method is running as background process when logic is started. + Typically a while loop responsible that the method keeps running + + while self.running: + await asyncio.sleep(1) + + :return: + ''' + + pass \ No newline at end of file diff --git a/core/api/property.py b/core/api/property.py index ff8b5fe..9c42c1f 100644 --- a/core/api/property.py +++ b/core/api/property.py @@ -5,7 +5,14 @@ class PropertyType(object): class Property(object): class Select(PropertyType): + def __init__(self, label, options, description=""): + ''' + + :param label: + :param options: + :param description: + ''' PropertyType.__init__(self) self.label = label self.options = options @@ -13,6 +20,16 @@ class Property(object): class Number(PropertyType): def __init__(self, label, configurable=False, default_value=None, unit="", description=""): + ''' + Test + + + :param label: + :param configurable: + :param default_value: + :param unit: + :param description: + ''' PropertyType.__init__(self) self.label = label self.configurable = configurable @@ -21,6 +38,13 @@ class Property(object): class Text(PropertyType): def __init__(self, label, configurable=False, default_value="", description=""): + ''' + + :param label: + :param configurable: + :param default_value: + :param description: + ''' PropertyType.__init__(self) self.label = label self.configurable = configurable @@ -29,6 +53,11 @@ class Property(object): class Actor(PropertyType): def __init__(self, label, description=""): + ''' + + :param label: + :param description: + ''' PropertyType.__init__(self) self.label = label self.configurable = True @@ -36,6 +65,11 @@ class Property(object): class Sensor(PropertyType): def __init__(self, label, description=""): + ''' + + :param label: + :param description: + ''' PropertyType.__init__(self) self.label = label self.configurable = True @@ -43,6 +77,12 @@ class Property(object): class Kettle(PropertyType): def __init__(self, label, description=""): + ''' + + :param label: + :param description: + ''' + PropertyType.__init__(self) self.label = label self.configurable = True diff --git a/core/controller/actor_controller.py b/core/controller/actor_controller.py index 7375bc6..7da1cc0 100644 --- a/core/controller/actor_controller.py +++ b/core/controller/actor_controller.py @@ -90,15 +90,22 @@ 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") - def on(self, id, power=100, **kwargs) -> None: + def on(self, id , power=100, **kwargs) -> None: ''' Method to switch an actor on. Supporting Event Topic "actor/+/on" - :param id: the actor id + :param actor_id: the actor id :param power: as integer value between 1 and 100 :param kwargs: :return: @@ -107,7 +114,7 @@ class ActorController(ActorHttp, CRUDController): id = int(id) if id in self.cache: print("POWER ON") - actor = self.cache[id].instance + actor = self.cache[id ].instance print("ONNNNN", actor) actor.on(power) diff --git a/core/controller/crud_controller.py b/core/controller/crud_controller.py index 2f1d9cc..3b2d271 100644 --- a/core/controller/crud_controller.py +++ b/core/controller/crud_controller.py @@ -9,6 +9,10 @@ class CRUDController(object): self.cache = {} async def init(self): + ''' + + :return: + ''' if self.caching is True: self.cache = await self.model.get_all() @@ -24,19 +28,35 @@ class CRUDController(object): return self.cache async def get_one(self, id): - + ''' + + :param id: + :return: + ''' return self.cache.get(id) - - - async def _pre_add_callback(self, data): + ''' + + :param data: + :return: + ''' pass async def _post_add_callback(self, m): + ''' + + :param m: + :return: + ''' pass async def add(self, **data): + ''' + + :param data: + :return: + ''' await self._pre_add_callback(data) m = await self.model.insert(**data) await self._post_add_callback(m) @@ -50,6 +70,12 @@ class CRUDController(object): pass async def update(self, id, data): + ''' + + :param id: + :param data: + :return: + ''' id = int(id) data["id"] = id @@ -71,18 +97,29 @@ class CRUDController(object): - - - - - async def _pre_delete_callback(self, m): + ''' + + :param m: + :return: + ''' pass async def _post_delete_callback(self, id): + ''' + + :param id: + :return: + ''' pass async def delete(self, id): + + ''' + + :param id: + :return: + ''' await self._pre_delete_callback(id) m = await self.model.delete(id) await self._post_delete_callback(id) @@ -95,6 +132,10 @@ class CRUDController(object): #self.cbpi.push("DELETE_%s" % self.key, id) async def delete_all(self): + ''' + + :return: + ''' self.model.delete_all() if self.caching is True: self.cache = {} diff --git a/core/controller/kettle_controller.py b/core/controller/kettle_controller.py index b92db10..d185595 100644 --- a/core/controller/kettle_controller.py +++ b/core/controller/kettle_controller.py @@ -11,12 +11,12 @@ class KettleHttp(HttpAPI): @request_mapping(path="/types", auth_required=False) async def get_types(self, request): - web.json_response(data=self.types, dumps=json_dumps) + web.json_response(data=self.cbpi.kettle.types, dumps=json_dumps) @request_mapping(path="/{id:\d+}/automatic", auth_required=False) async def start2(self, request): id = int(request.match_info['id']) - result = await self.toggle_automtic(id) + result = await self.cbpi.kettle.toggle_automtic(id) if result[0] is True: return web.Response(text="OK") else: @@ -25,15 +25,15 @@ class KettleHttp(HttpAPI): @request_mapping(path="/{id:\d+}/heater", auth_required=False) async def start(self, request): id = int(request.match_info['id']) - result = await self.heater_on(id) + result = await self.cbpi.kettle.heater_on(id) if result[0] is True: return web.Response(text="OK") else: return web.Response(status=404, text=result[1]) -class KettleController(CRUDController, KettleHttp): +class KettleController(CRUDController): ''' - The main actor controller + The main kettle controller ''' model = KettleModel @@ -41,7 +41,9 @@ class KettleController(CRUDController, KettleHttp): super(KettleController, self).__init__(cbpi) self.cbpi = cbpi self.types = {} - self.cbpi.register(self, "/kettle") + self.cbpi.register(self, None) + self.http = KettleHttp(cbpi) + self.cbpi.register(self.http, "/kettle") @@ -56,8 +58,10 @@ class KettleController(CRUDController, KettleHttp): async def toggle_automtic(self, id): ''' - :param id: - :return: + Convenience Method to toggle automatic + + :param id: kettle id as int + :return: (boolean, string) ''' kettle = await self.get_one(id) if kettle is None: diff --git a/core/controller/step_controller.py b/core/controller/step_controller.py index e69de29..d7a1a56 100644 --- a/core/controller/step_controller.py +++ b/core/controller/step_controller.py @@ -0,0 +1,8 @@ +class StepController(): + + + async def start(self): + pass + + async def stop(self): + pass diff --git a/core/craftbeerpi.py b/core/craftbeerpi.py index be5d83f..7c66f15 100644 --- a/core/craftbeerpi.py +++ b/core/craftbeerpi.py @@ -74,7 +74,7 @@ class CraftBeerPi(): doc["topic"] = method.__getattribute__("topic") except: pass - self.bus.register(method.__getattribute__("topic"), method, doc) + self.bus.register(method.__getattribute__("topic"), method) def register_background_task(self, obj): ''' @@ -234,7 +234,7 @@ class CraftBeerPi(): await self.kettle.init() async def load_plugins(app): - await PluginController.load_plugin_list() + #await PluginController.load_plugin_list() await self.plugin.load_plugins() async def call_initializer(app): diff --git a/core/eventbus.py b/core/eventbus.py index 73ec8bb..7a75e82 100644 --- a/core/eventbus.py +++ b/core/eventbus.py @@ -1,8 +1,14 @@ import inspect import logging +import asyncio + class EventBus(object): + + + + class Node(object): __slots__ = '_children', '_content' @@ -10,18 +16,39 @@ class EventBus(object): self._children = {} self._content = None - def register(self, key, value, doc=None): - if doc is not None: - self.docs[key] = doc - self.logger.info("key %s", key) + + 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 key.split('/'): + for sym in topic.split('/'): node = node._children.setdefault(sym, self.Node()) if not isinstance(node._content, list): node._content = [] - node._content.append(value) + + + 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: @@ -34,20 +61,27 @@ class EventBus(object): except KeyError: raise KeyError(key) - def unregister(self, key, method=None): + 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 unregister(self, key, method): lst = [] try: parent, node = None, self._root for k in key.split('/'): parent, node = node, node._children[k] lst.append((parent, k, node)) - # TODO - print(node._content) - if method is not None: - node._content = None - else: - node._content = None except KeyError: raise KeyError(key) else: # cleanup @@ -55,23 +89,43 @@ class EventBus(object): if node._children or node._content is not None: break del parent._children[k] + ''' def __init__(self, cbpi): self.logger = logging.getLogger(__name__) self._root = self.Node() + self.registry = {} self.docs = {} self.cbpi = cbpi def fire(self, topic: str, **kwargs) -> None: - + print("#### FIRE", topic) self.logger.info("EMIT EVENT %s", topic) - for methods in self.iter_match(topic): - for f in methods: - if inspect.iscoroutinefunction(f): - print("ITS ASYNC") - self.cbpi.app.loop.create_task(f(**kwargs, topic = topic)) + + cleanup_methods = [] + for content_array in self.iter_match(topic): + + print(content_array) + cleanup = [] + for idx, content_obj in enumerate(content_array): + print("#################") + + print("TOPIC", content_obj.method, content_obj.topic) + print("#################") + if inspect.iscoroutinefunction(content_obj.method): + self.cbpi.app.loop.create_task(content_obj.method(**kwargs, topic = topic)) else: - f(**kwargs, topic = topic) + content_obj.method(**kwargs, topic = topic) + + if content_obj.once is True: + cleanup.append(idx) + for idx in cleanup: + del content_array[idx] + + + print(self._root) + print("#### FIRE END ######") + def iter_match(self, topic): diff --git a/core/extension/dummylogic/__init__.py b/core/extension/dummylogic/__init__.py index 4f59580..88f6593 100644 --- a/core/extension/dummylogic/__init__.py +++ b/core/extension/dummylogic/__init__.py @@ -1,6 +1,6 @@ import asyncio -from core.api import Property +from core.api import Property, on_event from core.api.kettle_logic import CBPiKettleLogic @@ -11,17 +11,64 @@ class CustomLogic(CBPiKettleLogic): running = True + + async def wait_for_event(self, topic, timeout=None): + + + future_obj = self.cbpi.app.loop.create_future() + + async def callback(id, **kwargs): + print("---------------------------------- CALLBACK ----------------") + print(kwargs) + + + + if int(id) == 1: + self.cbpi.bus.unregister(callback) + future_obj.set_result("HELLO") + elif int(id) == 2: + self.cbpi.bus.unregister(callback) + else: + print("ID", id) + + + + print("TOPIC", topic) + self.cbpi.bus.register(topic=topic, method=callback) + + if timeout is not None: + + try: + print("----> WAIT FOR FUTURE") + await asyncio.wait_for(future_obj, timeout=10.0) + print("------> RETURN RESULT") + return future_obj.result() + except asyncio.TimeoutError: + print('timeout!') + else: + print("----> WAIT FOR FUTURE") + await future_obj + return future_obj.result() + + + async def run(self): - while self.running: + result = await self.wait_for_event("actor/+/on") + print("THE RESULT", result) + + ''' + while self.running: + + print("RUN", self.test) value = await self.cbpi.sensor.get_value(1) print(value) if value >= 10: break await asyncio.sleep(1) - + ''' print("STOP LOGIC") def setup(cbpi): diff --git a/docs_src/source/actor.rst b/docs_src/source/actor.rst index 7e593a4..218d65b 100644 --- a/docs_src/source/actor.rst +++ b/docs_src/source/actor.rst @@ -11,14 +11,27 @@ Architecture ActorController ^^^^^^^^^^^^^^^ -.. automodule:: core.controller.actor_controller +.. autoclass:: core.controller.actor_controller.ActorController :members: - :inherited-members: + :private-members: + :undoc-members: + :show-inheritance: + + +CBPiActor +^^^^^^^^^ + +.. autoclass:: core.api.actor.CBPiActor + :members: + :private-members: + :undoc-members: + :show-inheritance: + Custom Actor ^^^^^^^^^^^^^ -.. literalinclude:: ../../core/extension/dummy/__init__.py +.. literalinclude:: ../../core/extension/dummyactor/__init__.py :caption: __init__.py :name: __init__-py :language: python @@ -27,6 +40,6 @@ Custom Actor config.yaml -.. literalinclude:: ../../core/extension/dummy/config.yaml +.. literalinclude:: ../../core/extension/dummyactor/config.yaml :language: yaml :linenos: \ No newline at end of file diff --git a/docs_src/source/conf.py b/docs_src/source/conf.py index b8f9d1b..9e6916f 100644 --- a/docs_src/source/conf.py +++ b/docs_src/source/conf.py @@ -75,8 +75,8 @@ pygments_style = None # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -#html_theme = 'alabaster' -html_theme = 'sphinx_rtd_theme' +html_theme = 'alabaster' +#html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs_src/source/index.rst b/docs_src/source/index.rst index e48c00d..20eae43 100644 --- a/docs_src/source/index.rst +++ b/docs_src/source/index.rst @@ -14,7 +14,8 @@ Welcome to CraftBeerPi's documentation! core actor sensor - + kettle_controller + properties .. diff --git a/docs_src/source/kettle_controller.rst b/docs_src/source/kettle_controller.rst new file mode 100644 index 0000000..c44792d --- /dev/null +++ b/docs_src/source/kettle_controller.rst @@ -0,0 +1,39 @@ +Kettle +================ + + + +KettleController +^^^^^^^^^^^^^^^^ + +.. autoclass:: core.controller.kettle_controller.KettleController + :members: + :private-members: + :undoc-members: + :show-inheritance: + +CBPiKettleLogic +^^^^^^^^^^^^^^^ + + +.. autoclass:: core.api.kettle_logic.CBPiKettleLogic + :members: + :show-inheritance: + :inherited-members: + + +Custom Logic +^^^^^^^^^^^^^ + +.. literalinclude:: ../../core/extension/dummylogic/__init__.py + :caption: __init__.py + :name: __init__-py + :language: python + :linenos: + + +config.yaml + +.. literalinclude:: ../../core/extension/dummylogic/config.yaml + :language: yaml + :linenos: \ No newline at end of file diff --git a/docs_src/source/properties.rst b/docs_src/source/properties.rst new file mode 100644 index 0000000..cf4bf1b --- /dev/null +++ b/docs_src/source/properties.rst @@ -0,0 +1,10 @@ +Properties +=========== + + +.. autoclass:: core.api.property.Property + :members: + :private-members: + :undoc-members: + :show-inheritance: + diff --git a/main2.py b/main2.py new file mode 100644 index 0000000..23a7a00 --- /dev/null +++ b/main2.py @@ -0,0 +1,80 @@ +import asyncio + +from core.eventbus import EventBus + + +async def waiter(event): + print('waiting for it ...') + await asyncio.sleep(4) + print('... got it!') + event.set() + +async def main(loop): + # Create an Event object. + event = asyncio.Event() + + # Spawn a Task to wait until 'event' is set. + #waiter_task = asyncio.create_task(waiter(event)) + + waiter_task = loop.create_task(waiter(event)) + print("WAIT FOR EVENT") + await event.wait() + + + + # Wait until the waiter task is finished. + await waiter_task + +#loop = asyncio.get_event_loop() +#loop.run_until_complete(main(loop)) +#loop.close() + + +bus = EventBus(None) + +def test(**kwargs): + print("") + print("------------> HALLO WILD CARD") + print("") + print(hex(id(test))) + print("BUS",bus) + + +def test2(**kwargs): + print("------------> HALLO NAME") + + + +class Name(): + + def test(self, **kwargs): + print("---->OK") + bus.unregister(self.test) + print("#################### ID2", hex(id(n.test))) + + +n = Name() +print("the ID", hex(id(n.test))) +id1 = bus.register("test/#", n.test) +print("the ID2", hex(id(n.test))) + +if n.test == n.test: + print("SAME") +id1 = bus.register("test/#", test) +id2 = bus.register("test/name", test2) + +print(id1, id2) + + + +print(hex(id(test2))) + +print(bus.get_callbacks("test/name")) + +bus.fire("test/name") + +bus.fire("test/name") + +bus.unregister(test2) + +bus.fire("test/name") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 12b8868..c0f591f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,33 +6,48 @@ aiohttp-session==2.7.0 aiohttp-swagger==1.0.5 aiojobs==0.2.2 aiosqlite==0.7.0 +alabaster==0.7.12 asn1crypto==0.24.0 async-timeout==3.0.1 atomicwrites==1.2.1 attrs==18.2.0 +Babel==2.6.0 +certifi==2018.10.15 cffi==1.11.5 chardet==3.0.4 cryptography==2.3.1 docopt==0.6.2 +docutils==0.14 hbmqtt==0.9.4 idna==2.7 idna-ssl==1.1.0 +imagesize==1.1.0 Jinja2==2.10 MarkupSafe==1.0 more-itertools==4.3.0 multidict==4.4.2 +packaging==18.0 passlib==1.7.1 pluggy==0.7.1 py==1.7.0 pycparser==2.19 +pyfiglet==0.7.6 +Pygments==2.2.0 pync==2.0.3 +pyparsing==2.3.0 pytest==3.8.2 pytest-aiohttp==0.3.0 python-dateutil==2.7.5 +pytz==2018.7 PyYAML==3.13 +requests==2.20.1 six==1.11.0 +snowballstemmer==1.2.1 +Sphinx==1.8.2 +sphinx-rtd-theme==0.4.2 +sphinxcontrib-websupport==1.1.0 ticket-auth==0.1.4 transitions==0.6.8 +urllib3==1.24.1 websockets==6.0 yarl==1.2.6 -pyfiglet \ No newline at end of file