diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 17a4feb..077ed7f 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -3,36 +3,20 @@
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
+
+
-
-
+
+
+
+
-
-
-
@@ -44,13 +28,14 @@
+
-
+
@@ -66,9 +51,10 @@
-
+
+
@@ -109,31 +95,115 @@
-
+
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -150,25 +220,6 @@
- print
- setup_swagger
- Helo World
- index_url
- MY EXT
- plugin_list
- Hello
- config.yaml
- cbpi-actor
- StepModel
- ----------- END
- NORMAL START
- super
- _get_manged_fields_as_array
- _step_done
- job/step/done
- step_done
- STOP CURRENT STEP BEFORE NEXT
- fire
RUNNING
update
GOON
@@ -180,6 +231,25 @@
print(
notif
sensor111
+ CBPiWebSocket
+ self.ws
+ port
+ load_plugin_list
+ broker
+ client
+ ok_msg
+ mqtt_methods
+ HTTP SENSOR
+ on_message
+ listen
+ on_start
+ initializer
+ plugin_list
+ doe
+ list
+ LIST
+ call
+ handle_action
@@ -206,19 +276,6 @@
@@ -293,21 +363,14 @@
-
-
+
-
-
-
-
-
-
-
-
+
+
@@ -329,24 +392,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -361,33 +407,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -421,7 +440,107 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -429,30 +548,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -482,7 +577,96 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -502,11 +686,11 @@
+
-
@@ -555,6 +739,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -577,7 +783,7 @@
-
+
@@ -585,12 +791,12 @@
-
+
-
+
@@ -677,22 +883,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -744,20 +934,20 @@
-
-
+
+
-
+
+
+
-
-
@@ -802,7 +992,7 @@
-
+
1541288846149
@@ -822,9 +1012,6 @@
-
-
-
@@ -852,9 +1039,12 @@
+
+
+
-
+
@@ -867,15 +1057,16 @@
+
-
+
-
+
-
+
@@ -887,13 +1078,13 @@
-
+
-
+
@@ -927,7 +1118,7 @@
file://$PROJECT_DIR$/cbpi/craftbeerpi.py
- 41
+ 42
@@ -946,173 +1137,51 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
+
-
+
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -1121,185 +1190,336 @@
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
+
-
-
+
+
-
-
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
-
+
-
+
-
-
-
-
+
-
-
+
+
-
+
-
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
-
+
+
+
+
-
-
+
+
+
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
-
-
-
-
-
\ No newline at end of file
diff --git a/cbpi/api/decorator.py b/cbpi/api/decorator.py
index 83a02c8..2913918 100644
--- a/cbpi/api/decorator.py
+++ b/cbpi/api/decorator.py
@@ -1,3 +1,7 @@
+from functools import wraps
+
+from voluptuous import Schema
+
__all__ = ["request_mapping", "on_startup", "on_event", "on_mqtt_message", "on_websocket_message", "action", "background_task"]
from aiohttp_auth import auth
@@ -9,7 +13,7 @@ def composed(*decs):
return f
return deco
-def request_mapping(path, name=None, method="GET", auth_required=True):
+def request_mapping(path, name=None, method="GET", auth_required=True, json_schema=None):
def on_http_request(path, name=None):
def real_decorator(func):
@@ -21,14 +25,34 @@ def request_mapping(path, name=None, method="GET", auth_required=True):
return real_decorator
+ def validate_json_body(func):
+
+
+ @wraps(func)
+ async def wrapper(*args):
+
+ if json_schema is not None:
+ data = await args[-1].json()
+ schema = Schema(json_schema)
+ schema(data)
+
+ return await func(*args)
+
+ return wrapper
+
+
+
if auth_required is True:
return composed(
- on_http_request(path, name),
- auth.auth_required
+ on_http_request(path, name),
+ auth.auth_required,
+ validate_json_body
+
)
else:
return composed(
- on_http_request(path, name)
+ on_http_request(path, name),
+ validate_json_body
)
def on_websocket_message(path, name=None):
diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py
index 6a45e2b..b5d6a85 100644
--- a/cbpi/controller/plugin_controller.py
+++ b/cbpi/controller/plugin_controller.py
@@ -1,19 +1,15 @@
-import asyncio
import logging
import os
from importlib import import_module
-
+import datetime
import aiohttp
import yaml
-from aiohttp import web
-
-from cbpi.api import *
-from cbpi.utils.utils import load_config, json_dumps
-
-logger = logging.getLogger(__name__)
import subprocess
import sys
+from cbpi.api import *
+from cbpi.utils.utils import load_config
+logger = logging.getLogger(__name__)
class PluginController():
modules = {}
@@ -21,53 +17,87 @@ class PluginController():
def __init__(self, cbpi):
self.cbpi = cbpi
- self.cbpi.register(self, "/plugin")
- @classmethod
+ self.plugins = {}
+ self.plugins = load_config("./config/plugin_list.txt")
+
async def load_plugin_list(self):
async with aiohttp.ClientSession() as session:
- async with session.get('https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins_v4.yaml') as resp:
+ async with session.get('http://localhost:2202/list') as resp:
if (resp.status == 200):
data = yaml.load(await resp.text())
+ self.plugins = data
return data
+ def installed_plugins(self):
+ return self.plugins
+
+ async def install(self, package_name):
+ async def install(cbpi, plugins, package_name):
+ data = subprocess.check_output([sys.executable, "-m", "pip", "install", package_name])
+ data = data.decode('UTF-8')
+ if package_name not in self.plugins:
+ now = datetime.datetime.now()
+ self.plugins[package_name] = dict(version="1.0", installation_date=now.strftime("%Y-%m-%d %H:%M:%S"))
+ with open('./config/plugin_list.txt', 'w') as outfile:
+ yaml.dump(self.plugins, outfile, default_flow_style=False)
+ if data.startswith('Requirement already satisfied'):
+ self.cbpi.notify(key="p", message="Plugin already installed ", type="warning")
+ else:
+
+ self.cbpi.notify(key="p", message="Plugin installed ", type="success")
+
+ async with aiohttp.ClientSession() as session:
+ async with session.get('http://localhost:2202/get/%s' % package_name) as resp:
+
+ if (resp.status == 200):
+ data = await resp.json()
+ await self.cbpi.job.start_job(install(self.cbpi, self.plugins, data["package_name"]), data["package_name"], "plugins_install")
+ return True
+ else:
+ self.cbpi.notify(key="p", message="Failed to install Plugin %s " % package_name, type="danger")
+ return False
+
+ async def uninstall(self, package_name):
+ async def uninstall(cbpi, plugins, package_name):
+ print("try to uninstall", package_name)
+ try:
+ data = subprocess.check_output([sys.executable, "-m", "pip", "uninstall", "-y", package_name])
+ data = data.decode('UTF-8')
+ if data.startswith("Successfully uninstalled"):
+ cbpi.notify(key="p", message="Plugin %s Uninstalled" % package_name, type="success")
+ else:
+ cbpi.notify(key="p", message=data, type="success")
+ except Exception as e:
+ print(e)
+
+ if package_name in self.plugins:
+ print("Uninstall", self.plugins[package_name])
+ await self.cbpi.job.start_job(uninstall(self.cbpi, self.plugins, package_name), package_name, "plugins_uninstall")
+
def load_plugins(self):
this_directory = os.path.dirname(__file__)
-
for filename in os.listdir(os.path.join(this_directory, "../extension")):
-
if os.path.isdir(os.path.join(this_directory, "../extension/") + filename) is False or filename == "__pycache__":
continue
try:
logger.info("Trying to load plugin %s" % filename)
-
data = load_config(os.path.join(this_directory, "../extension/%s/config.yaml" % filename))
-
if (data.get("version") == 4):
-
self.modules[filename] = import_module("cbpi.extension.%s" % (filename))
self.modules[filename].setup(self.cbpi)
-
logger.info("Plugin %s loaded successful" % filename)
else:
logger.warning("Plugin %s is not supporting version 4" % filename)
-
except Exception as e:
print(e)
logger.error(e)
def load_plugins_from_evn(self):
- plugins = []
- this_directory = os.path.dirname(__file__)
- with open("./config/plugin_list.txt") as f:
-
- plugins = f.read().splitlines()
- plugins = list(set(plugins))
-
- for p in plugins:
+ for p in self.plugins:
logger.debug("Load Plugin %s" % p)
try:
logger.info("Try to load plugin: %s " % p)
@@ -79,53 +109,6 @@ class PluginController():
logger.error("FAILED to load plugin %s " % p)
logger.error(e)
- @on_event("job/plugins_install/done")
- async def done(self, **kwargs):
- self.cbpi.notify(key="p", message="Plugin installed ", type="success")
- print("DONE INSTALL PLUGIN", kwargs)
-
- @request_mapping(path="/install", method="GET", auth_required=False)
- async def install_plugin(self, request):
- """
- ---
- description: Install Plugin
- tags:
- - Plugin
- produces:
- - application/json
- responses:
- "204":
- description: successful operation. Return "pong" text
- "405":
- description: invalid HTTP Method
- """
-
- async def install(name):
- await asyncio.sleep(5)
- subprocess.call([sys.executable, "-m", "pip", "install", name])
-
- print("OK")
-
- await self.cbpi.job.start_job(install('requests'), "requests", "plugins_install")
- return web.Response(status=204)
-
- @request_mapping(path="/list", method="GET", auth_required=False)
- async def get_plugins(self, request):
- """
- ---
- description: Get a list of avialable plugins
- tags:
- - Plugin
- produces:
- - application/json
- responses:
- "200":
- description: successful operation. Return "pong" text
- "405":
- description: invalid HTTP Method
- """
- return web.json_response(await self.load_plugin_list(), dumps=json_dumps)
-
def register(self, name, clazz) -> None:
'''
Register a new actor type
diff --git a/cbpi/controller/step_controller.py b/cbpi/controller/step_controller.py
index c072c05..72a4a5a 100644
--- a/cbpi/controller/step_controller.py
+++ b/cbpi/controller/step_controller.py
@@ -1,9 +1,11 @@
+import json
import logging
import time
from cbpi.api import *
from cbpi.controller.crud_controller import CRUDController
from cbpi.database.model import StepModel
+from utils.encoder import ComplexEncoder
class StepController(CRUDController):
@@ -31,20 +33,7 @@ class StepController(CRUDController):
else:
return False
- @on_event("step/action")
- async def handle_action(self, action, **kwargs):
- '''
- Event Handler for "step/action".
- It invokes the provided method name on the current step
-
-
- :param action: the method name which will be invoked
- :param kwargs:
- :return: None
- '''
- if self.current_step is not None:
- self.current_step.__getattribute__(action)()
def _get_manged_fields_as_array(self, type_cfg):
@@ -106,7 +95,7 @@ class StepController(CRUDController):
self.current_step = next_step
# start the step job
self.current_job = await self.cbpi.job.start_job(self.current_step.instance.run(), next_step.name, "step")
- await self.cbpi.bus.fire("step/%s/started" % self.current_step.id)
+ await self.cbpi.bus.fire("step/%s/started" % self.current_step.id, step=next_step)
else:
await self.cbpi.bus.fire("step/brewing/finished")
else:
@@ -132,6 +121,7 @@ class StepController(CRUDController):
self.current_step.state = "D"
await self.model.update_state(self.current_step.id, "D", int(time.time()))
+ await self.cbpi.bus.fire("step/%s/done" % self.current_step.id, step=self.current_step)
self.current_step = None
# start the next step
@@ -197,3 +187,10 @@ class StepController(CRUDController):
async def get_state(self):
return dict(items=await self.get_all(),types=self.types,is_running=self.is_running(),current_step=self.current_step)
+
+ @on_event(topic="step/action")
+ async def call_action(self, name, parameter, **kwargs) -> None:
+ print(name, parameter)
+ if self.current_step is not None:
+
+ self.current_step.instance.__getattribute__(name)(**parameter)
diff --git a/cbpi/controller/system_controller.py b/cbpi/controller/system_controller.py
index 7928eb3..767bcca 100644
--- a/cbpi/controller/system_controller.py
+++ b/cbpi/controller/system_controller.py
@@ -1,6 +1,7 @@
import datetime
import re
+import aiohttp
from aiohttp import web
import os
from aiojobs.aiohttp import get_scheduler_from_app
@@ -15,7 +16,20 @@ class SystemController():
def __init__(self, cbpi):
self.cbpi = cbpi
self.service = cbpi.actor
+
self.cbpi.register(self, "/system")
+ self.cbpi.app.on_startup.append(self.check_for_update)
+
+
+ async def check_for_update(self, app):
+ timeout = aiohttp.ClientTimeout(total=1)
+ async with aiohttp.ClientSession(timeout=timeout) as session:
+ async with session.post('http://localhost:2202/check', json=dict(version=app["cbpi"].version)) as resp:
+ if (resp.status == 200):
+ data = await resp.json()
+ print(data)
+
+
@request_mapping("/", method="GET", auth_required=False)
async def state(self, request):
diff --git a/cbpi/craftbeerpi.py b/cbpi/craftbeerpi.py
index fb8c5b2..7361f2a 100644
--- a/cbpi/craftbeerpi.py
+++ b/cbpi/craftbeerpi.py
@@ -32,6 +32,7 @@ from cbpi.http_endpoints.http_sensor import SensorHttpEndpoints
from cbpi.http_endpoints.http_step import StepHttpEndpoints
from cbpi.controller.translation_controller import TranslationController
from cbpi.http_endpoints.http_translation import TranslationHttpEndpoint
+from http_endpoints.http_plugin import PluginHttpEndpoints
logger = logging.getLogger(__name__)
@@ -61,6 +62,8 @@ class CraftBeerPi():
def __init__(self):
+ self.version = "4.0.0.1"
+
self.static_config = load_config("./config/config.yaml")
self.database_file = "./craftbeerpi.db"
logger.info("Init CraftBeerPI")
@@ -68,6 +71,7 @@ class CraftBeerPi():
policy = auth.SessionTktAuthentication(urandom(32), 60, include_ip=True)
middlewares = [web.normalize_path_middleware(), session_middleware(EncryptedCookieStorage(urandom(32))), auth.auth_middleware(policy), error_middleware]
self.app = web.Application(middlewares=middlewares)
+ self.app["cbpi"] = self
self._setup_shutdownhook()
self.initializer = []
@@ -94,7 +98,7 @@ class CraftBeerPi():
self.http_kettle = KettleHttpEndpoints(self)
self.http_dashboard = DashBoardHttpEndpoints(self)
self.http_translation = TranslationHttpEndpoint(self)
-
+ self.http_plugin = PluginHttpEndpoints(self)
self.notification = NotificationController(self)
self.login = Login(self)
@@ -125,7 +129,7 @@ class CraftBeerPi():
'''
self.register_http_endpoints(obj, url_prefix, static)
self.bus.register_object(obj)
- #self.ws.register_object(obj)
+ #self.ws.register_object(obj)
self.job.register_background_task(obj)
self.register_on_startup(obj)
diff --git a/cbpi/extension/dummysensor/__init__.py b/cbpi/extension/dummysensor/__init__.py
index 5dfd20a..4ee82ab 100644
--- a/cbpi/extension/dummysensor/__init__.py
+++ b/cbpi/extension/dummysensor/__init__.py
@@ -82,7 +82,7 @@ class HTTPSensor(CBPiSensor):
try:
value = cache.pop(self.key, None)
- print("HTTP SENSOR READ", value)
+
if value is not None:
self.log_data(value)
await cbpi.bus.fire("sensor/%s/data" % self.id, value=value)
diff --git a/cbpi/extension/mqtt/__init__.py b/cbpi/extension/mqtt/__init__.py
new file mode 100644
index 0000000..e706d8f
--- /dev/null
+++ b/cbpi/extension/mqtt/__init__.py
@@ -0,0 +1,36 @@
+import json
+
+from cbpi.utils.encoder import ComplexEncoder
+from hbmqtt.mqtt.constants import QOS_0
+from hbmqtt.client import MQTTClient
+
+class CBPiMqttClient:
+
+ def __init__(self, cbpi):
+ self.cbpi = cbpi
+ self.cbpi.bus.register("#", self.listen)
+ self.client = None
+ self.cbpi.app.on_startup.append(self.init_client)
+
+
+ async def init_client(self, cbpi):
+
+ self.client = MQTTClient()
+ await self.client.connect('mqtt://localhost:1883')
+
+
+ async def listen(self, topic, **kwargs):
+ if self.client is not None:
+ print(topic, kwargs)
+ await self.client.publish(topic, str.encode(json.dumps(kwargs, cls=ComplexEncoder)), QOS_0)
+
+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:
+ '''
+ print("MQTT REGISTER-------------")
+ c = CBPiMqttClient(cbpi)
\ No newline at end of file
diff --git a/cbpi/extension/mqtt/config.yaml b/cbpi/extension/mqtt/config.yaml
new file mode 100644
index 0000000..05eb064
--- /dev/null
+++ b/cbpi/extension/mqtt/config.yaml
@@ -0,0 +1,2 @@
+name: MQTT
+version: 4.1
\ No newline at end of file
diff --git a/cbpi/http_endpoints/http_actor.py b/cbpi/http_endpoints/http_actor.py
index fe6f9cd..25b9759 100644
--- a/cbpi/http_endpoints/http_actor.py
+++ b/cbpi/http_endpoints/http_actor.py
@@ -72,7 +72,7 @@ class ActorHttpEndpoints(HttpCrudEndpoints):
- in: body
name: body
description: Created an actor
- required: false
+ required: true
schema:
type: object
properties:
diff --git a/cbpi/http_endpoints/http_login.py b/cbpi/http_endpoints/http_login.py
index 7be98cd..04de655 100644
--- a/cbpi/http_endpoints/http_login.py
+++ b/cbpi/http_endpoints/http_login.py
@@ -9,7 +9,6 @@ class Login():
def __init__(self,cbpi):
self.cbpi = cbpi
self.cbpi.register(self, url_prefix="/")
-
self.db = {cbpi.static_config.get("username", "cbpi"): cbpi.static_config.get("password", "cbpi")}
@request_mapping(path="/logout", name="Logout", method="GET", auth_required=True)
diff --git a/cbpi/http_endpoints/http_plugin.py b/cbpi/http_endpoints/http_plugin.py
new file mode 100644
index 0000000..3d1c41c
--- /dev/null
+++ b/cbpi/http_endpoints/http_plugin.py
@@ -0,0 +1,86 @@
+from aiohttp import web
+from api import request_mapping
+from utils import json_dumps
+
+
+class PluginHttpEndpoints:
+
+ def __init__(self,cbpi):
+ self.cbpi = cbpi
+ self.cbpi.register(self, url_prefix="/plugin")
+
+ @request_mapping(path="/install/", method="POST", auth_required=False, json_schema={"package_name": str})
+ async def install(self, request):
+ """
+ ---
+ description: Install Plugin
+ tags:
+ - Plugin
+ parameters:
+ - in: body
+ name: body
+ description: Install a plugin
+ required: true
+ schema:
+ type: object
+ properties:
+ package_name:
+ type: string
+ produces:
+ - application/json
+ responses:
+ "204":
+ description: successful operation. Return "pong" text
+ "405":
+ description: invalid HTTP Method
+ """
+
+ data = await request.json()
+ return web.Response(status=204) if await self.cbpi.plugin.install(data["package_name"]) is True else web.Response(status=500)
+
+ @request_mapping(path="/uninstall", method="POST", auth_required=False, json_schema={"package_name": str})
+ async def uninstall(self, request):
+ """
+ ---
+ description: Uninstall Plugin
+ tags:
+ - Plugin
+ parameters:
+ - in: body
+ name: body
+ description: Uninstall a plugin
+ required: true
+ schema:
+ type: object
+ properties:
+ package_name:
+ type: string
+ produces:
+ - application/json
+ responses:
+ "204":
+ description: successful operation. Return "pong" text
+ "405":
+ description: invalid HTTP Method
+ """
+
+ data = await request.json()
+ return web.Response(status=204) if await self.cbpi.plugin.uninstall(data["package_name"]) is True else web.Response(status=500)
+
+
+ @request_mapping(path="/list", method="GET", auth_required=False)
+ async def list(self, request):
+ """
+ ---
+ description: Get a list of avialable plugins
+ tags:
+ - Plugin
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: successful operation. Return "pong" text
+ "405":
+ description: invalid HTTP Method
+ """
+ return web.json_response(await self.cbpi.plugin.load_plugin_list(), dumps=json_dumps)
diff --git a/cbpi/http_endpoints/http_step.py b/cbpi/http_endpoints/http_step.py
index 194aa9d..84dcd4e 100644
--- a/cbpi/http_endpoints/http_step.py
+++ b/cbpi/http_endpoints/http_step.py
@@ -159,18 +159,35 @@ class StepHttpEndpoints(HttpCrudEndpoints):
return web.Response(status=204)
- @request_mapping(path="/action", auth_required=False)
+ @request_mapping(path="/action", method="POST", auth_required=False, json_schema={"action": str, "parameter": dict})
async def http_action(self, request):
"""
- ---
- description: Call step action
- tags:
- - Step
- responses:
- "204":
- description: successful operation
- """
- await self.cbpi.bus.fire("step/action", action="test")
+ ---
+ description: Call Step Action
+ tags:
+ - Step
+ parameters:
+ - in: body
+ name: body
+ description: Step Action
+ required: true
+ schema:
+ type: object
+ properties:
+ action:
+ type: string
+ parameter:
+ type: object
+ produces:
+ - application/json
+ responses:
+ "204":
+ description: successful operation
+ "405":
+ description: invalid HTTP Method
+ """
+ data = await request.json()
+ await self.cbpi.bus.fire("step/action", name=data["action"], parameter=data["parameter"])
return web.Response(text="OK")
@request_mapping(path="/start", auth_required=False)
diff --git a/cbpi/mqtt/__init__.py b/cbpi/mqtt/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/cbpi/mqtt/mqtt.py b/cbpi/mqtt/mqtt.py
deleted file mode 100644
index 303dc03..0000000
--- a/cbpi/mqtt/mqtt.py
+++ /dev/null
@@ -1,89 +0,0 @@
-from aiojobs.aiohttp import get_scheduler_from_app
-from cbpi.mqtt_matcher import MQTTMatcher
-from hbmqtt.broker import Broker
-from hbmqtt.client import MQTTClient
-from hbmqtt.mqtt.constants import QOS_1, QOS_0
-from typing import Callable
-
-
-class MQTT():
- def __init__(self,cbpi):
-
- self.config = {
- 'listeners': {
- 'default': {
- 'type': 'tcp',
- 'bind': '0.0.0.0:1885',
- },
- 'ws': {
- 'bind': '0.0.0.0:8081',
- 'type': 'ws'
- }
- },
- 'sys_interval': 10,
- 'topic-check': {
- 'enabled': True,
- 'plugins': [
- 'topic_taboo'
- ]
- },
- 'auth': {
- 'allow-anonymous': True,
- 'password-file': '/Users/manuelfritsch/github/aio_sample.cbpi/user.txt'
- }
- }
-
- self.cbpi = cbpi
- self.broker = Broker(self.config, plugin_namespace="hbmqtt.broker.plugins")
- self.client = MQTTClient()
- self.matcher = MQTTMatcher()
- self.mqtt_methods = {"test": self.ok_msg, "$SYS/broker/#": self.sysmsg}
- self.cbpi.app.on_startup.append(self.start_broker)
- self.count = 0
-
- def sysmsg(self, msg):
-
- pass
-
- def ok_msg(self, msg):
- self.count = self.count + 1
-
-
- def publish(self, topic, message):
-
- self.cbpi.app.loop.create_task(self.client.publish(topic, str.encode(message), QOS_0))
-
- def register_callback(self, func: Callable, topic) -> None:
-
- self.mqtt_methods[topic] = func
-
- async def on_message(self):
- while True:
-
- message = await self.client.deliver_message()
- matched = False
- packet = message.publish_packet
-
- #print(message.topic.split('/'))
- data = packet.payload.data.decode("utf-8")
-
- for callback in self.matcher.iter_match(message.topic):
-
- callback(data)
- matched = True
-
- if matched == False:
- print("NO HANDLER", data)
-
- async def start_broker(self, app):
-
- await self.broker.start()
- #
- await self.client.connect('mqtt://username:manuel@localhost:1885')
- # await self.client.connect('mqtt://broker.hivemq.com:1883')
-
- for k, v in self.mqtt_methods.items():
-
- await self.client.subscribe([(k, QOS_1)])
- self.matcher[k] = v
- await get_scheduler_from_app(app).spawn(self.on_message())
diff --git a/cbpi/mqtt/mqtt_matcher.py b/cbpi/mqtt/mqtt_matcher.py
deleted file mode 100644
index cf9637b..0000000
--- a/cbpi/mqtt/mqtt_matcher.py
+++ /dev/null
@@ -1,167 +0,0 @@
-class MQTTMatcher(object):
-
-
- class Node(object):
- __slots__ = '_children', '_content'
-
- def __init__(self):
- self._children = {}
- self._content = None
-
-
- def register(self, key, value):
- node = self._root
- for sym in key.split('/'):
- node = node._children.setdefault(sym, self.Node())
-
- if not isinstance(node._content, list):
- node._content = []
- node._content.append(value)
-
- 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, key, method=None):
-
- lst = []
- try:
- parent, node = None, self._root
- for k in key.split('/'):
- parent, node = node, node._children[k]
- lst.append((parent, k, node))
- # TODO
-
- if method is not None:
- node._content = None
- else:
- node._content = None
- except KeyError:
- raise KeyError(key)
- else: # cleanup
- for parent, k, node in reversed(lst):
- if node._children or node._content is not None:
- break
- del parent._children[k]
-
- def __init__(self):
- self._root = self.Node()
-
- def __setitem__(self, key, value):
-
- node = self._root
- for sym in key.split('/'):
-
- node = node._children.setdefault(sym, self.Node())
-
- if not isinstance(node._content, list):
- #print("new array")
- node._content = []
- node._content.append(value)
- #node._content = value
-
- def __getitem__(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 __delitem__(self, thekey):
- print("DELETE")
-
- if isinstance(thekey, tuple):
- key = thekey[1]
- methods = thekey[0]
- print(methods.__module__, methods.__name__)
- else:
- methods = None
- key = thekey
-
- 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 methods is not None:
-
- node._content = None
- else:
- node._content = None
- except KeyError:
- raise KeyError(key)
- else: # cleanup
- for parent, k, node in reversed(lst):
- if node._children or node._content is not None:
- break
- del parent._children[k]
- '''
- 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)
-
-
-if __name__ == "__main__":
- m = MQTTMatcher()
-
- def test_name():
- print("actor/1/on")
-
- def test_name2():
- print("actor/2/on")
-
- def test_name3():
- print("actor/#")
-
- def test_name4():
- print("actor/+/on")
-
-
-
- m.register("actor/1/on", test_name)
- m.register("actor/1/on", test_name)
- m.register("actor/1/on", test_name)
-
-
-
-
- m.unregister("actor/1/on")
-
- for methods in m.iter_match("actor/1/on"):
-
- for f in methods:
- f()
-
diff --git a/cbpi_cloud/default_broker.yaml b/cbpi_cloud/default_broker.yaml
new file mode 100644
index 0000000..c7d9dc7
--- /dev/null
+++ b/cbpi_cloud/default_broker.yaml
@@ -0,0 +1,14 @@
+listeners:
+ default:
+ type: tcp
+ bind: 0.0.0.0:1883
+sys_interval: 20
+auth:
+ allow-anonymous: true
+plugins:
+ - auth_file
+ - auth_anonymous
+topic-check:
+ enabled': True
+ plugins':
+ - topic_taboo
\ No newline at end of file
diff --git a/cbpi_cloud/repo/plugins.yaml b/cbpi_cloud/repo/plugins.yaml
new file mode 100644
index 0000000..17a2d68
--- /dev/null
+++ b/cbpi_cloud/repo/plugins.yaml
@@ -0,0 +1,13 @@
+SampleActor:
+ description: A sample Actor for CraftBeerPi
+ api: 4.0
+ author: CraftBeerPi11
+ pip: requests
+ repo_url: https://github.com/craftbeerpi/sample_actor
+
+SampleActor2:
+ description: A sample Actor2 for CraftBeerPi
+ api: 4.0
+ author: CraftBeerPi
+ pip: requests
+ repo_url: https://github.com/craftbeerpi/sample_actor
diff --git a/cbpi_cloud/run.py b/cbpi_cloud/run.py
new file mode 100644
index 0000000..1402795
--- /dev/null
+++ b/cbpi_cloud/run.py
@@ -0,0 +1,65 @@
+import yaml
+from aiohttp import web
+
+
+def load_yaml():
+ try:
+ with open('./repo/plugins.yaml', 'rt') as f:
+ data = yaml.load(f)
+ return data
+ except Exception as e:
+ print(e)
+ pass
+
+data = load_yaml()
+
+for k, v in data.items():
+ del v["pip"]
+data2 = load_yaml()
+
+
+async def check(request):
+ peername = request.transport.get_extra_info('peername')
+ if peername is not None:
+ host, port = peername
+ print(host, port)
+ data = await request.json()
+ print(data)
+ return web.json_response(data=dict(latestversion="4.0.0.3"))
+
+async def reload_yaml(request):
+ global data, data2
+
+ file = load_yaml()
+ for k, v in file.items():
+ del v["pip"]
+ data = file
+ data2 = load_yaml()
+
+ return web.json_response(data=data2)
+
+async def get_list(request):
+ print("Request List")
+ return web.json_response(data=data)
+
+async def get_package_name(request):
+ print("Request Package")
+ name = request.match_info.get('plugin_name', None)
+ if name in data2:
+ package_name = data2[name]["pip"]
+ else:
+ package_name = None
+ return web.json_response(data=dict(package_name=package_name))
+
+
+
+
+
+app = web.Application()
+app.add_routes([
+ web.get('/list', get_list),
+ web.post('/check', check),
+ web.get('/reload', reload_yaml),
+ web.get('/get/{plugin_name}', get_package_name)])
+
+web.run_app(app, port=2202)
diff --git a/config/plugin_list.txt b/config/plugin_list.txt
index 40ca454..3e70e1e 100644
--- a/config/plugin_list.txt
+++ b/config/plugin_list.txt
@@ -1,2 +1,13 @@
-cbpi-actor
-cbpi-ui
\ No newline at end of file
+SampleActor:
+ api: 4.0
+ author: CraftBeerPi11
+ description: A sample Actor for CraftBeerPi
+ repo_url: https://github.com/craftbeerpi/sample_actor
+SampleActor2:
+ api: 4.0
+ author: CraftBeerPi
+ description: A sample Actor2 for CraftBeerPi
+ repo_url: https://github.com/craftbeerpi/sample_actor
+requests:
+ installation_date: '2019-07-29 23:02:25'
+ version: '1.0'
diff --git a/craftbeerpi.db b/craftbeerpi.db
index e5d0de5..10d69b0 100644
Binary files a/craftbeerpi.db and b/craftbeerpi.db differ
diff --git a/tests/test_step.py b/tests/test_step.py
index 6e73825..e2ccb9d 100644
--- a/tests/test_step.py
+++ b/tests/test_step.py
@@ -1,13 +1,10 @@
import asyncio
-from unittest import mock
-
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
class StepTestCase(AioHTTPTestCase):
-
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
@@ -71,14 +68,11 @@ class StepTestCase(AioHTTPTestCase):
if future in done:
pass
-
-
@unittest_run_loop
async def test_process(self):
step_ctlr = self.cbpi.step
-
await step_ctlr.clear_all()
await step_ctlr.add(**{"name": "Kettle1", "type": "CustomStepCBPi", "config": {"name1": "1", "temp": 99}})
await step_ctlr.add(**{"name": "Kettle1", "type": "CustomStepCBPi", "config": {"name1": "1", "temp": 99}})
@@ -97,11 +91,9 @@ class StepTestCase(AioHTTPTestCase):
await self.print_steps()
-
-
async def print_steps(self):
s = await self.cbpi.step.get_all()
print(s)
for k, v in s.items():
- print(k, v.to_json())
\ No newline at end of file
+ print(k, v.to_json())