mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-25 00:18:17 +01:00
event bus changed to async
This commit is contained in:
parent
a8060fff08
commit
5e701e6d61
21 changed files with 799 additions and 758 deletions
1258
.idea/workspace.xml
1258
.idea/workspace.xml
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,5 @@
|
||||||
|
from abc import ABCMeta
|
||||||
|
|
||||||
__all__ = ["CBPiActor"]
|
__all__ = ["CBPiActor"]
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -6,7 +8,13 @@ from core.api.extension import CBPiExtension
|
||||||
|
|
||||||
logger = logging.getLogger(__file__)
|
logger = logging.getLogger(__file__)
|
||||||
|
|
||||||
class CBPiActor(CBPiExtension):
|
class CBPiActor(CBPiExtension, metaclass=ABCMeta):
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def on(self, power):
|
def on(self, power):
|
||||||
'''
|
'''
|
||||||
|
@ -34,4 +42,7 @@ class CBPiActor(CBPiExtension):
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def reprJSON(self):
|
||||||
|
return dict(state=True)
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from core.utils.utils import load_config as load
|
|
||||||
|
|
||||||
__all__ = ["CBPiExtension"]
|
__all__ = ["CBPiExtension"]
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ class CBPiExtension():
|
||||||
super(CBPiExtension, self).__setattr__(name, value)
|
super(CBPiExtension, self).__setattr__(name, value)
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
|
from core.utils.utils import load_config as load
|
||||||
path = os.path.dirname(sys.modules[self.__class__.__module__].__file__)
|
path = os.path.dirname(sys.modules[self.__class__.__module__].__file__)
|
||||||
try:
|
try:
|
||||||
return load("%s/config.yaml" % path)
|
return load("%s/config.yaml" % path)
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import pprint
|
||||||
|
from asyncio import Future
|
||||||
|
import asyncio
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from core.api.actor import CBPiActor
|
from core.api.actor import CBPiActor
|
||||||
|
@ -17,7 +20,11 @@ class ActorHttp(HttpAPI):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
id = int(request.match_info['id'])
|
id = int(request.match_info['id'])
|
||||||
self.cbpi.bus.fire(topic="actor/%s/switch/on" % id, id=id, power=99)
|
result = await self.cbpi.bus.fire2(topic="actor/%s/switch/on" % id, id=id, power=99)
|
||||||
|
print(result.timeout)
|
||||||
|
|
||||||
|
for key, value in result.results.items():
|
||||||
|
print(key, value.result)
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +35,7 @@ class ActorHttp(HttpAPI):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
id = int(request.match_info['id'])
|
id = int(request.match_info['id'])
|
||||||
self.cbpi.bus.fire(topic="actor/%s/off" % id, id=id)
|
await self.cbpi.bus.fire(topic="actor/%s/off" % id, id=id)
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
||||||
@request_mapping(path="/{id:\d+}/toggle", auth_required=False)
|
@request_mapping(path="/{id:\d+}/toggle", auth_required=False)
|
||||||
|
@ -39,7 +46,7 @@ class ActorHttp(HttpAPI):
|
||||||
"""
|
"""
|
||||||
id = int(request.match_info['id'])
|
id = int(request.match_info['id'])
|
||||||
print("ID", id)
|
print("ID", id)
|
||||||
self.cbpi.bus.fire(topic="actor/%s/toggle" % id, id=id)
|
await self.cbpi.bus.fire(topic="actor/%s/toggle" % id, id=id)
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
||||||
class ActorController(ActorHttp, CRUDController):
|
class ActorController(ActorHttp, CRUDController):
|
||||||
|
@ -58,16 +65,6 @@ class ActorController(ActorHttp, CRUDController):
|
||||||
self.types = {}
|
self.types = {}
|
||||||
self.actors = {}
|
self.actors = {}
|
||||||
|
|
||||||
|
|
||||||
def register(self, name, clazz) -> None:
|
|
||||||
|
|
||||||
print("REGISTER", name)
|
|
||||||
if issubclass(clazz, CBPiActor):
|
|
||||||
print("ITS AN ACTOR")
|
|
||||||
|
|
||||||
parse_props(clazz)
|
|
||||||
self.types[name] = clazz
|
|
||||||
|
|
||||||
async def init(self):
|
async def init(self):
|
||||||
'''
|
'''
|
||||||
This method initializes all actors during startup. It creates actor instances
|
This method initializes all actors during startup. It creates actor instances
|
||||||
|
@ -76,25 +73,28 @@ class ActorController(ActorHttp, CRUDController):
|
||||||
'''
|
'''
|
||||||
await super(ActorController, self).init()
|
await super(ActorController, self).init()
|
||||||
|
|
||||||
for name, clazz in self.types.items():
|
|
||||||
print("Type", name)
|
|
||||||
|
|
||||||
for id, value in self.cache.items():
|
for id, value in self.cache.items():
|
||||||
|
await self._init_actor(value)
|
||||||
|
|
||||||
if value.type in self.types:
|
async def _init_actor(self, actor):
|
||||||
cfg = value.config.copy()
|
if actor.type in self.types:
|
||||||
|
cfg = actor.config.copy()
|
||||||
|
cfg.update(dict(cbpi=self.cbpi, id=id, name=actor.name))
|
||||||
|
clazz = self.types[actor.type]["class"];
|
||||||
|
|
||||||
cfg.update(dict(cbpi=self.cbpi, id=id, name=value.name))
|
self.cache[actor.id].instance = clazz(**cfg)
|
||||||
clazz = self.types[value.type]["class"];
|
self.cache[actor.id].instance.init()
|
||||||
|
await self.cbpi.bus.fire(topic="actor/%s/initialized" % actor.id, id=actor.id)
|
||||||
|
|
||||||
self.cache[id].instance = clazz(**cfg)
|
|
||||||
print("gpIO", self.cache[id].instance, self.cache[id].instance.gpio)
|
|
||||||
|
|
||||||
|
async def _stop_actor(self, actor):
|
||||||
|
actor.instance.stop()
|
||||||
|
await self.cbpi.bus.fire(topic="actor/%s/stopped" % actor.id, id=actor.id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@on_event(topic="actor/+/switch/on")
|
@on_event(topic="actor/+/switch/on")
|
||||||
def on(self, id , power=100, **kwargs) -> None:
|
async def on(self, id , future: Future, 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"
|
||||||
|
@ -109,11 +109,13 @@ class ActorController(ActorHttp, CRUDController):
|
||||||
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
|
||||||
self.cbpi.bus.fire("actor/%s/on/ok" % id)
|
await self.cbpi.bus.fire("actor/%s/on/ok" % id)
|
||||||
actor.on(power)
|
actor.on(power)
|
||||||
|
|
||||||
|
future.set_result("OK")
|
||||||
|
|
||||||
@on_event(topic="actor/+/toggle")
|
@on_event(topic="actor/+/toggle")
|
||||||
def toggle(self, id, power=100, **kwargs) -> None:
|
async def toggle(self, id, power=100, **kwargs) -> None:
|
||||||
'''
|
'''
|
||||||
Method to toggle an actor on or off
|
Method to toggle an actor on or off
|
||||||
Supporting Event Topic "actor/+/toggle"
|
Supporting Event Topic "actor/+/toggle"
|
||||||
|
@ -132,7 +134,7 @@ class ActorController(ActorHttp, CRUDController):
|
||||||
actor.on()
|
actor.on()
|
||||||
|
|
||||||
@on_event(topic="actor/+/off")
|
@on_event(topic="actor/+/off")
|
||||||
def off(self, id, **kwargs) -> None:
|
async def off(self, id, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Method to switch and actor off
|
Method to switch and actor off
|
||||||
|
@ -147,3 +149,27 @@ class ActorController(ActorHttp, CRUDController):
|
||||||
if id in self.cache:
|
if id in self.cache:
|
||||||
actor = self.cache[id].instance
|
actor = self.cache[id].instance
|
||||||
actor.off()
|
actor.off()
|
||||||
|
|
||||||
|
async def _post_add_callback(self, m):
|
||||||
|
'''
|
||||||
|
|
||||||
|
:param m:
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
await self._init_actor(m)
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _pre_delete_callback(self, actor_id):
|
||||||
|
if int(actor_id) not in self.cache:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.cache[int(actor_id)].instance is not None:
|
||||||
|
await self._stop_actor(self.cache[int(actor_id)])
|
||||||
|
|
||||||
|
async def _pre_update_callback(self, actor):
|
||||||
|
|
||||||
|
if actor.instance is not None:
|
||||||
|
await self._stop_actor(actor)
|
||||||
|
|
||||||
|
async def _post_update_callback(self, actor):
|
||||||
|
self._init_actor(actor)
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
import json
|
||||||
|
import pprint
|
||||||
from abc import abstractmethod,ABCMeta
|
from abc import abstractmethod,ABCMeta
|
||||||
|
|
||||||
|
from core.utils.encoder import ComplexEncoder
|
||||||
|
|
||||||
|
|
||||||
class CRUDController(metaclass=ABCMeta):
|
class CRUDController(metaclass=ABCMeta):
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,8 +66,11 @@ class CRUDController(metaclass=ABCMeta):
|
||||||
'''
|
'''
|
||||||
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)
|
|
||||||
self.cache[m.id] = m
|
self.cache[m.id] = m
|
||||||
|
await self._post_add_callback(m)
|
||||||
|
|
||||||
|
await self.cbpi.bus.fire(topic="actor/%s/added" % m.id, actor=m)
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
||||||
async def _pre_update_callback(self, m):
|
async def _pre_update_callback(self, m):
|
||||||
|
@ -117,21 +125,29 @@ class CRUDController(metaclass=ABCMeta):
|
||||||
|
|
||||||
async def delete(self, id):
|
async def delete(self, id):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
:param id:
|
:param id:
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
await self._pre_delete_callback(id)
|
await self._pre_delete_callback(id)
|
||||||
|
|
||||||
|
if id not in self.cache:
|
||||||
|
|
||||||
|
return
|
||||||
m = await self.model.delete(id)
|
m = await self.model.delete(id)
|
||||||
await self._post_delete_callback(id)
|
await self._post_delete_callback(id)
|
||||||
try:
|
try:
|
||||||
if self.caching is True:
|
if self.caching is True:
|
||||||
del self.cache[id]
|
print("DELTE FROM ACHE")
|
||||||
|
del self.cache[int(id)]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
pass
|
pass
|
||||||
|
pprint.pprint(self.cache)
|
||||||
#self.cbpi.push("DELETE_%s" % self.key, id)
|
await self.cbpi.bus.fire(topic="actor/%s/deleted" % id, id=id)
|
||||||
|
|
||||||
async def delete_all(self):
|
async def delete_all(self):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -72,11 +72,11 @@ class KettleController(CRUDController):
|
||||||
if kettle.logic is None:
|
if kettle.logic is None:
|
||||||
return (False, "No Logic defined")
|
return (False, "No Logic defined")
|
||||||
id = kettle.heater
|
id = kettle.heater
|
||||||
self.cbpi.bus.fire(topic="kettle/%s/automatic" % id, id=id)
|
await self.cbpi.bus.fire(topic="kettle/%s/automatic" % id, id=id)
|
||||||
return (True, "Logic switched on switched")
|
return (True, "Logic switched on switched")
|
||||||
|
|
||||||
@on_event(topic="job/done")
|
@on_event(topic="job/done")
|
||||||
def job_stop(self, key, **kwargs) -> None:
|
async def job_stop(self, key, **kwargs) -> None:
|
||||||
|
|
||||||
match = re.match("kettle_logic_(\d+)", key)
|
match = re.match("kettle_logic_(\d+)", key)
|
||||||
if match is not None:
|
if match is not None:
|
||||||
|
@ -136,7 +136,7 @@ class KettleController(CRUDController):
|
||||||
if kettle.heater is None:
|
if kettle.heater is None:
|
||||||
return (False, "No Heater defined")
|
return (False, "No Heater defined")
|
||||||
id = kettle.heater
|
id = kettle.heater
|
||||||
self.cbpi.bus.fire(topic="actor/%s/on" % id, id=id, power=99)
|
await self.cbpi.bus.fire(topic="actor/%s/on" % id, id=id, power=99)
|
||||||
return (True,"Heater switched on")
|
return (True,"Heater switched on")
|
||||||
|
|
||||||
async def heater_off(self, id):
|
async def heater_off(self, id):
|
||||||
|
@ -153,7 +153,7 @@ class KettleController(CRUDController):
|
||||||
if kettle.heater is None:
|
if kettle.heater is None:
|
||||||
return (False, "No Heater defined")
|
return (False, "No Heater defined")
|
||||||
id = kettle.heater
|
id = kettle.heater
|
||||||
self.cbpi.bus.fire(topic="actor/%s/off" % id, id=id, power=99)
|
await self.cbpi.bus.fire(topic="actor/%s/off" % id, id=id, power=99)
|
||||||
return (True, "Heater switched off")
|
return (True, "Heater switched off")
|
||||||
|
|
||||||
async def agitator_on(self, id):
|
async def agitator_on(self, id):
|
||||||
|
|
|
@ -17,5 +17,5 @@ class NotificationController():
|
||||||
|
|
||||||
|
|
||||||
@on_event(topic="notification/#")
|
@on_event(topic="notification/#")
|
||||||
def _on_event(self, key, message, type, **kwargs):
|
async def _on_event(self, key, message, type, **kwargs):
|
||||||
self.cbpi.ws.send("YES")
|
self.cbpi.ws.send("YES")
|
|
@ -42,7 +42,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
:param request: web requset
|
:param request: web requset
|
||||||
:return: web.Response(text="OK"
|
:return: web.Response(text="OK"
|
||||||
'''
|
'''
|
||||||
self.cbpi.bus.fire("step/action", action="test")
|
await self.cbpi.bus.fire("step/action", action="test")
|
||||||
return web.Response(text="OK")
|
return web.Response(text="OK")
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.cbpi.bus.fire("step/start")
|
await self.cbpi.bus.fire("step/start")
|
||||||
return web.Response(text="OK")
|
return web.Response(text="OK")
|
||||||
|
|
||||||
@request_mapping(path="/reset", auth_required=False)
|
@request_mapping(path="/reset", auth_required=False)
|
||||||
|
@ -67,7 +67,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
:param request:
|
:param request:
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
self.cbpi.bus.fire("step/reset")
|
await self.cbpi.bus.fire("step/reset")
|
||||||
return web.Response(text="OK")
|
return web.Response(text="OK")
|
||||||
|
|
||||||
@request_mapping(path="/next", auth_required=False)
|
@request_mapping(path="/next", auth_required=False)
|
||||||
|
@ -79,11 +79,11 @@ class StepController(HttpAPI, CRUDController):
|
||||||
:param request:
|
:param request:
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
self.cbpi.bus.fire("step/next")
|
await self.cbpi.bus.fire("step/next")
|
||||||
return web.Response(text="OK")
|
return web.Response(text="OK")
|
||||||
|
|
||||||
@on_event("step/action")
|
@on_event("step/action")
|
||||||
def handle_action(self, action, **kwargs):
|
async def handle_action(self, action, **kwargs):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Event Handler for "step/action".
|
Event Handler for "step/action".
|
||||||
|
@ -99,7 +99,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
|
|
||||||
|
|
||||||
@on_event("step/next")
|
@on_event("step/next")
|
||||||
def handle_next(self, **kwargs):
|
async def handle_next(self, **kwargs):
|
||||||
'''
|
'''
|
||||||
Event Handler for "step/next".
|
Event Handler for "step/next".
|
||||||
It start the next step
|
It start the next step
|
||||||
|
@ -114,7 +114,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
|
|
||||||
|
|
||||||
@on_event("step/start")
|
@on_event("step/start")
|
||||||
def handle_start(self, **kwargs):
|
async def handle_start(self, **kwargs):
|
||||||
'''
|
'''
|
||||||
Event Handler for "step/start".
|
Event Handler for "step/start".
|
||||||
It starts the brewing process
|
It starts the brewing process
|
||||||
|
@ -122,10 +122,10 @@ class StepController(HttpAPI, CRUDController):
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return: None
|
:return: None
|
||||||
'''
|
'''
|
||||||
self.start()
|
await self.start()
|
||||||
|
|
||||||
@on_event("step/reset")
|
@on_event("step/reset")
|
||||||
def handle_reset(self, **kwargs):
|
async def handle_reset(self, **kwargs):
|
||||||
'''
|
'''
|
||||||
Event Handler for "step/reset".
|
Event Handler for "step/reset".
|
||||||
Resets the current step
|
Resets the current step
|
||||||
|
@ -142,10 +142,10 @@ class StepController(HttpAPI, CRUDController):
|
||||||
self.steps[self.current_step.id]["state"] = None
|
self.steps[self.current_step.id]["state"] = None
|
||||||
self.current_step = None
|
self.current_step = None
|
||||||
self.current_task = None
|
self.current_task = None
|
||||||
self.start()
|
await self.start()
|
||||||
|
|
||||||
@on_event("step/stop")
|
@on_event("step/stop")
|
||||||
def handle_stop(self, **kwargs):
|
async def handle_stop(self, **kwargs):
|
||||||
'''
|
'''
|
||||||
Event Handler for "step/stop".
|
Event Handler for "step/stop".
|
||||||
Stops the current step
|
Stops the current step
|
||||||
|
@ -163,7 +163,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
self.current_step = None
|
self.current_step = None
|
||||||
|
|
||||||
@on_event("step/+/done")
|
@on_event("step/+/done")
|
||||||
def handle_done(self, topic, **kwargs):
|
async def handle_done(self, topic, **kwargs):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Event Handler for "step/+/done".
|
Event Handler for "step/+/done".
|
||||||
|
@ -173,7 +173,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
self.start()
|
await self.start()
|
||||||
|
|
||||||
def _step_done(self, task):
|
def _step_done(self, task):
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
self.cache[self.current_step.id].state = "D"
|
self.cache[self.current_step.id].state = "D"
|
||||||
step_id = self.current_step.id
|
step_id = self.current_step.id
|
||||||
self.current_step = None
|
self.current_step = None
|
||||||
self.cbpi.bus.fire("step/%s/done" % step_id)
|
self.cbpi.bus.sync_fire("step/%s/done" % step_id)
|
||||||
|
|
||||||
def _get_manged_fields_as_array(self, type_cfg):
|
def _get_manged_fields_as_array(self, type_cfg):
|
||||||
print("tYPE", type_cfg)
|
print("tYPE", type_cfg)
|
||||||
|
@ -191,7 +191,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def start(self):
|
async def start(self):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Start the first step
|
Start the first step
|
||||||
|
@ -215,7 +215,7 @@ class StepController(HttpAPI, CRUDController):
|
||||||
open_step = True
|
open_step = True
|
||||||
break
|
break
|
||||||
if open_step == False:
|
if open_step == False:
|
||||||
self.cbpi.bus.fire("step/berwing/finished")
|
await self.cbpi.bus.fire("step/berwing/finished")
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -222,7 +222,7 @@ class CraftBeerPi():
|
||||||
:param type: notification type (info,warning,danger,successs)
|
:param type: notification type (info,warning,danger,successs)
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
self.bus.fire(topic="notification/%s" % key, key=key, message=message, type=type)
|
self.bus.sync_fire(topic="notification/%s" % key, key=key, message=message, type=type)
|
||||||
|
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
|
|
|
@ -7,6 +7,8 @@ class ActorModel(DBModel):
|
||||||
__json_fields__ = ["config"]
|
__json_fields__ = ["config"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SensorModel(DBModel):
|
class SensorModel(DBModel):
|
||||||
__fields__ = ["name", "type", "config"]
|
__fields__ = ["name", "type", "config"]
|
||||||
__table_name__ = "sensor"
|
__table_name__ = "sensor"
|
||||||
|
|
|
@ -3,6 +3,9 @@ import inspect
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
class EventBus(object):
|
class EventBus(object):
|
||||||
class Node(object):
|
class Node(object):
|
||||||
__slots__ = '_children', '_content'
|
__slots__ = '_children', '_content'
|
||||||
|
@ -12,15 +15,37 @@ class EventBus(object):
|
||||||
self._content = None
|
self._content = None
|
||||||
|
|
||||||
class Content(object):
|
class Content(object):
|
||||||
def __init__(self, parent, topic, method, once):
|
def __init__(self, parent, topic, method, once, supports_future=False):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.method = method
|
self.method = method
|
||||||
self.name = method.__name__
|
self.name = method.__name__
|
||||||
self.once = once
|
self.once = once
|
||||||
self.topic = topic
|
self.topic = topic
|
||||||
|
self.supports_future = supports_future
|
||||||
|
|
||||||
|
class Result():
|
||||||
|
|
||||||
|
def __init__(self, result, timeout):
|
||||||
|
self.result = result
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
class ResultContainer():
|
||||||
|
|
||||||
|
def __init__(self, results, timeout=False):
|
||||||
|
self.results = {}
|
||||||
|
self.timeout = timeout
|
||||||
|
for key, value in results.items():
|
||||||
|
if value.done() is True:
|
||||||
|
self.results[key] = EventBus.Result(value.result(), True)
|
||||||
|
else:
|
||||||
|
self.results[key] = EventBus.Result(None, False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def register(self, topic, method, once=False):
|
def register(self, topic, method, once=False):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if method in self.registry:
|
if method in self.registry:
|
||||||
raise RuntimeError("Method %s already registerd. Please unregister first!" % method.__name__)
|
raise RuntimeError("Method %s already registerd. Please unregister first!" % method.__name__)
|
||||||
self.logger.info("Topic %s", topic)
|
self.logger.info("Topic %s", topic)
|
||||||
|
@ -31,7 +56,14 @@ class EventBus(object):
|
||||||
if not isinstance(node._content, list):
|
if not isinstance(node._content, list):
|
||||||
node._content = []
|
node._content = []
|
||||||
|
|
||||||
c = self.Content(node, topic, method, once)
|
sig = inspect.signature(method)
|
||||||
|
|
||||||
|
|
||||||
|
if "future" in sig.parameters:
|
||||||
|
supports_future = True
|
||||||
|
else:
|
||||||
|
supports_future = False
|
||||||
|
c = self.Content(node, topic, method, once, supports_future)
|
||||||
node._content.append(c)
|
node._content.append(c)
|
||||||
self.registry[method] = c
|
self.registry[method] = c
|
||||||
|
|
||||||
|
@ -71,29 +103,36 @@ class EventBus(object):
|
||||||
|
|
||||||
print(self.loop)
|
print(self.loop)
|
||||||
|
|
||||||
def fire(self, topic: str, **kwargs) -> None:
|
def sync_fire(self,topic: str,timeout=1, **kwargs):
|
||||||
self.logger.info("EMIT EVENT %s Data: %s", topic, kwargs)
|
self.loop.create_task(self.fire(topic=topic, timeout=timeout, **kwargs))
|
||||||
|
|
||||||
|
async def fire(self, topic: str, timeout=1, **kwargs):
|
||||||
|
|
||||||
|
futures = {}
|
||||||
|
|
||||||
|
async def wait(futures):
|
||||||
|
if(len(futures) > 0):
|
||||||
|
await asyncio.wait(futures.values())
|
||||||
|
|
||||||
|
|
||||||
#self.cbpi.ws.send(json.dumps(dict(topic=topic, data=dict(**kwargs))))
|
|
||||||
trx = dict(i=0)
|
|
||||||
for e in self.iter_match(topic):
|
for e in self.iter_match(topic):
|
||||||
content_array = e
|
content_array = e
|
||||||
keep_idx = []
|
keep_idx = []
|
||||||
for idx, content_obj in enumerate(content_array):
|
for idx, content_obj in enumerate(content_array):
|
||||||
|
|
||||||
if inspect.iscoroutinefunction(content_obj.method):
|
if inspect.iscoroutinefunction(content_obj.method):
|
||||||
if hasattr(content_obj.method, "future"):
|
if content_obj.supports_future is True:
|
||||||
|
|
||||||
|
fut = self.loop.create_future()
|
||||||
|
|
||||||
|
futures["%s.%s" % (content_obj.method.__module__, content_obj.name)] = fut
|
||||||
|
self.loop.create_task(content_obj.method(**kwargs, topic = topic, future=fut))
|
||||||
|
|
||||||
self.loop.create_task(content_obj.method(**kwargs, future=content_obj.method.future, topic=topic))
|
|
||||||
else:
|
else:
|
||||||
self.loop.create_task(content_obj.method(**kwargs, topic = topic))
|
self.loop.create_task(content_obj.method(**kwargs, topic=topic))
|
||||||
else:
|
else:
|
||||||
if hasattr(content_obj.method, "future"):
|
# only asnyc
|
||||||
content_obj.method(**kwargs, future=content_obj.method.future, topic=topic)
|
pass
|
||||||
else:
|
|
||||||
content_obj.method(**kwargs, topic = topic)
|
|
||||||
|
|
||||||
|
|
||||||
if content_obj.once is False:
|
if content_obj.once is False:
|
||||||
keep_idx.append(idx)
|
keep_idx.append(idx)
|
||||||
|
|
||||||
|
@ -101,6 +140,13 @@ class EventBus(object):
|
||||||
if len(keep_idx) < len(e):
|
if len(keep_idx) < len(e):
|
||||||
e[0].parent._content = [e[0].parent._content[i] for i in keep_idx]
|
e[0].parent._content = [e[0].parent._content[i] for i in keep_idx]
|
||||||
|
|
||||||
|
if timeout is not None:
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(wait(futures), timeout=timeout)
|
||||||
|
is_timedout = False
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
is_timedout = True
|
||||||
|
return self.ResultContainer(futures, is_timedout)
|
||||||
|
|
||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
|
|
|
@ -31,14 +31,15 @@ class MyComp(CBPiExtension, CRUDController, HttpAPI):
|
||||||
|
|
||||||
|
|
||||||
@on_event(topic="actor/#")
|
@on_event(topic="actor/#")
|
||||||
def listen(self, **kwargs):
|
async def listen(self, **kwargs):
|
||||||
print("Test", kwargs)
|
print("Test", kwargs)
|
||||||
|
|
||||||
|
|
||||||
@on_event(topic="kettle/+/automatic")
|
@on_event(topic="kettle/+/automatic")
|
||||||
def listen2(self, **kwargs):
|
async def listen2(self, **kwargs):
|
||||||
print("HANDLE AUTOMATIC", kwargs)
|
print("HANDLE AUTOMATIC", kwargs)
|
||||||
|
|
||||||
self.cbpi.bus.fire(topic="actor/%s/toggle" % 1, id=1)
|
await self.cbpi.bus.fire(topic="actor/%s/toggle" % 1, id=1)
|
||||||
|
|
||||||
|
|
||||||
def setup(cbpi):
|
def setup(cbpi):
|
||||||
|
|
|
@ -8,6 +8,12 @@ class CustomActor(CBPiActor):
|
||||||
# Custom property which can be configured by the user
|
# Custom property which can be configured by the user
|
||||||
gpio = Property.Number(label="Test")
|
gpio = Property.Number(label="Test")
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
print("#########INIT MY CUSTOM ACTOR")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
print("#########STOP MY CUSTOM ACTOR")
|
||||||
|
|
||||||
|
|
||||||
@action(key="name", parameters={})
|
@action(key="name", parameters={})
|
||||||
def myAction(self):
|
def myAction(self):
|
||||||
|
|
|
@ -37,7 +37,7 @@ class CustomSensor(CBPiSensor):
|
||||||
await asyncio.sleep(self.interval)
|
await asyncio.sleep(self.interval)
|
||||||
|
|
||||||
self.value = self.value + 1
|
self.value = self.value + 1
|
||||||
cbpi.bus.fire("sensor/%s" % self.id, value=self.value)
|
await cbpi.bus.fire("sensor/%s" % self.id, value=self.value)
|
||||||
print("SENSOR IS RUNNING", self.value)
|
print("SENSOR IS RUNNING", self.value)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -130,4 +130,4 @@ class HttpAPI():
|
||||||
"""
|
"""
|
||||||
id = request.match_info['id']
|
id = request.match_info['id']
|
||||||
await self.delete(id)
|
await self.delete(id)
|
||||||
return web.Response(str=204)
|
return web.Response(status=204)
|
||||||
|
|
|
@ -114,7 +114,7 @@ class Scheduler(*bases):
|
||||||
def _done(self, job):
|
def _done(self, job):
|
||||||
|
|
||||||
print("JOB DONE")
|
print("JOB DONE")
|
||||||
self.cbpi.bus.fire("job/done", key=job.name)
|
self.cbpi.bus.sync_fire("job/done", key=job.name)
|
||||||
self._jobs.discard(job)
|
self._jobs.discard(job)
|
||||||
if not self.pending_count:
|
if not self.pending_count:
|
||||||
return
|
return
|
||||||
|
|
|
@ -10,13 +10,14 @@ class ComplexEncoder(JSONEncoder):
|
||||||
from core.database.model import ActorModel
|
from core.database.model import ActorModel
|
||||||
from core.database.orm_framework import DBModel
|
from core.database.orm_framework import DBModel
|
||||||
from core.api.kettle_logic import CBPiKettleLogic
|
from core.api.kettle_logic import CBPiKettleLogic
|
||||||
|
print("OBJECT", obj)
|
||||||
try:
|
try:
|
||||||
if isinstance(obj, DBModel):
|
if isinstance(obj, DBModel):
|
||||||
return obj.__dict__
|
return obj.__dict__
|
||||||
|
elif callable(getattr(obj, "reprJSON")):
|
||||||
elif isinstance(obj, ActorModel):
|
return obj.reprJSON()
|
||||||
return None
|
#elif isinstance(obj, ActorModel):
|
||||||
|
# return None
|
||||||
elif hasattr(obj, "callback"):
|
elif hasattr(obj, "callback"):
|
||||||
return obj()
|
return obj()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -21,8 +21,9 @@ class WebSocket:
|
||||||
async def listen(self, topic, **kwargs):
|
async def listen(self, topic, **kwargs):
|
||||||
print("WS", topic)
|
print("WS", topic)
|
||||||
|
|
||||||
self.send(json.dumps(dict(topic=topic, data=dict(**kwargs))))
|
from core.utils.encoder import ComplexEncoder
|
||||||
|
|
||||||
|
self.send(json.dumps(dict(topic=topic, data=dict(**kwargs)),skipkeys=True, check_circular=True, cls=ComplexEncoder))
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ class WebSocket:
|
||||||
else:
|
else:
|
||||||
msg_obj = msg.json()
|
msg_obj = msg.json()
|
||||||
|
|
||||||
self.cbpi.bus.fire(msg_obj["topic"], id=1, power=22)
|
await self.cbpi.bus.fire(msg_obj["topic"], id=1, power=22)
|
||||||
# await self.fire(msg_obj["key"], ws, msg)
|
# await self.fire(msg_obj["key"], ws, msg)
|
||||||
|
|
||||||
# await ws.send_str(msg.data)
|
# await ws.send_str(msg.data)
|
||||||
|
|
BIN
craftbeerpi.db
BIN
craftbeerpi.db
Binary file not shown.
|
@ -36,6 +36,8 @@ Here an example how listen on an event.
|
||||||
|
|
||||||
It's imporante to add **kwargs as parameter to the listening method. This makes sure that maybe addtional event paramenter are not causing an exception.
|
It's imporante to add **kwargs as parameter to the listening method. This makes sure that maybe addtional event paramenter are not causing an exception.
|
||||||
|
|
||||||
|
A list of all registered events listeners can be found under: `http://<IP_ADDRESS>:<PORT>/system/events`
|
||||||
|
|
||||||
|
|
||||||
HTTP Endpoints
|
HTTP Endpoints
|
||||||
--------------
|
--------------
|
||||||
|
@ -80,6 +82,13 @@ The WebSocket Event is having the following structure.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SQL Files
|
||||||
|
---------
|
||||||
|
Currently only one SQL file for database initialisation is available.
|
||||||
|
It's located under: `./core/sql`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Web User Interface
|
Web User Interface
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
The Web UI is based on ReactJS + Redux.
|
The Web UI is based on ReactJS + Redux.
|
||||||
|
@ -98,3 +107,8 @@ After server startup you can find the API documentaiton under: `http://<IP_ADDRE
|
||||||
To generate the swagger file `aiohttp-swagger` is used. for more information see: https://aiohttp-swagger.readthedocs.io/en/latest/
|
To generate the swagger file `aiohttp-swagger` is used. for more information see: https://aiohttp-swagger.readthedocs.io/en/latest/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Custom Extensions & Pluins
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Custom Extension should be placed under `./core/extensions`
|
|
@ -14,6 +14,15 @@ class KettleTestCase(AioHTTPTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_example(self):
|
async def test_example(self):
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
for i in range(100):
|
||||||
|
resp = await self.client.request("GET", "/actor/")
|
||||||
|
print(resp)
|
||||||
|
resp = await self.client.post(path="/actor/", json={ "name": "Test", "type": "CustomActor", "config": {"gpio": 22 }})
|
||||||
|
print(resp)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
result = await self.cbpi.kettle.toggle_automtic(1)
|
result = await self.cbpi.kettle.toggle_automtic(1)
|
||||||
print("#### RESULT", result)
|
print("#### RESULT", result)
|
||||||
assert result[0] is True
|
assert result[0] is True
|
||||||
|
@ -28,3 +37,4 @@ class KettleTestCase(AioHTTPTestCase):
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
#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
|
#assert await self.cbpi.kettle.toggle_automtic(99) is False
|
||||||
|
'''
|
Loading…
Reference in a new issue