new step controller

This commit is contained in:
Manuel Fritsch 2021-01-17 22:49:18 +01:00
parent 8ced60706b
commit 05e08d0dc6
26 changed files with 1125 additions and 201 deletions

114
.vscode/.ropeproject/config.py vendored Normal file
View file

@ -0,0 +1,114 @@
# The default ``config.py``
# flake8: noqa
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
'.hg', '.svn', '_svn', '.git', '.tox']
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
# prefs['python_files'] = ['*.py']
# Custom source folders: By default rope searches the project
# for finding source folders (folders that should be searched
# for finding modules). You can add paths to that list. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
# prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
# prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs['save_objectdb'] = True
prefs['compress_objectdb'] = False
# If `True`, rope analyzes each module when it is being saved.
prefs['automatic_soa'] = True
# The depth of calls to follow in static object analysis
prefs['soa_followed_calls'] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs['perform_doa'] = True
# Rope can check the validity of its object DB when running.
prefs['validate_objectdb'] = True
# How many undos to hold?
prefs['max_history_items'] = 32
# Shows whether to save history across sessions.
prefs['save_history'] = True
prefs['compress_history'] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs['indent_size'] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs['extension_modules'] = []
# Add all standard c-extensions to extension_modules list.
prefs['import_dynload_stdmods'] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs['ignore_syntax_errors'] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs['ignore_bad_imports'] = False
# If `True`, rope will insert new module imports as
# `from <package> import <module>` by default.
prefs['prefer_module_from_imports'] = False
# If `True`, rope will transform a comma list of imports into
# multiple separate import statements when organizing
# imports.
prefs['split_imports'] = False
# If `True`, rope will remove all top-level import statements and
# reinsert them at the top of the module when making changes.
prefs['pull_imports_to_top'] = True
# If `True`, rope will sort imports alphabetically by module name instead
# of alphabetically by import statement, with from imports after normal
# imports.
prefs['sort_imports_alphabetically'] = False
# Location of implementation of
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
# case, you don't have to change this value, unless you're an rope expert.
# Change this value to inject you own implementations of interfaces
# listed in module rope.base.oi.type_hinting.providers.interfaces
# For example, you can add you own providers for Django Models, or disable
# the search type-hinting in a class hierarchy, etc.
prefs['type_hinting_factory'] = (
'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!

BIN
.vscode/.ropeproject/objectdb vendored Normal file

Binary file not shown.

View file

@ -6,6 +6,7 @@ __all__ = ["CBPiActor",
"on_startup", "on_startup",
"request_mapping", "request_mapping",
"action", "action",
"parameters",
"background_task", "background_task",
"CBPiKettleLogic", "CBPiKettleLogic",
"CBPiSimpleStep", "CBPiSimpleStep",
@ -13,7 +14,8 @@ __all__ = ["CBPiActor",
"KettleException", "KettleException",
"SensorException", "SensorException",
"ActorException", "ActorException",
"CBPiSensor"] "CBPiSensor",
"CBPiStep"]
from cbpi.api.actor import * from cbpi.api.actor import *
from cbpi.api.sensor import * from cbpi.api.sensor import *

View file

@ -2,7 +2,7 @@ from functools import wraps
from voluptuous import Schema from voluptuous import Schema
__all__ = ["request_mapping", "on_startup", "on_event", "action", "background_task"] __all__ = ["request_mapping", "on_startup", "on_event", "action", "background_task", "parameters"]
from aiohttp_auth import auth from aiohttp_auth import auth
@ -73,6 +73,13 @@ def action(key, parameters):
return real_decorator return real_decorator
def parameters(parameter):
def real_decorator(func):
func.cbpi_p = True
func.cbpi_parameters = parameter
return func
return real_decorator
def background_task(name, interval): def background_task(name, interval):
def real_decorator(func): def real_decorator(func):
func.background_task = True func.background_task = True

View file

@ -29,8 +29,6 @@ class Property(object):
''' '''
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 label:
:param configurable: :param configurable:

View file

@ -3,7 +3,61 @@ import time
import asyncio import asyncio
import logging import logging
from abc import abstractmethod, ABCMeta from abc import abstractmethod, ABCMeta
import logging
class CBPiStep(metaclass=ABCMeta):
def __init__(self, cbpi, id, name, props) :
self.cbpi = cbpi
self.props = {"wohoo": 0, "count": 5, **props}
self.id = id
self.name = name
self.status = 0
self.running = False
self.stop_reason = None
self.pause = False
self.task = None
self._exception_count = 0
self._max_exceptions = 2
self.state_msg = "No state"
def get_state(self):
return self.state_msg
def stop(self):
self.stop_reason = "STOP"
self.running = False
def start(self):
self.running = True
self.stop_reason = None
def next(self):
self.stop_reason = "NEXT"
self.running = False
async def reset(self):
pass
async def update(self, props):
await self.cbpi.step2.update_props(self.id, props)
async def run(self):
while self.running:
try:
await self.execute()
except:
self._exception_count += 1
logging.error("Step has thrown exception")
if self._exception_count >= self._max_exceptions:
self.stop_reason = "MAX_EXCEPTIONS"
return (self.id, self.stop_reason)
await asyncio.sleep(1)
return (self.id, self.stop_reason)
@abstractmethod
async def execute(self):
pass
class CBPiSimpleStep(metaclass=ABCMeta): class CBPiSimpleStep(metaclass=ABCMeta):

View file

@ -2,7 +2,7 @@ import logging
import json import json
from cbpi.controller.crud_controller import CRUDController from cbpi.controller.crud_controller import CRUDController
from cbpi.database.model import DashboardModel, DashboardContentModel from cbpi.database.model import DashboardModel, DashboardContentModel
import os
class DashboardController(CRUDController): class DashboardController(CRUDController):
@ -20,19 +20,23 @@ class DashboardController(CRUDController):
return dict(items=self.cache) return dict(items=self.cache)
async def get_content(self, dashboard_id): async def get_content(self, dashboard_id):
with open('./config/dashboard/cbpi_dashboard_%s.json' % dashboard_id) as json_file: try:
data = json.load(json_file) with open('./config/dashboard/cbpi_dashboard_%s.json' % dashboard_id) as json_file:
return data data = json.load(json_file)
return data
except:
return {}
async def add_content(self, dashboard_id, data): async def add_content(self, dashboard_id, data):
with open('./config/dashboard/cbpi_dashboard_%s.json' % dashboard_id, 'w') as outfile: with open('./config/dashboard/cbpi_dashboard_%s.json' % dashboard_id, 'w') as outfile:
json.dump(data, outfile, indent=4, sort_keys=True) json.dump(data, outfile, indent=4, sort_keys=True)
print(data)
return {"status": "OK"} return {"status": "OK"}
async def delete_content(self, content_id): async def delete_content(self, dashboard_id):
await DashboardContentModel.delete(content_id) if os.path.exists('./config/dashboard/cbpi_dashboard_%s.json' % dashboard_id):
os.remove('./config/dashboard/cbpi_dashboard_%s.json' % dashboard_id)
async def delete_dashboard(self, dashboard_id): async def delete_dashboard(self, dashboard_id):

View file

@ -11,6 +11,7 @@ from cbpi.utils.utils import load_config
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PluginController(): class PluginController():
modules = {} modules = {}
types = {} types = {}
@ -34,18 +35,22 @@ class PluginController():
async def install(self, package_name): async def install(self, package_name):
async def install(cbpi, plugins, package_name): async def install(cbpi, plugins, package_name):
data = subprocess.check_output([sys.executable, "-m", "pip", "install", package_name]) data = subprocess.check_output(
[sys.executable, "-m", "pip", "install", package_name])
data = data.decode('UTF-8') data = data.decode('UTF-8')
if package_name not in self.plugins: if package_name not in self.plugins:
now = datetime.datetime.now() now = datetime.datetime.now()
self.plugins[package_name] = dict(version="1.0", installation_date=now.strftime("%Y-%m-%d %H:%M:%S")) 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: with open('./config/plugin_list.txt', 'w') as outfile:
yaml.dump(self.plugins, outfile, default_flow_style=False) yaml.dump(self.plugins, outfile, default_flow_style=False)
if data.startswith('Requirement already satisfied'): if data.startswith('Requirement already satisfied'):
self.cbpi.notify(key="p", message="Plugin already installed ", type="warning") self.cbpi.notify(
key="p", message="Plugin already installed ", type="warning")
else: else:
self.cbpi.notify(key="p", message="Plugin installed ", type="success") self.cbpi.notify(
key="p", message="Plugin installed ", type="success")
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get('http://localhost:2202/get/%s' % package_name) as resp: async with session.get('http://localhost:2202/get/%s' % package_name) as resp:
@ -55,17 +60,20 @@ class PluginController():
await self.cbpi.job.start_job(install(self.cbpi, self.plugins, data["package_name"]), data["package_name"], "plugins_install") await self.cbpi.job.start_job(install(self.cbpi, self.plugins, data["package_name"]), data["package_name"], "plugins_install")
return True return True
else: else:
self.cbpi.notify(key="p", message="Failed to install Plugin %s " % package_name, type="danger") self.cbpi.notify(
key="p", message="Failed to install Plugin %s " % package_name, type="danger")
return False return False
async def uninstall(self, package_name): async def uninstall(self, package_name):
async def uninstall(cbpi, plugins, package_name): async def uninstall(cbpi, plugins, package_name):
print("try to uninstall", package_name) print("try to uninstall", package_name)
try: try:
data = subprocess.check_output([sys.executable, "-m", "pip", "uninstall", "-y", package_name]) data = subprocess.check_output(
[sys.executable, "-m", "pip", "uninstall", "-y", package_name])
data = data.decode('UTF-8') data = data.decode('UTF-8')
if data.startswith("Successfully uninstalled"): if data.startswith("Successfully uninstalled"):
cbpi.notify(key="p", message="Plugin %s Uninstalled" % package_name, type="success") cbpi.notify(key="p", message="Plugin %s Uninstalled" %
package_name, type="success")
else: else:
cbpi.notify(key="p", message=data, type="success") cbpi.notify(key="p", message=data, type="success")
except Exception as e: except Exception as e:
@ -83,15 +91,17 @@ class PluginController():
continue continue
try: try:
logger.info("Trying to load plugin %s" % filename) logger.info("Trying to load plugin %s" % filename)
data = load_config(os.path.join(this_directory, "../extension/%s/config.yaml" % filename)) data = load_config(os.path.join(
this_directory, "../extension/%s/config.yaml" % filename))
if (data.get("active") is True and data.get("version") == 4): if (data.get("active") is True and data.get("version") == 4):
self.modules[filename] = import_module("cbpi.extension.%s" % (filename)) self.modules[filename] = import_module(
"cbpi.extension.%s" % (filename))
self.modules[filename].setup(self.cbpi) self.modules[filename].setup(self.cbpi)
logger.info("Plugin %s loaded successful" % filename) logger.info("Plugin %s loaded successful" % filename)
else: else:
logger.warning("Plugin %s is not supporting version 4" % filename) logger.warning(
"Plugin %s is not supporting version 4" % filename)
except Exception as e: except Exception as e:
print(e) print(e)
@ -132,9 +142,47 @@ class PluginController():
if issubclass(clazz, CBPiSimpleStep): if issubclass(clazz, CBPiSimpleStep):
self.cbpi.step.types[name] = self._parse_props(clazz) self.cbpi.step.types[name] = self._parse_props(clazz)
if issubclass(clazz, CBPiStep):
self.cbpi.step2.types[name] = self._parse_step_props(clazz,name)
if issubclass(clazz, CBPiExtension): if issubclass(clazz, CBPiExtension):
self.c = clazz(self.cbpi) self.c = clazz(self.cbpi)
def _parse_property_object(self, p):
if isinstance(p, Property.Number):
return {"label": p.label, "type": "number", "configurable": p.configurable, "description": p.description, "default_value": p.default_value}
elif isinstance(p, Property.Text):
return {"label": p.label, "type": "text", "configurable": p.configurable, "default_value": p.default_value, "description": p.description}
elif isinstance(p, Property.Select):
return {"label": p.label, "type": "select", "configurable": True, "options": p.options, "description": p.description}
elif isinstance(p, Property.Actor):
return {"label": p.label, "type": "actor", "configurable": p.configurable, "description": p.description}
elif isinstance(p, Property.Sensor):
return {"label": p.label, "type": "sensor", "configurable": p.configurable, "description": p.description}
elif isinstance(p, Property.Kettle):
return {"label": p.label, "type": "kettle", "configurable": p.configurable, "description": p.description}
def _parse_step_props(self, cls, name):
result = {"name": name, "class": cls,
"properties": [], "actions": []}
if hasattr(cls, "cbpi_parameters"):
parameters = []
for p in cls.cbpi_parameters:
parameters.append(self._parse_property_object(p))
result["properties"] = parameters
for method_name, method in cls.__dict__.items():
if hasattr(method, "action"):
key = method.__getattribute__("key")
parameters = []
for p in method.__getattribute__("parameters"):
parameters.append(self._parse_property_object(p))
result["actions"].append({"method": method_name, "label": key, "parameters": parameters})
return result
def _parse_props(self, cls): def _parse_props(self, cls):
name = cls.__name__ name = cls.__name__
@ -142,7 +190,8 @@ class PluginController():
result = {"name": name, "class": cls, "properties": [], "actions": []} result = {"name": name, "class": cls, "properties": [], "actions": []}
tmpObj = cls(cbpi=None, managed_fields=None) tmpObj = cls(cbpi=None, managed_fields=None)
members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")] members = [attr for attr in dir(tmpObj) if not callable(
getattr(tmpObj, attr)) and not attr.startswith("__")]
for m in members: for m in members:
if isinstance(tmpObj.__getattribute__(m), Property.Number): if isinstance(tmpObj.__getattribute__(m), Property.Number):
t = tmpObj.__getattribute__(m) t = tmpObj.__getattribute__(m)
@ -158,18 +207,22 @@ class PluginController():
{"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description}) {"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Actor): elif isinstance(tmpObj.__getattribute__(m), Property.Actor):
t = tmpObj.__getattribute__(m) t = tmpObj.__getattribute__(m)
result["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description}) result["properties"].append(
{"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Sensor): elif isinstance(tmpObj.__getattribute__(m), Property.Sensor):
t = tmpObj.__getattribute__(m) t = tmpObj.__getattribute__(m)
result["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description}) result["properties"].append(
{"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Kettle): elif isinstance(tmpObj.__getattribute__(m), Property.Kettle):
t = tmpObj.__getattribute__(m) t = tmpObj.__getattribute__(m)
result["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description}) result["properties"].append(
{"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})
for method_name, method in cls.__dict__.items(): for method_name, method in cls.__dict__.items():
if hasattr(method, "action"): if hasattr(method, "action"):
key = method.__getattribute__("key") key = method.__getattribute__("key")
parameters = method.__getattribute__("parameters") parameters = method.__getattribute__("parameters")
result["actions"].append({"method": method_name, "label": key, "parameters": parameters}) result["actions"].append(
{"method": method_name, "label": key, "parameters": parameters})
return result return result

View file

@ -21,11 +21,8 @@ class SensorController(CRUDController):
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
:return: :return:
''' '''
await super(SensorController, self).init() await super(SensorController, self).init()
for id, value in self.cache.items(): for id, value in self.cache.items():
await self.init_sensor(value) await self.init_sensor(value)
@ -34,6 +31,9 @@ class SensorController(CRUDController):
return dict(items=self.cache,types=self.types) return dict(items=self.cache,types=self.types)
async def init_sensor(self, sensor): async def init_sensor(self, sensor):
print("INIT SENSOR")
if sensor.type in self.types: if sensor.type in self.types:
cfg = sensor.config.copy() cfg = sensor.config.copy()
cfg.update(dict(cbpi=self.cbpi, id=sensor.id, name=sensor.name)) cfg.update(dict(cbpi=self.cbpi, id=sensor.id, name=sensor.name))

View file

@ -0,0 +1,251 @@
import asyncio
from tabulate import tabulate
import json
import copy
import shortuuid
import logging
import os.path
from ..api.step import CBPiStep
class Step2(CBPiStep):
async def execute(self):
print(self.props)
await self.update(self.props)
print("HALLO")
#raise Exception("RROR")
class StepControllerNg:
def __init__(self, cbpi):
self.cbpi = cbpi
self.woohoo = "HALLLO"
self.logger = logging.getLogger(__name__)
self.path = os.path.join(".", 'config', "step_data.json")
self._loop = asyncio.get_event_loop()
self.basic_data = {}
self.step = None
self.types = {}
self.cbpi.app.on_cleanup.append(self.shutdown)
async def init(self):
logging.info("INIT STEP Controller")
self.load(startActive=True)
def load(self, startActive=False):
# create file if not exists
if os.path.exists(self.path) is False:
with open(self.path, "w") as file:
json.dump(dict(basic={}, profile=[]), file, indent=4, sort_keys=True)
#load from json file
with open(self.path) as json_file:
data = json.load(json_file)
self.basic_data = data["basic"]
self.profile = data["profile"]
# Start step after start up
self.profile = list(map(lambda item: {**item, "instance": self.create_step(item.get("id"), item.get("type"), item.get("name"), item.get("props", {}))}, self.profile))
if startActive is True:
active_step = self.find_by_status("A")
if active_step is not None:
self._loop.create_task(self.start_step(active_step))
async def add(self, data):
logging.info("Add step")
print(data)
id = shortuuid.uuid()
item = {**{"status": "I", "props": {}}, **data, "id": id, "instance": self.create_step(id, data.get("type"), data.get("name"), data.get("props", {}))}
self.profile.append(item)
await self.save()
return item
async def update(self, id, data):
logging.info("update step")
print(id, data)
#if "instance" in data: del data["instance"]
self.profile = list(map(lambda old: {**old, **data} if old["id"] == id else old, self.profile))
print(tabulate(self.profile))
await self.save()
return self.find_by_id(id)
async def save(self):
logging.debug("save profile")
data = dict(basic=self.basic_data, profile=list(map(lambda x: dict(name=x["name"], type=x.get("type"), id=x["id"], status=x["status"],props=x["props"]), self.profile)))
with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True)
await self.push_udpate()
async def start(self):
# already running
if self.find_by_status("A") is not None:
logging.error("Steps already running")
return
# Find next inactive step
step = self.find_by_status("P")
if step is not None:
logging.info("Resume step")
await self.start_step(step)
await self.save()
return
step = self.find_by_status("I")
if step is not None:
logging.info("Start Step")
await self.start_step(step)
await self.save()
return
logging.info("BREWING COMPLETE")
async def next(self):
logging.info("Trigger Next")
step = self.find_by_status("A")
if step is not None:
instance = step.get("instance")
if instance is not None:
logging.info("Next")
instance.next()
await instance.task
else:
logging.info("No Step is running")
async def resume(self):
step = self.find_by_status("P")
if step is not None:
instance = step.get("instance")
if instance is not None:
await self.start_step(step)
else:
logging.info("Nothing to resume")
async def stop(self):
logging.info("STOP STEP")
step = self.find_by_status("A")
if step != None and step.get("instance") is not None:
logging.info("CALLING STOP STEP")
instance = step.get("instance")
instance.stop()
# wait for task to be finished
await instance.task
logging.info("STEP STOPPED")
step["status"] = "P"
await self.save()
async def reset_all(self):
step = self.find_by_status("A")
if step is not None:
logging.error("Please stop before reset")
return
for item in self.profile:
logging.info("Reset %s" % item.get("name"))
item["status"] = "I"
await item["instance"].reset()
await self.push_udpate()
def create_step(self, id, type, name, props):
type_cfg = self.types.get(type)
clazz = type_cfg.get("class")
return clazz(self.cbpi, id, name, {**props})
def create_dict(self, data):
return dict(name=data["name"], id=data["id"], type=data.get("type"), status=data["status"],props=data["props"], state_text=data["instance"].get_state())
def get_types2(self):
result = {}
for key, value in self.types.items():
print(value)
result[key] = dict(name=value.get("name"), properties=value.get("properties"), actions=value.get("actions"))
return result
def get_state(self):
return {"basic": self.basic_data, "profile": list(map(lambda x: self.create_dict(x), self.profile)), "types":self.get_types2()}
async def move(self, id, direction):
index = self.get_index_by_id(id)
if direction not in [-1, 1]:
self.logger.error("Cant move. Direction 1 and -1 allowed")
return
self.profile[index], self.profile[index+direction] = self.profile[index+direction], self.profile[index]
self.save()
await self.push_udpate()
async def delete(self, id):
step = self.find_by_id(id)
if step.get("status") == "A":
logging.error("Cant delete active Step %s", id)
return
self.profile = list(filter(lambda x: x["id"] != id, self.profile))
await self.save()
async def shutdown(self, app):
logging.info("Mash Profile Shutdonw")
for p in self.profile:
instance = p.get("instance")
# Stopping all running task
if instance.task != None and instance.task.done() is False:
logging.info("Stop Step")
instance.stop()
await instance.task
await self.save()
def done(self, task):
id, reason = task.result()
print(id, reason)
if reason == "MAX_EXCEPTIONS":
step_current = self.find_by_id(id)
step_current["status"] = "E"
self._loop.create_task(self.save())
return
if reason == "NEXT":
step_current = self.find_by_status("A")
if step_current is not None:
step_current["status"] = "D"
async def wrapper():
await self.save()
await self.start()
self._loop.create_task(wrapper())
def find_by_status(self, status):
return next((item for item in self.profile if item["status"] == status), None)
def find_by_id(self, id):
return next((item for item in self.profile if item["id"] == id), None)
def get_index_by_id(self, id):
return next((i for i, item in enumerate(self.profile) if item["id"] == id), None)
async def push_udpate(self):
print("PUS UPDATE")
await self.cbpi.bus.fire("step/update", data=list(map(lambda x: self.create_dict(x), self.profile)))
async def start_step(self,step):
logging.info("############# start step")
step.get("instance").start()
step["instance"].task = self._loop.create_task(step["instance"].run(), name=step["name"])
print(step["instance"].task)
step["instance"].task .add_done_callback(self.done)
step["status"] = "A"
async def update_props(self, id, props):
logging.info("SAVE PROPS")
print(id, props)
step = self.find_by_id(id)
step["props"] = props
await self.save()
await self.push_udpate()

View file

@ -19,6 +19,7 @@ from cbpi.controller.notification_controller import NotificationController
from cbpi.controller.plugin_controller import PluginController from cbpi.controller.plugin_controller import PluginController
from cbpi.controller.sensor_controller import SensorController from cbpi.controller.sensor_controller import SensorController
from cbpi.controller.step_controller import StepController from cbpi.controller.step_controller import StepController
from cbpi.controller.step_controller_ng import StepControllerNg
from cbpi.controller.system_controller import SystemController from cbpi.controller.system_controller import SystemController
from cbpi.controller.log_file_controller import LogController from cbpi.controller.log_file_controller import LogController
from cbpi.database.model import DBModel from cbpi.database.model import DBModel
@ -32,6 +33,7 @@ from cbpi.http_endpoints.http_dashboard import DashBoardHttpEndpoints
from cbpi.http_endpoints.http_kettle import KettleHttpEndpoints from cbpi.http_endpoints.http_kettle import KettleHttpEndpoints
from cbpi.http_endpoints.http_sensor import SensorHttpEndpoints from cbpi.http_endpoints.http_sensor import SensorHttpEndpoints
from cbpi.http_endpoints.http_step import StepHttpEndpoints from cbpi.http_endpoints.http_step import StepHttpEndpoints
from cbpi.http_endpoints.http_step2 import StepHttpEndpoints2
from cbpi.controller.translation_controller import TranslationController from cbpi.controller.translation_controller import TranslationController
from cbpi.http_endpoints.http_translation import TranslationHttpEndpoint from cbpi.http_endpoints.http_translation import TranslationHttpEndpoint
from cbpi.http_endpoints.http_plugin import PluginHttpEndpoints from cbpi.http_endpoints.http_plugin import PluginHttpEndpoints
@ -94,9 +96,11 @@ class CraftBeerPi():
self.kettle = KettleController(self) self.kettle = KettleController(self)
self.step = StepController(self) self.step = StepController(self)
self.step2 = StepControllerNg(self)
self.dashboard = DashboardController(self) self.dashboard = DashboardController(self)
self.http_step = StepHttpEndpoints(self) self.http_step = StepHttpEndpoints(self)
self.http_step2 = StepHttpEndpoints2(self)
self.http_sensor = SensorHttpEndpoints(self) self.http_sensor = SensorHttpEndpoints(self)
self.http_config = ConfigHttpEndpoints(self) self.http_config = ConfigHttpEndpoints(self)
self.http_actor = ActorHttpEndpoints(self) self.http_actor = ActorHttpEndpoints(self)
@ -199,8 +203,8 @@ class CraftBeerPi():
print("SWAGGER.......") print("SWAGGER.......")
setup_swagger(self.app, setup_swagger(self.app,
description=long_description, description=long_description,
title=self.static_config.get("name", "CraftBeerPi"), title=self.static_config.get("name", "CraftBeerPi 4.0"),
api_version=self.static_config.get("version", ""), api_version=self.static_config.get("version", "4.0"),
contact="info@craftbeerpi.com") contact="info@craftbeerpi.com")
def notify(self, key: str, message: str, type: str = "info") -> None: def notify(self, key: str, message: str, type: str = "info") -> None:
@ -251,7 +255,7 @@ class CraftBeerPi():
self.plugin.load_plugins() self.plugin.load_plugins()
self.plugin.load_plugins_from_evn() self.plugin.load_plugins_from_evn()
await self.sensor.init() await self.sensor.init()
await self.step.init() await self.step2.init()
await self.actor.init() await self.actor.init()
await self.kettle.init() await self.kettle.init()
await self.call_initializer(self.app) await self.call_initializer(self.app)

View file

@ -116,7 +116,7 @@ class CBPiEventBus(object):
futures = {} futures = {}
self.logger.info("FIRE %s %s" % (topic, kwargs)) #self.logger.info("FIRE %s %s" % (topic, kwargs))
async def wait(futures): async def wait(futures):
if(len(futures) > 0): if(len(futures) > 0):

View file

@ -38,6 +38,24 @@ class CustomStepCBPi(CBPiSimpleStep):
#self.cbpi.notify(key="step", message="HELLO FROM STEP") #self.cbpi.notify(key="step", message="HELLO FROM STEP")
@parameters([Property.Number(label="Test", configurable=True), Property.Text(label="Test", configurable=True, default_value="HALLO")])
class Step2(CBPiStep):
i = 0
@action(key="name", parameters=[Property.Number(label="Test", configurable=True)])
async def action(self, **kwargs):
print("HALLO")
async def execute(self):
print(self.props)
self.i += 1
print(self.i)
self.state_msg = "COUNT %s" % self.i
await self.update(self.props)
print("JETZT GEHTS LO")
#raise Exception("RROR")
def setup(cbpi): def setup(cbpi):
@ -48,5 +66,5 @@ def setup(cbpi):
:param cbpi: the cbpi core :param cbpi: the cbpi core
:return: :return:
''' '''
cbpi.plugin.register("CustomStep2", Step2)
cbpi.plugin.register("CustomStepCBPi", CustomStepCBPi) cbpi.plugin.register("CustomStepCBPi", CustomStepCBPi)

View file

@ -13,7 +13,7 @@ class ConfigHttpEndpoints(HttpCrudEndpoints):
self.cbpi.register(self, "/config") self.cbpi.register(self, "/config")
@request_mapping(path="/{name}/", method="PUT", auth_required=False) @request_mapping(path="/{name}/", method="PUT", auth_required=False)
async def http_post(self, request) -> web.Response: async def http_put(self, request) -> web.Response:
""" """
--- ---
@ -26,6 +26,15 @@ class ConfigHttpEndpoints(HttpCrudEndpoints):
description: "Parameter name" description: "Parameter name"
required: true required: true
type: "string" type: "string"
- name: body
in: body
description: "Parameter Value"
required: true
schema:
type: object
properties:
value:
type: string
responses: responses:
"204": "204":
description: successful operation description: successful operation

View file

@ -179,3 +179,26 @@ class DashBoardHttpEndpoints(HttpCrudEndpoints):
dashboard_id = int(request.match_info['id']) dashboard_id = int(request.match_info['id'])
await self.cbpi.dashboard.add_content(dashboard_id, data) await self.cbpi.dashboard.add_content(dashboard_id, data)
return web.Response(status=204) return web.Response(status=204)
@request_mapping(path="/{id:\d+}/content", method="DELETE", auth_required=False)
async def delete_conent(self, request):
"""
---
description: Add Dashboard Content
tags:
- Dashboard
parameters:
- name: "id"
in: "path"
description: "Dashboard ID"
required: true
type: "integer"
format: "int64"
responses:
"200":
description: successful operation
"""
dashboard_id = int(request.match_info['id'])
await self.cbpi.dashboard.delete_content(dashboard_id)
return web.Response(status=204)

View file

@ -0,0 +1,200 @@
from aiohttp import web
from cbpi.api import *
from cbpi.http_endpoints.http_curd_endpoints import HttpCrudEndpoints
class StepHttpEndpoints2():
def __init__(self, cbpi):
self.cbpi = cbpi
self.controller = cbpi.step2
self.cbpi.register(self, "/step2")
def create_dict(self, data):
return dict(name=data["name"], id=data["id"], type=data.get("type"), status=data["status"],props=data["props"], state_text=data["instance"].get_state())
@request_mapping(path="/", auth_required=False)
async def http_get_all(self, request):
"""
---
description: Get all steps
tags:
- Step2
responses:
"200":
description: successful operation
"""
return web.json_response(data=self.controller.get_state())
@request_mapping(path="/", method="POST", auth_required=False)
async def http_add(self, request):
"""
---
description: Add
tags:
- Step2
parameters:
- in: body
name: body
description: Created an step
required: true
schema:
type: object
responses:
"200":
description: successful operation
"""
data = await request.json()
result = await self.controller.add(data)
print("RESULT", result)
return web.json_response(self.create_dict(result))
@request_mapping(path="/{id}", method="PUT", auth_required=False)
async def http_update(self, request):
"""
---
description: Update
tags:
- Step2
parameters:
- in: body
name: body
description: Created an kettle
required: false
schema:
type: object
responses:
"200":
description: successful operation
"""
data = await request.json()
id = request.match_info['id']
result = await self.controller.update(id, data)
print("RESULT", result)
return web.json_response(self.create_dict(result))
@request_mapping(path="/{id}", method="DELETE", auth_required=False)
async def http_delete(self, request):
"""
---
description: Delete
tags:
- Step2
responses:
"204":
description: successful operation
"""
id = request.match_info['id']
await self.controller.delete(id)
return web.Response(status=204)
@request_mapping(path="/next", method="POST", auth_required=False)
async def http_next(self, request):
"""
---
description: Next
tags:
- Step2
responses:
"204":
description: successful operation
"""
await self.controller.next()
return web.Response(status=204)
@request_mapping(path="/move", method="PUT", auth_required=False)
async def http_move(self, request):
"""
---
description: Move
tags:
- Step2
parameters:
- in: body
name: body
description: Created an kettle
required: false
schema:
type: object
properties:
id:
type: string
direction:
type: "integer"
format: "int64"
responses:
"204":
description: successful operation
"""
data = await request.json()
print("MOVE", data)
await self.controller.move(data["id"], data["direction"])
return web.Response(status=204)
@request_mapping(path="/start", method="POST", auth_required=False)
async def http_start(self, request):
"""
---
description: Move
tags:
- Step2
responses:
"204":
description: successful operation
"""
await self.controller.start()
return web.Response(status=204)
@request_mapping(path="/stop", method="POST", auth_required=False)
async def http_stop(self, request):
"""
---
description: Stop Step
tags:
- Step2
responses:
"204":
description: successful operation
"""
await self.controller.stop()
return web.Response(status=204)
@request_mapping(path="/reset", method="POST", auth_required=False)
async def http_reset(self, request):
"""
---
description: Move
tags:
- Step2
responses:
"204":
description: successful operation
"""
print("RESE HTTP")
await self.controller.reset_all()
return web.Response(status=204)

View file

@ -26,7 +26,7 @@ class SystemHttpEndpoints:
actor=self.cbpi.actor.get_state(), actor=self.cbpi.actor.get_state(),
sensor=self.cbpi.sensor.get_state(), sensor=self.cbpi.sensor.get_state(),
kettle=self.cbpi.kettle.get_state(), kettle=self.cbpi.kettle.get_state(),
step=await self.cbpi.step.get_state(), step=self.cbpi.step2.get_state(),
dashboard=self.cbpi.dashboard.get_state(), dashboard=self.cbpi.dashboard.get_state(),
translations=self.cbpi.translation.get_all(), translations=self.cbpi.translation.get_all(),
config=self.cbpi.config.get_state()) config=self.cbpi.config.get_state())

View file

@ -4,8 +4,7 @@ from aiohttp.web import View
from . import create_scheduler from . import create_scheduler
__all__ = ('setup', 'spawn', 'get_scheduler', 'get_scheduler_from_app', __all__ = ('setup', 'spawn', 'get_scheduler', 'get_scheduler_from_app', 'atomic')
'atomic')
def get_scheduler(request): def get_scheduler(request):

View file

@ -1,196 +1,315 @@
{ {
"elements": [ "elements": [
{ {
"id": "8bf7b72b-76e8-4c7a-86da-23f211d1942b", "id": "db0c8199-6935-4c77-989a-28528b6743d7",
"name": "Kettle", "name": "Kettle",
"props": { "props": {
"heigth": "150", "heigth": "150",
"width": "100" "width": "100"
}, },
"type": "Kettle", "type": "Kettle",
"x": 200,
"y": 115
},
{
"id": "78e5fe5a-6db8-4bd6-ba18-e67469cb1a0f",
"name": "Kettle",
"props": {
"heigth": "150",
"width": "100"
},
"type": "Kettle",
"x": 610,
"y": 115
},
{
"id": "0f45b8b5-8ebd-41b2-9bd2-a957f655b84c",
"name": "Kettle",
"props": {
"heigth": "150",
"width": "100"
},
"type": "Kettle",
"x": 410,
"y": 115
},
{
"id": "9d7d8ebf-ffad-4e35-96db-23d5b7f9c11c",
"name": "Sensor Data",
"props": {
"sensor": 1,
"unit": "\u00b0"
},
"type": "Sensor",
"x": 240,
"y": 230
},
{
"id": "97331778-eca2-4e35-b94b-3337763a372c",
"name": "Sensor Data",
"props": {
"sensor": 1,
"unit": "\u00b0"
},
"type": "Sensor",
"x": 450,
"y": 230
},
{
"id": "f20b7ed4-cdc8-46c2-a01a-ac0c0eab3801",
"name": "Sensor Data",
"props": {
"sensor": 1,
"unit": "\u00b0"
},
"type": "Sensor",
"x": 655,
"y": 230
},
{
"id": "03eec665-ab3f-423b-bfb7-6faea856937b",
"name": "MashTun",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 205, "x": 205,
"y": 120 "y": 155
}, },
{ {
"id": "9912ec53-71c7-4499-a1df-6466a4c02607", "id": "35f8c20b-c801-4cf5-946c-29bcf88a989b",
"name": "Hot Liqour Tank", "name": "Kettle",
"props": {
"heigth": "150",
"width": "100"
},
"type": "Kettle",
"x": 400,
"y": 155
},
{
"id": "e62714ea-52c8-4544-af53-e7711fa3a087",
"name": "Kettle",
"props": {
"heigth": "150",
"width": "100"
},
"type": "Kettle",
"x": 585,
"y": 155
},
{
"id": "d7f576b7-7fa7-4be7-8b31-68fef3b65777",
"name": "Mash",
"props": { "props": {
"color": "#fff", "color": "#fff",
"size": "10" "size": "10"
}, },
"type": "Text", "type": "Text",
"x": 415, "x": 210,
"y": 120 "y": 135
}, },
{ {
"id": "4df11ebf-a8c9-4dc1-af8e-b6566bda0137", "id": "36db0df9-922c-4cf6-8222-63f1eb34e22a",
"name": "HLT",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 405,
"y": 135
},
{
"id": "7ae6a76b-712f-4d54-a661-7285b8f6d47b",
"name": "Boil", "name": "Boil",
"props": { "props": {
"color": "#fff", "color": "#fff",
"size": "10" "size": "10"
}, },
"type": "Text", "type": "Text",
"x": 590,
"y": 140
},
{
"id": "dfaad0f6-455c-4da6-9c82-789c6b36e046",
"name": "CraftBeerPi Brewery",
"props": {
"color": "#fff",
"size": "26"
},
"type": "Text",
"x": 205,
"y": 75
},
{
"id": "4d2c8dfe-61a9-433d-83a8-72f74d17e7e5",
"name": "Sensor Data",
"props": {
"sensor": 1,
"unit": "\u00b0"
},
"type": "Sensor",
"x": 245,
"y": 260
},
{
"id": "13a6b89d-50c7-4efb-b940-ec174e522314",
"name": "Sensor Data",
"props": {
"sensor": 1,
"unit": "\u00b0"
},
"type": "Sensor",
"x": 445,
"y": 260
},
{
"id": "8d171952-791d-4f72-bfc9-dac8714b839f",
"name": "Sensor Data",
"props": {
"sensor": 1,
"unit": "\u00b0"
},
"type": "Sensor",
"x": 630,
"y": 260
},
{
"id": "3963a344-8223-471f-aee6-5119e69f007f",
"name": "TargetTemp",
"props": {
"color": "#fff",
"kettle": "1",
"size": "12",
"unit": "\u00b0"
},
"type": "TargetTemp",
"x": 215,
"y": 175
},
{
"id": "50333692-e956-4a8e-830f-934cd1d037c4",
"name": "TargetTemp",
"props": {
"color": "#fff",
"kettle": "1",
"size": "12",
"unit": "\u00b0"
},
"type": "TargetTemp",
"x": 410,
"y": 175
},
{
"id": "28860d2d-f326-4375-a972-4e40a07bcf29",
"name": "Target Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 215,
"y": 160
},
{
"id": "2f6129ab-61a5-4080-95d3-8832f3f8d57e",
"name": "Target Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 410,
"y": 160
},
{
"id": "4b3f0ef9-61a8-4be2-9f6f-f954a04a77ce",
"name": "TargetTemp",
"props": {
"color": "#fff",
"kettle": "1",
"size": "12",
"unit": "\u00b0"
},
"type": "TargetTemp",
"x": 595,
"y": 175
},
{
"id": "9fc5f252-7f83-4eb6-89f6-99fd343502b8",
"name": "Target temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 595,
"y": 160
},
{
"id": "3ec3e5d8-f82e-40c1-8c41-cb8286659d3b",
"name": "Led",
"props": {
"actor": 1
},
"type": "Led",
"x": 245,
"y": 220
},
{
"id": "2e325539-6ed9-4e0d-b1dc-de860c47a1be",
"name": "Heater",
"props": {
"actor": 1
},
"type": "ActorButton",
"x": 210,
"y": 380
},
{
"id": "78b85989-c1bc-47e3-ad7b-0defeabb9bdc",
"name": "Pump",
"props": {
"actor": 2
},
"type": "ActorButton",
"x": 305,
"y": 380
},
{
"id": "3c3f81d0-cdfd-4521-a2fe-2f039f17b583",
"name": "Current Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 235,
"y": 280
},
{
"id": "0ac051db-5550-4dac-a8ba-e3c1f131704b",
"name": "Current Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 430,
"y": 280
},
{
"id": "e9c833c2-6c87-4849-9ada-479ec95e79da",
"name": "Current Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 615, "x": 615,
"y": 120 "y": 280
},
{
"id": "e51ca9a8-e911-42ea-ba19-ca21cfed72ce",
"name": "Current Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 220,
"y": 215
},
{
"id": "6eb1d953-c579-4417-89ef-55cb472517a5",
"name": "Current Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 435,
"y": 215
},
{
"id": "ad3087a4-5b8f-4ade-b665-4b2acbbaecc2",
"name": "Current Temp",
"props": {
"color": "#fff",
"size": "10"
},
"type": "Text",
"x": 635,
"y": 215
},
{
"id": "891eac86-3d81-4438-b9c4-65035c71e52f",
"name": "CraftBeerPi v4.0",
"props": {
"color": "#fff",
"size": "30"
},
"type": "Text",
"x": 200,
"y": 65
} }
], ],
"pathes": [ "pathes": [
{ {
"coordinates": [ "coordinates": [
[ [
670, 305,
260 185
], ],
[ [
670, 405,
305 185
],
[
265,
305
],
[
265,
260
] ]
], ],
"id": "74b4bf4d-3bb8-44d8-a50b-f27c915f6218" "id": "49e7684e-21a3-4e0b-8e94-60f95abee80f"
}, },
{ {
"coordinates": [ "coordinates": [
[
400,
275
],
[ [
305, 305,
230 275
],
[
410,
230
] ]
], ],
"id": "c967d63c-7ec5-42b9-bbc3-e0d85b420670" "id": "5ba909c1-49a9-46e5-a6d0-1d0350c37aa4"
}, },
{ {
"coordinates": [ "coordinates": [
[ [
410, 255,
145 300
], ],
[ [
300, 255,
145 350
],
[
555,
350
],
[
555,
200
],
[
585,
200
] ]
], ],
"id": "e5498af8-3086-4bee-b35b-085a13bbd2b2" "id": "aed2d4d3-b99e-4af5-b8cf-d92d47721be4"
},
{
"coordinates": [
[
685,
275
],
[
805,
275
]
],
"id": "176fed29-56c2-4534-9cab-8c328d0e138c"
} }
] ]
} }

35
config/step_data.json Normal file
View file

@ -0,0 +1,35 @@
{
"basic": {},
"profile": [
{
"id": "eopJy6oxGqrNuRNtiAPXvN",
"name": "AMAZING",
"props": {
"count": 5,
"wohoo": 0
},
"status": "P",
"type": "CustomStep2"
},
{
"id": "duxvgLknKLjGYhdm9TKqUE",
"name": "Manuel",
"props": {
"count": 5,
"wohoo": 0
},
"status": "I",
"type": "CustomStep2"
},
{
"id": "hyXYDBUAENgwD7yNwaeLe7",
"name": "HALLO",
"props": {
"count": 5,
"wohoo": 0
},
"status": "I",
"type": "CustomStep2"
}
]
}

Binary file not shown.

1
data.txt Normal file
View file

@ -0,0 +1 @@
{"people": [{"name": "Scott", "website": "stackabuse.com", "from": "Nebraska"}, {"name": "Larry", "website": "google.com", "from": "Michigan"}, {"name": "Tim", "website": "apple.com", "from": "Alabama"}]}

View file

@ -1,19 +0,0 @@
import datetime
import yaml
from cbpi.utils.utils import load_config
package_name = "test222"
with open("./config/plugin_list.txt", 'rt') as f:
print(f)
plugins = yaml.load(f)
if plugins is None:
plugins = {}
now = datetime.datetime.now()
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(plugins, outfile, default_flow_style=False)

0
sampletest.py Normal file
View file

6
step_data.json Normal file
View file

@ -0,0 +1,6 @@
{
"basic": {},
"profile": [
]
}

46
tests/test_step_ng.py Normal file
View file

@ -0,0 +1,46 @@
import logging
from unittest import mock
import unittest
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from cbpi.craftbeerpi import CraftBeerPi
import pprint
import asyncio
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
pp = pprint.PrettyPrinter(indent=4)
class ActorTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
await self.cbpi.init_serivces()
return self.cbpi.app
'''
@unittest_run_loop
async def test_get_all(self):
resp = await self.client.get(path="/step2")
assert resp.status == 200
@unittest_run_loop
async def test_add_step(self):
resp = await self.client.post(path="/step2", json=dict(name="Manuel"))
data = await resp.json()
assert resp.status == 200
@unittest_run_loop
async def test_delete(self):
resp = await self.client.post(path="/step2", json=dict(name="Manuel"))
data = await resp.json()
assert resp.status == 200
resp = await self.client.delete(path="/step2/%s" % data["id"])
assert resp.status == 204
'''
@unittest_run_loop
async def test_move(self):
await self.cbpi.step2.resume()
if __name__ == '__main__':
unittest.main()