Event Bus unregister added

This commit is contained in:
manuel83 2018-11-29 21:59:08 +01:00
parent 4f74b39e39
commit 35ee2fbad9
20 changed files with 922 additions and 966 deletions

View file

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.6.1 virtualenv at ~/cbpi41" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.7.1 virtualenv at ~/cbp42" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="TestRunnerService"> <component name="TestRunnerService">

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6.1 virtualenv at ~/cbpi41" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7.1 virtualenv at ~/cbp42" project-jdk-type="Python SDK" />
</project> </project>

File diff suppressed because it is too large Load diff

View file

@ -44,6 +44,7 @@ def on_event(topic):
def real_decorator(func): def real_decorator(func):
func.eventbus = True func.eventbus = True
func.topic = topic func.topic = topic
func.c = None
return func return func
return real_decorator return real_decorator
@ -51,6 +52,7 @@ def on_event(topic):
def action(key, parameters): def action(key, parameters):
def real_decorator(func): def real_decorator(func):
func.action = True func.action = True
func.key = key func.key = key
func.parameters = parameters func.parameters = parameters
return func return func

View file

@ -3,4 +3,29 @@ from core.api.extension import CBPiExtension
class CBPiKettleLogic(CBPiExtension): class CBPiKettleLogic(CBPiExtension):
'''
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 pass

View file

@ -5,7 +5,14 @@ class PropertyType(object):
class Property(object): class Property(object):
class Select(PropertyType): class Select(PropertyType):
def __init__(self, label, options, description=""): def __init__(self, label, options, description=""):
'''
:param label:
:param options:
:param description:
'''
PropertyType.__init__(self) PropertyType.__init__(self)
self.label = label self.label = label
self.options = options self.options = options
@ -13,6 +20,16 @@ class Property(object):
class Number(PropertyType): class Number(PropertyType):
def __init__(self, label, configurable=False, default_value=None, unit="", description=""): 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) PropertyType.__init__(self)
self.label = label self.label = label
self.configurable = configurable self.configurable = configurable
@ -21,6 +38,13 @@ class Property(object):
class Text(PropertyType): class Text(PropertyType):
def __init__(self, label, configurable=False, default_value="", description=""): def __init__(self, label, configurable=False, default_value="", description=""):
'''
:param label:
:param configurable:
:param default_value:
:param description:
'''
PropertyType.__init__(self) PropertyType.__init__(self)
self.label = label self.label = label
self.configurable = configurable self.configurable = configurable
@ -29,6 +53,11 @@ class Property(object):
class Actor(PropertyType): class Actor(PropertyType):
def __init__(self, label, description=""): def __init__(self, label, description=""):
'''
:param label:
:param description:
'''
PropertyType.__init__(self) PropertyType.__init__(self)
self.label = label self.label = label
self.configurable = True self.configurable = True
@ -36,6 +65,11 @@ class Property(object):
class Sensor(PropertyType): class Sensor(PropertyType):
def __init__(self, label, description=""): def __init__(self, label, description=""):
'''
:param label:
:param description:
'''
PropertyType.__init__(self) PropertyType.__init__(self)
self.label = label self.label = label
self.configurable = True self.configurable = True
@ -43,6 +77,12 @@ class Property(object):
class Kettle(PropertyType): class Kettle(PropertyType):
def __init__(self, label, description=""): def __init__(self, label, description=""):
'''
:param label:
:param description:
'''
PropertyType.__init__(self) PropertyType.__init__(self)
self.label = label self.label = label
self.configurable = True self.configurable = True

View file

@ -90,15 +90,22 @@ class ActorController(ActorHttp, CRUDController):
self.cache[id].instance = clazz(**cfg) self.cache[id].instance = clazz(**cfg)
print("gpIO", self.cache[id].instance, self.cache[id].instance.gpio) 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/+/on")
def on(self, id, power=100, **kwargs) -> None: def on(self, id , power=100, **kwargs) -> None:
''' '''
Method to switch an actor on. Method to switch an actor on.
Supporting Event Topic "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 power: as integer value between 1 and 100
:param kwargs: :param kwargs:
:return: :return:
@ -107,7 +114,7 @@ class ActorController(ActorHttp, CRUDController):
id = int(id) id = int(id)
if id in self.cache: if id in self.cache:
print("POWER ON") print("POWER ON")
actor = self.cache[id].instance actor = self.cache[id ].instance
print("ONNNNN", actor) print("ONNNNN", actor)
actor.on(power) actor.on(power)

View file

@ -9,6 +9,10 @@ class CRUDController(object):
self.cache = {} self.cache = {}
async def init(self): async def init(self):
'''
:return:
'''
if self.caching is True: if self.caching is True:
self.cache = await self.model.get_all() self.cache = await self.model.get_all()
@ -24,19 +28,35 @@ class CRUDController(object):
return self.cache return self.cache
async def get_one(self, id): async def get_one(self, id):
'''
:param id:
:return:
'''
return self.cache.get(id) return self.cache.get(id)
async def _pre_add_callback(self, data): async def _pre_add_callback(self, data):
'''
:param data:
:return:
'''
pass pass
async def _post_add_callback(self, m): async def _post_add_callback(self, m):
'''
:param m:
:return:
'''
pass pass
async def add(self, **data): async def add(self, **data):
'''
:param data:
:return:
'''
await self._pre_add_callback(data) await self._pre_add_callback(data)
m = await self.model.insert(**data) m = await self.model.insert(**data)
await self._post_add_callback(m) await self._post_add_callback(m)
@ -50,6 +70,12 @@ class CRUDController(object):
pass pass
async def update(self, id, data): async def update(self, id, data):
'''
:param id:
:param data:
:return:
'''
id = int(id) id = int(id)
data["id"] = id data["id"] = id
@ -71,18 +97,29 @@ class CRUDController(object):
async def _pre_delete_callback(self, m): async def _pre_delete_callback(self, m):
'''
:param m:
:return:
'''
pass pass
async def _post_delete_callback(self, id): async def _post_delete_callback(self, id):
'''
:param id:
:return:
'''
pass pass
async def delete(self, id): async def delete(self, id):
'''
:param id:
:return:
'''
await self._pre_delete_callback(id) await self._pre_delete_callback(id)
m = await self.model.delete(id) m = await self.model.delete(id)
await self._post_delete_callback(id) await self._post_delete_callback(id)
@ -95,6 +132,10 @@ class CRUDController(object):
#self.cbpi.push("DELETE_%s" % self.key, id) #self.cbpi.push("DELETE_%s" % self.key, id)
async def delete_all(self): async def delete_all(self):
'''
:return:
'''
self.model.delete_all() self.model.delete_all()
if self.caching is True: if self.caching is True:
self.cache = {} self.cache = {}

View file

@ -11,12 +11,12 @@ class KettleHttp(HttpAPI):
@request_mapping(path="/types", auth_required=False) @request_mapping(path="/types", auth_required=False)
async def get_types(self, request): 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) @request_mapping(path="/{id:\d+}/automatic", auth_required=False)
async def start2(self, request): async def start2(self, request):
id = int(request.match_info['id']) 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: if result[0] is True:
return web.Response(text="OK") return web.Response(text="OK")
else: else:
@ -25,15 +25,15 @@ class KettleHttp(HttpAPI):
@request_mapping(path="/{id:\d+}/heater", auth_required=False) @request_mapping(path="/{id:\d+}/heater", auth_required=False)
async def start(self, request): async def start(self, request):
id = int(request.match_info['id']) 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: if result[0] is True:
return web.Response(text="OK") return web.Response(text="OK")
else: else:
return web.Response(status=404, text=result[1]) 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 model = KettleModel
@ -41,7 +41,9 @@ class KettleController(CRUDController, KettleHttp):
super(KettleController, self).__init__(cbpi) super(KettleController, self).__init__(cbpi)
self.cbpi = cbpi self.cbpi = cbpi
self.types = {} 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): async def toggle_automtic(self, id):
''' '''
:param id: Convenience Method to toggle automatic
:return:
:param id: kettle id as int
:return: (boolean, string)
''' '''
kettle = await self.get_one(id) kettle = await self.get_one(id)
if kettle is None: if kettle is None:

View file

@ -0,0 +1,8 @@
class StepController():
async def start(self):
pass
async def stop(self):
pass

View file

@ -74,7 +74,7 @@ class CraftBeerPi():
doc["topic"] = method.__getattribute__("topic") doc["topic"] = method.__getattribute__("topic")
except: except:
pass pass
self.bus.register(method.__getattribute__("topic"), method, doc) self.bus.register(method.__getattribute__("topic"), method)
def register_background_task(self, obj): def register_background_task(self, obj):
''' '''
@ -234,7 +234,7 @@ class CraftBeerPi():
await self.kettle.init() await self.kettle.init()
async def load_plugins(app): async def load_plugins(app):
await PluginController.load_plugin_list() #await PluginController.load_plugin_list()
await self.plugin.load_plugins() await self.plugin.load_plugins()
async def call_initializer(app): async def call_initializer(app):

View file

@ -1,8 +1,14 @@
import inspect import inspect
import logging import logging
import asyncio
class EventBus(object): class EventBus(object):
class Node(object): class Node(object):
__slots__ = '_children', '_content' __slots__ = '_children', '_content'
@ -10,18 +16,39 @@ class EventBus(object):
self._children = {} self._children = {}
self._content = None self._content = None
def register(self, key, value, doc=None):
if doc is not None:
self.docs[key] = doc class Content(object):
self.logger.info("key %s", key) 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 node = self._root
for sym in key.split('/'): for sym in topic.split('/'):
node = node._children.setdefault(sym, self.Node()) node = node._children.setdefault(sym, self.Node())
if not isinstance(node._content, list): if not isinstance(node._content, list):
node._content = [] 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): def get_callbacks(self, key):
try: try:
@ -34,20 +61,27 @@ class EventBus(object):
except KeyError: except KeyError:
raise KeyError(key) 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 = [] lst = []
try: try:
parent, node = None, self._root parent, node = None, self._root
for k in key.split('/'): for k in key.split('/'):
parent, node = node, node._children[k] parent, node = node, node._children[k]
lst.append((parent, k, node)) lst.append((parent, k, node))
# TODO
print(node._content)
if method is not None:
node._content = None
else:
node._content = None
except KeyError: except KeyError:
raise KeyError(key) raise KeyError(key)
else: # cleanup else: # cleanup
@ -55,23 +89,43 @@ class EventBus(object):
if node._children or node._content is not None: if node._children or node._content is not None:
break break
del parent._children[k] del parent._children[k]
'''
def __init__(self, cbpi): def __init__(self, cbpi):
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self._root = self.Node() self._root = self.Node()
self.registry = {}
self.docs = {} self.docs = {}
self.cbpi = cbpi self.cbpi = cbpi
def fire(self, topic: str, **kwargs) -> None: def fire(self, topic: str, **kwargs) -> None:
print("#### FIRE", topic)
self.logger.info("EMIT EVENT %s", topic) self.logger.info("EMIT EVENT %s", topic)
for methods in self.iter_match(topic):
for f in methods: cleanup_methods = []
if inspect.iscoroutinefunction(f): for content_array in self.iter_match(topic):
print("ITS ASYNC")
self.cbpi.app.loop.create_task(f(**kwargs, topic = 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: 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): def iter_match(self, topic):

View file

@ -1,6 +1,6 @@
import asyncio import asyncio
from core.api import Property from core.api import Property, on_event
from core.api.kettle_logic import CBPiKettleLogic from core.api.kettle_logic import CBPiKettleLogic
@ -11,17 +11,64 @@ class CustomLogic(CBPiKettleLogic):
running = True 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): async def run(self):
result = await self.wait_for_event("actor/+/on")
print("THE RESULT", result)
'''
while self.running: while self.running:
print("RUN", self.test) print("RUN", self.test)
value = await self.cbpi.sensor.get_value(1) value = await self.cbpi.sensor.get_value(1)
print(value) print(value)
if value >= 10: if value >= 10:
break break
await asyncio.sleep(1) await asyncio.sleep(1)
'''
print("STOP LOGIC") print("STOP LOGIC")
def setup(cbpi): def setup(cbpi):

View file

@ -11,14 +11,27 @@ Architecture
ActorController ActorController
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
.. automodule:: core.controller.actor_controller .. autoclass:: core.controller.actor_controller.ActorController
:members: :members:
:inherited-members: :private-members:
:undoc-members:
:show-inheritance:
CBPiActor
^^^^^^^^^
.. autoclass:: core.api.actor.CBPiActor
:members:
:private-members:
:undoc-members:
:show-inheritance:
Custom Actor Custom Actor
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
.. literalinclude:: ../../core/extension/dummy/__init__.py .. literalinclude:: ../../core/extension/dummyactor/__init__.py
:caption: __init__.py :caption: __init__.py
:name: __init__-py :name: __init__-py
:language: python :language: python
@ -27,6 +40,6 @@ Custom Actor
config.yaml config.yaml
.. literalinclude:: ../../core/extension/dummy/config.yaml .. literalinclude:: ../../core/extension/dummyactor/config.yaml
:language: yaml :language: yaml
:linenos: :linenos:

View file

@ -75,8 +75,8 @@ pygments_style = None
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
# #
#html_theme = 'alabaster' html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme' #html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a 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 # further. For a list of options available for each theme, see the

View file

@ -14,7 +14,8 @@ Welcome to CraftBeerPi's documentation!
core core
actor actor
sensor sensor
kettle_controller
properties
.. ..

View file

@ -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:

View file

@ -0,0 +1,10 @@
Properties
===========
.. autoclass:: core.api.property.Property
:members:
:private-members:
:undoc-members:
:show-inheritance:

80
main2.py Normal file
View file

@ -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")

View file

@ -6,33 +6,48 @@ aiohttp-session==2.7.0
aiohttp-swagger==1.0.5 aiohttp-swagger==1.0.5
aiojobs==0.2.2 aiojobs==0.2.2
aiosqlite==0.7.0 aiosqlite==0.7.0
alabaster==0.7.12
asn1crypto==0.24.0 asn1crypto==0.24.0
async-timeout==3.0.1 async-timeout==3.0.1
atomicwrites==1.2.1 atomicwrites==1.2.1
attrs==18.2.0 attrs==18.2.0
Babel==2.6.0
certifi==2018.10.15
cffi==1.11.5 cffi==1.11.5
chardet==3.0.4 chardet==3.0.4
cryptography==2.3.1 cryptography==2.3.1
docopt==0.6.2 docopt==0.6.2
docutils==0.14
hbmqtt==0.9.4 hbmqtt==0.9.4
idna==2.7 idna==2.7
idna-ssl==1.1.0 idna-ssl==1.1.0
imagesize==1.1.0
Jinja2==2.10 Jinja2==2.10
MarkupSafe==1.0 MarkupSafe==1.0
more-itertools==4.3.0 more-itertools==4.3.0
multidict==4.4.2 multidict==4.4.2
packaging==18.0
passlib==1.7.1 passlib==1.7.1
pluggy==0.7.1 pluggy==0.7.1
py==1.7.0 py==1.7.0
pycparser==2.19 pycparser==2.19
pyfiglet==0.7.6
Pygments==2.2.0
pync==2.0.3 pync==2.0.3
pyparsing==2.3.0
pytest==3.8.2 pytest==3.8.2
pytest-aiohttp==0.3.0 pytest-aiohttp==0.3.0
python-dateutil==2.7.5 python-dateutil==2.7.5
pytz==2018.7
PyYAML==3.13 PyYAML==3.13
requests==2.20.1
six==1.11.0 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 ticket-auth==0.1.4
transitions==0.6.8 transitions==0.6.8
urllib3==1.24.1
websockets==6.0 websockets==6.0
yarl==1.2.6 yarl==1.2.6
pyfiglet