config controller added

This commit is contained in:
manuel83 2018-12-29 00:27:19 +01:00
parent d5dac67c35
commit 28f87c6c3f
49 changed files with 1217 additions and 1927 deletions

View file

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/core" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Python 3.7.1 virtualenv at ~/cbp42" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>

View file

@ -2,7 +2,7 @@
<project version="4">
<component name="dataSourceStorageLocal">
<data-source name="craftbeerpi.db" uuid="5067e7fe-480d-4433-bc40-f2d1c38362a2">
<database-info product="SQLite" version="3.16.1" jdbc-version="2.1" driver-name="SQLiteJDBC" driver-version="native" />
<database-info product="SQLite" version="3.16.1" jdbc-version="2.1" driver-name="SQLiteJDBC" driver-version="native" dbms="SQLITE" exact-version="3.16.1" />
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
<secret-storage>master_key</secret-storage>
<auth-required>false</auth-required>

4
.idea/encodings.xml Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownExportedFiles">
<htmlFiles />
<imageFiles />
<otherFiles />
</component>
</project>

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="NONE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="false" showSelectionInPreview="true" openRemoteLinks="true">
<PanelProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.panel" providerName="Default - Swing" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false">
<PegdownExtensions>
<option name="ABBREVIATIONS" value="false" />
<option name="ANCHORLINKS" value="true" />
<option name="ASIDE" value="false" />
<option name="ATXHEADERSPACE" value="true" />
<option name="AUTOLINKS" value="true" />
<option name="DEFINITIONS" value="false" />
<option name="DEFINITION_BREAK_DOUBLE_BLANK_LINE" value="false" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="FOOTNOTES" value="false" />
<option name="HARDWRAPS" value="false" />
<option name="HTML_DEEP_PARSER" value="false" />
<option name="INSERTED" value="false" />
<option name="QUOTES" value="false" />
<option name="RELAXEDHRULES" value="true" />
<option name="SMARTS" value="false" />
<option name="STRIKETHROUGH" value="true" />
<option name="SUBSCRIPT" value="false" />
<option name="SUPERSCRIPT" value="false" />
<option name="SUPPRESS_HTML_BLOCKS" value="false" />
<option name="SUPPRESS_INLINE_HTML" value="false" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
<option name="TOC" value="false" />
<option name="WIKILINKS" value="true" />
</PegdownExtensions>
<ParserOptions>
<option name="COMMONMARK_LISTS" value="true" />
<option name="DUMMY" value="false" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="FLEXMARK_FRONT_MATTER" value="false" />
<option name="GFM_LOOSE_BLANK_LINE_AFTER_ITEM_PARA" value="false" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="GITBOOK_URL_ENCODING" value="false" />
<option name="GITHUB_EMOJI_URL" value="false" />
<option name="GITHUB_LISTS" value="false" />
<option name="GITHUB_WIKI_LINKS" value="true" />
<option name="JEKYLL_FRONT_MATTER" value="false" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" embedUrlContent="false" addPageHeader="true" embedImages="false" embedHttpImages="false">
<GeneratorProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.generator" providerName="Default Swing HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.css" providerName="Default Swing Stylesheet" />
</StylesheetProvider>
<ScriptProviders />
<cssText />
</CssSettings>
<HtmlExportSettings updateOnSave="false" parentDir="$ProjectFileDir$" targetDir="$ProjectFileDir$" cssDir="" scriptDir="" plainHtml="false" imageDir="" copyLinkedImages="false" imageUniquifyType="0" targetExt="" useTargetExt="false" noCssNoScripts="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
</project>

View file

@ -0,0 +1,3 @@
<component name="MarkdownNavigator.ProfileManager">
<settings default="" pdf-export="" />
</component>

File diff suppressed because it is too large Load diff

View file

@ -7,3 +7,7 @@ port: 8080
username: cbpi
password: 123
test:
name: Manuel
name2: Manuel

View file

@ -1,6 +0,0 @@
__all__ = ["CBPiActor", "CBPiExtension","Property", "PropertyType", "on_websocket_message", "on_mqtt_message", "on_event", "on_startup", "action", "background_task"]
from core.api.actor import *
from core.api.extension import *
from core.api.property import *
from core.api.decorator import *

View file

@ -1,48 +0,0 @@
from abc import ABCMeta
__all__ = ["CBPiActor"]
import logging
from core.api.extension import CBPiExtension
logger = logging.getLogger(__file__)
class CBPiActor(CBPiExtension, metaclass=ABCMeta):
def init(self):
pass
def stop(self):
pass
def on(self, power):
'''
Code to switch the actor on. Power is provided as integer value
:param power: power value between 0 and 100
:return: None
'''
pass
def off(self):
'''
Code to switch the actor off
:return: None
'''
pass
def state(self):
'''
Return the current actor state
:return:
'''
pass
def reprJSON(self):
return dict(state=True)

View file

@ -1,96 +0,0 @@
__all__ = ["request_mapping", "on_startup", "on_event", "on_mqtt_message", "on_websocket_message", "action", "background_task"]
from aiohttp_auth import auth
def composed(*decs):
def deco(f):
for dec in reversed(decs):
f = dec(f)
return f
return deco
def request_mapping(path, name=None, method="GET", auth_required=True):
def on_http_request(path, name=None):
def real_decorator(func):
func.route = True
func.path = path
func.name = name
func.method = method
return func
return real_decorator
if auth_required is True:
return composed(
on_http_request(path, name),
auth.auth_required
)
else:
return composed(
on_http_request(path, name)
)
def on_websocket_message(path, name=None):
def real_decorator(func):
func.ws = True
func.key = path
func.name = name
return func
return real_decorator
def on_event(topic):
def real_decorator(func):
func.eventbus = True
func.topic = topic
func.c = None
return func
return real_decorator
def action(key, parameters):
def real_decorator(func):
func.action = True
func.key = key
func.parameters = parameters
return func
return real_decorator
def on_mqtt_message(topic):
def real_decorator(func):
func.mqtt = True
func.topic = topic
return func
return real_decorator
def background_task(name, interval):
def real_decorator(func):
func.background_task = True
func.name = name
func.interval = interval
return func
return real_decorator
def on_startup(name, order=0):
def real_decorator(func):
func.on_startup = True
func.name = name
func.order = order
return func
return real_decorator
def entry_exit(f):
def new_f():
f()
return new_f

View file

@ -1,45 +0,0 @@
import logging
import os
import sys
__all__ = ["CBPiExtension"]
logger = logging.getLogger(__file__)
logging.basicConfig(level=logging.INFO)
class CBPiExtension():
def init(self):
pass
def stop(self):
pass
def __init__(self, *args, **kwds):
for a in kwds:
super(CBPiExtension, self).__setattr__(a, kwds.get(a))
self.cbpi = kwds.get("cbpi")
self.id = kwds.get("id")
self.value = None
self.__dirty = False
def __setattr__(self, name, value):
if name != "_CBPiExtension__dirty":
self.__dirty = True
super(CBPiExtension, self).__setattr__(name, value)
else:
super(CBPiExtension, self).__setattr__(name, value)
def load_config(self):
from core.utils.utils import load_config as load
path = os.path.dirname(sys.modules[self.__class__.__module__].__file__)
try:
return load("%s/config.yaml" % path)
except:
logger.warning("Faild to load config %s/config.yaml" % path)

View file

@ -1,38 +0,0 @@
from core.api.extension import CBPiExtension
class CBPiKettleLogic(CBPiExtension):
'''
Base Class for a Kettle logic.
'''
def init(self):
'''
Code which will be executed when the logic is initialised. Needs to be overwritten by the implementing logic
:return: None
'''
pass
def stop(self):
'''
Code which will be executed when the logic is stopped. Needs to be overwritten by the implementing logic
:return: None
'''
pass
def run(self):
'''
This method is running as background process when logic is started.
Typically a while loop responsible that the method keeps running
while self.running:
await asyncio.sleep(1)
:return: None
'''
pass

View file

@ -1,113 +0,0 @@
__all__ = ["PropertyType", "Property"]
class PropertyType(object):
pass
class Property(object):
class Select(PropertyType):
'''
Select Property. The user can select value from list set as options parameter
'''
def __init__(self, label, options, description=""):
'''
:param label:
:param options:
:param description:
'''
PropertyType.__init__(self)
self.label = label
self.options = options
self.description = description
class Number(PropertyType):
'''
The user can set a number value
'''
def __init__(self, label, configurable=False, default_value=None, unit="", description=""):
'''
Test
:param label:
:param configurable:
:param default_value:
:param unit:
:param description:
'''
PropertyType.__init__(self)
self.label = label
self.configurable = configurable
self.default_value = default_value
self.description = description
class Text(PropertyType):
'''
The user can set a text value
'''
def __init__(self, label, configurable=False, default_value="", description=""):
'''
:param label:
:param configurable:
:param default_value:
:param description:
'''
PropertyType.__init__(self)
self.label = label
self.configurable = configurable
self.default_value = default_value
self.description = description
class Actor(PropertyType):
'''
The user select an actor which is available in the system. The value of this variable will be the actor id
'''
def __init__(self, label, description=""):
'''
:param label:
:param description:
'''
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
class Sensor(PropertyType):
'''
The user select a sensor which is available in the system. The value of this variable will be the sensor id
'''
def __init__(self, label, description=""):
'''
:param label:
:param description:
'''
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
class Kettle(PropertyType):
'''
The user select a kettle which is available in the system. The value of this variable will be the kettle id
'''
def __init__(self, label, description=""):
'''
:param label:
:param description:
'''
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description

View file

@ -1,9 +0,0 @@
from core.api.extension import CBPiExtension
class CBPiSensor(CBPiExtension):
async def run(self, cbpi):
print("RUN NOT IMPLEMENTED")
def state(self):
pass

View file

@ -1,134 +0,0 @@
import time
import asyncio
import logging
from abc import abstractmethod,ABCMeta
class CBPiSimpleStep(metaclass=ABCMeta):
__dirty = False
managed_fields = []
_interval = 1
_max_exceptions = 2
_exception_count = 0
def __init__(self, *args, **kwargs):
self.logger = logging.getLogger(__name__)
for a in kwargs:
super(CBPiSimpleStep, self).__setattr__(a, kwargs.get(a))
self.id = kwargs.get("id")
self.is_stopped = False
self.is_next = False
self.start = time.time()
def running(self):
'''
Method checks if the step should continue running.
The method will return False if the step is requested to stop or the next step should start
:return: True if the step is running. Otherwise False.
'''
if self.is_next is True:
return False
if self.is_stopped is True:
return False
return True
async def run(self):
'''
This method in running in the background. It invokes the run_cycle method in the configured interval
It checks if a managed variable was modified in the last exection cycle. If yes, the method will persisit the new value of the
managed property
:return: None
'''
while self.running():
try:
await self.run_cycle()
except Exception as e:
logging.exception("CBPiSimpleStep Error")
self._exception_count = self._exception_count + 1
if self._exception_count == self._max_exceptions:
self.logger.error("Step Exception limit exceeded. Stopping Step")
self.stop()
print("INTER",self._interval)
await asyncio.sleep(self._interval)
if self.is_dirty():
# Now we have to store the managed props
state = {}
for field in self.managed_fields:
state[field] = self.__getattribute__(field)
#step_controller.model.update_step_state(step_controller.current_step.id, state)
print("STATE",state)
await self.cbpi.step.model.update_step_state(self.id, state)
self.reset_dirty()
@abstractmethod
async def run_cycle(self):
'''
This method is executed in the defined interval.
That the place to put your step logic.
The method need to be overwritten in the Ccstom step implementaion
:return: None
'''
print("NOTING IMPLEMENTED")
pass
def next(self):
'''
Request to stop the the step
:return: None
'''
self.is_next = True
def stop(self):
'''
Request to stop the step
:return: None
'''
self.is_stopped = True
def reset(self):
'''
Reset the step. This method needs to be overwritten by the custom step implementation
:return: None
'''
pass
def is_dirty(self):
'''
Check if a managed variable has a new value
:return: True if at least one managed variable has a new value assigend. Otherwise False
'''
return self.__dirty
def reset_dirty(self):
'''
Reset the dirty flag
:return:
'''
self.__dirty = False
def __setattr__(self, name, value):
if name != "_Step__dirty" and name in self.managed_fields:
self.__dirty = True
super(CBPiSimpleStep, self).__setattr__(name, value)
else:
super(CBPiSimpleStep, self).__setattr__(name, value)

View file

@ -1,11 +1,13 @@
import json
import logging
from asyncio import Future
from aiohttp import web
from core.api.decorator import on_event, request_mapping
from cbpi_api import *
from core.controller.crud_controller import CRUDController
from core.database.model import ActorModel
from core.http_endpoints.http_api import HttpAPI
from utils.encoder import ComplexEncoder
class ActorHttp(HttpAPI):
@ -17,11 +19,11 @@ class ActorHttp(HttpAPI):
:return:
"""
id = int(request.match_info['id'])
result = await self.cbpi.bus.fire2(topic="actor/%s/switch/on" % id, id=id, power=99)
print(result.timeout)
result = await self.cbpi.bus.fire(topic="actor/%s/switch/on" % id, id=id, power=99)
for key, value in result.results.items():
print(key, value.result)
pass
return web.Response(status=204)
@ -42,7 +44,7 @@ class ActorHttp(HttpAPI):
:return:
"""
id = int(request.match_info['id'])
print("ID", id)
await self.cbpi.bus.fire(topic="actor/%s/toggle" % id, id=id)
return web.Response(status=204)
@ -57,11 +59,16 @@ class ActorController(ActorHttp, CRUDController):
super(ActorController, self).__init__(cbpi)
self.cbpi = cbpi
self.state = False;
self.logger = logging.getLogger(__name__)
self.cbpi.register(self, "/actor")
self.types = {}
self.actors = {}
def info(self):
return json.dumps(dict(name="ActorController", types=self.types), cls=ComplexEncoder)
async def init(self):
'''
This method initializes all actors during startup. It creates actor instances
@ -104,8 +111,8 @@ class ActorController(ActorHttp, CRUDController):
id = int(id)
if id in self.cache:
print("POWER ON")
actor = self.cache[id ].instance
self.logger.debug("ON %s" % id)
actor = self.cache[id].instance
await self.cbpi.bus.fire("actor/%s/on/ok" % id)
actor.on(power)
@ -122,6 +129,7 @@ class ActorController(ActorHttp, CRUDController):
:return:
'''
self.logger.debug("TOGGLE %s" % id)
id = int(id)
if id in self.cache:
actor = self.cache[id].instance
@ -141,6 +149,7 @@ class ActorController(ActorHttp, CRUDController):
:param kwargs:
"""
self.logger.debug("OFF %s" % id)
id = int(id)
if id in self.cache:

View file

@ -1,8 +1,36 @@
import logging
import os
from aiohttp import web
from cbpi_api import request_mapping
from core.controller.crud_controller import CRUDController
from core.database.model import ConfigModel
from utils import load_config, json_dumps
from cbpi_api.config import ConfigType
class ConfigHTTPController():
@request_mapping(path="/{name}/", method="POST", auth_required=False)
async def http_post(self, request) -> web.Response:
"""
:param request:
:return:
"""
name = request.match_info['name']
data = await request.json()
print(data)
await self.set(name=name, value=data.get("value"))
return web.Response(status=204)
class ConfigController(CRUDController):
@request_mapping(path="/", auth_required=False)
async def http_get_all(self, request) -> web.Response:
"""
:param request:
:return:
"""
return web.json_response(self.cache, dumps=json_dumps)
class ConfigController(ConfigHTTPController):
'''
The main actor controller
@ -10,9 +38,35 @@ class ConfigController(CRUDController):
model = ConfigModel
def __init__(self, cbpi):
super(ConfigController, self).__init__(cbpi)
self.cache = {}
self.logger = logging.getLogger(__name__)
self.cbpi = cbpi
self.cbpi.register(self, "/config")
async def init(self):
this_directory = os.path.dirname(__file__)
self.static = load_config(os.path.join(this_directory, '../../config/config.yaml'))
items = await self.model.get_all()
for key, value in items.items():
self.cache[value.name] = value
async def get(self, name, default=None):
self.logger.info("GET CONFIG VALUE %s (default %s)" % (name,default))
if name in self.cache and self.cache[name].value is not None:
return self.cache[name].value
else:
return default
async def set(self, name, value):
self.logger.debug("SET %s = %s" % (name, value))
if name in self.cache:
self.cache[name].value = value
m = await self.model.update(**self.cache[name].__dict__)
await self.cbpi.bus.fire(topic="config/%s/update" % name, name=name, value=value)
async def add(self, name, value, type: ConfigType, description, options=None):
m = await self.model.insert(name=name, value=value, type=type.value, description=description, options=options)
await self.cbpi.bus.fire(topic="config/%s/add" % name, name=name, value=value)

View file

@ -1,8 +1,5 @@
import json
import pprint
from abc import abstractmethod,ABCMeta
from core.utils.encoder import ComplexEncoder
from abc import ABCMeta
class CRUDController(metaclass=ABCMeta):
@ -141,10 +138,10 @@ class CRUDController(metaclass=ABCMeta):
await self._post_delete_callback(id)
try:
if self.caching is True:
print("DELTE FROM ACHE")
del self.cache[int(id)]
except Exception as e:
print(e)
pass
pprint.pprint(self.cache)
await self.cbpi.bus.fire(topic="actor/%s/deleted" % id, id=id)

View file

@ -2,7 +2,8 @@ import re
from aiohttp import web
from core.api import request_mapping, on_event
from cbpi_api import *
from core.controller.crud_controller import CRUDController
from core.database.model import KettleModel
from core.http_endpoints.http_api import HttpAPI
@ -84,7 +85,7 @@ class KettleController(CRUDController):
kettle = self.cache[int(kid)]
kettle.instance = None
print("STOP KETTLE LOGIC", kid)
@on_event(topic="kettle/+/automatic")
async def handle_automtic_event(self, id, **kwargs):
@ -97,9 +98,9 @@ class KettleController(CRUDController):
:return: None
'''
id = int(id)
print("WOOOHO", type(id), self.cache)
if id in self.cache:
print("id", id)
kettle = self.cache[id]
if hasattr(kettle, "instance") is False:
@ -111,7 +112,7 @@ class KettleController(CRUDController):
cfg = kettle.config.copy()
cfg.update(dict(cbpi=self.cbpi))
kettle.instance = clazz(**cfg)
print("START LOGIC")
await self.cbpi.start_job(kettle.instance.run(), "Kettle_logic_%s" % kettle.id, "kettle_logic%s"%id)
else:
kettle.instance.running = False
@ -120,7 +121,7 @@ class KettleController(CRUDController):
def _is_logic_running(self, kettle_id):
scheduler = get_scheduler_from_app(self.cbpi.app)
print("JOB KETTLE RUNNING", scheduler.is_running("Kettle_logic_%s"%kettle_id))
async def heater_on(self, id):
'''

View file

@ -1,5 +1,4 @@
from core.api.decorator import on_event
from cbpi_api import *
class NotificationController():
'''

View file

@ -1,5 +1,8 @@
import imp
import importlib
import logging
import os
import sys
from importlib import import_module
from pprint import pprint
@ -7,17 +10,11 @@ import aiohttp
import yaml
from aiohttp import web
from core.api.actor import CBPiActor
from core.api.decorator import request_mapping
from core.api.extension import CBPiExtension
from core.api.kettle_logic import CBPiKettleLogic
from core.api.property import Property
from core.api.sensor import CBPiSensor
from core.api.step import CBPiSimpleStep
from cbpi_api import *
from core.utils.utils import load_config, json_dumps
logger = logging.getLogger(__file__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class PluginController():
@ -64,6 +61,18 @@ class PluginController():
except Exception as e:
logger.error(e)
def load_plugins_from_evn(self):
plugins = ["cbpi-actor"]
for p in plugins:
self.modules[p] = import_module(p)
self.modules[p].setup(self.cbpi)
@request_mapping(path="/plugins", method="GET", auth_required=False)
async def get_plugins(self, request):
"""
@ -90,7 +99,7 @@ class PluginController():
:param clazz: actor class
:return: None
'''
print("REGISTER", name, clazz)
if issubclass(clazz, CBPiActor):
self.cbpi.actor.types[name] = {"class": clazz, "config": self._parse_props(clazz)}
@ -103,12 +112,12 @@ class PluginController():
if issubclass(clazz, CBPiSimpleStep):
self.cbpi.step.types[name] = self._parse_props(clazz)
print(self.cbpi.step.types)
if issubclass(clazz, CBPiExtension):
self.c = clazz(self.cbpi)
def _parse_props(self, cls):
print("PARSE", cls)
name = cls.__name__
result = {"name": name, "class": cls, "properties": [], "actions": []}
@ -143,5 +152,5 @@ class PluginController():
key = method.__getattribute__("key")
parameters = method.__getattribute__("parameters")
result["actions"].append({"method": method_name, "label": key, "parameters": parameters})
pprint(result)
return result

View file

@ -1,10 +1,10 @@
import json
import logging
import os
from logging.handlers import TimedRotatingFileHandler
from core.utils.encoder import ComplexEncoder
from core.job.aiohttp import get_scheduler_from_app
from core.api.decorator import background_task
from cbpi_api import *
from core.controller.crud_controller import CRUDController
from core.database.model import SensorModel
from core.http_endpoints.http_api import HttpAPI
@ -22,7 +22,9 @@ class SensorController(CRUDController, HttpAPI):
self.sensors = {}
def info(self):
return json.dumps(dict(name="SensorController", types=self.types), cls=ComplexEncoder)
async def init(self):
'''
@ -31,9 +33,9 @@ class SensorController(CRUDController, HttpAPI):
:return:
'''
await super(SensorController, self).init()
print("INIT SENSOR")
for name, clazz in self.types.items():
print("Type", name)
pass
for id, value in self.cache.items():
if value.type in self.types:
@ -43,7 +45,7 @@ class SensorController(CRUDController, HttpAPI):
self.cache[id].instance = clazz(**cfg)
scheduler = get_scheduler_from_app(self.cbpi.app)
self.cache[id].instance.job = await scheduler.spawn(self.cache[id].instance.run(self.cbpi), value.name, "sensor")
print("------------")
async def get_value(self, id):

View file

@ -2,7 +2,7 @@ import asyncio
import time
from aiohttp import web
from core.api import on_event, request_mapping
from cbpi_api import *
from core.controller.crud_controller import CRUDController
from core.database.model import StepModel
from core.http_endpoints.http_api import HttpAPI
@ -36,7 +36,7 @@ class StepController(HttpAPI, CRUDController):
:return:
'''
await super(StepController, self).init()
print("INIT LAST STEP")
await self.init_after_startup()
async def init_after_startup(self):
@ -45,14 +45,14 @@ class StepController(HttpAPI, CRUDController):
if step is not None:
print("INIT LAST STEP", step.__dict__)
# get the type
print(self.types)
step_type = self.types.get(step.type)
if step_type is None:
# step type not found. cant restart step
print("STEP TYPE NONT FOUND")
return
if step.stepstate is not None:
@ -201,10 +201,10 @@ class StepController(HttpAPI, CRUDController):
:return:
'''
print("IS SHUTODONW", self.cbpi.shutdown)
if self.cbpi.shutdown:
return
print("JOB DONE STEP")
self.cache[self.current_step.id].state = "D"
step_id = self.current_step.id
self.current_step = None
@ -214,7 +214,7 @@ class StepController(HttpAPI, CRUDController):
def _get_manged_fields_as_array(self, type_cfg):
print("tYPE", type_cfg)
result = []
for f in type_cfg.get("properties"):
result.append(f.get("name"))
@ -236,7 +236,7 @@ class StepController(HttpAPI, CRUDController):
inactive = await self.model.get_by_state("I")
active = await self.model.get_by_state("A")
print("STEPES", inactive, active)
if active is not None:
active.state = 'D'

View file

@ -2,7 +2,7 @@ import datetime
from aiohttp import web
from aiojobs.aiohttp import get_scheduler_from_app
from core.api.decorator import request_mapping
from cbpi_api import *
class SystemController():
@ -28,9 +28,7 @@ class SystemController():
result = []
for j in scheduler:
try:
print(datetime.datetime.fromtimestamp(
j.start_time
).strftime('%Y-%m-%d %H:%M:%S'))
result.append(dict(name=j.name, type=j.type, time=j.start_time))
except:
pass

View file

@ -26,8 +26,11 @@ from core.eventbus import EventBus
from core.http_endpoints.http_login import Login
from core.utils import *
from core.websocket import WebSocket
from core.utils.encoder import ComplexEncoder
logger = logging.getLogger(__file__)
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
@ -72,12 +75,14 @@ class CraftBeerPi():
self.kettle = KettleController(self)
self.step = StepController(self)
self.notification = NotificationController(self)
#self.dummy = MyComp(self)
self.login = Login(self)
self.plugin.load_plugins()
self.plugin.load_plugins_from_evn()
self.register_events(self.ws)
@ -193,7 +198,7 @@ class CraftBeerPi():
if url_prefix is not None:
print("Prefx", url_prefix)
logger.debug("URL Prefix: %s " % (url_prefix,))
sub = web.Application()
sub.add_routes(routes)
if static is not None:
@ -242,12 +247,13 @@ class CraftBeerPi():
:return:
'''
print("INIT CONTROLLER")
from pyfiglet import Figlet
f = Figlet(font='big')
print("")
print(f.renderText("%s %s" % (self.config.get("name"), self.config.get("version"))))
print("")
async def init_database(app):
await DBModel.test_connection()
@ -256,14 +262,22 @@ class CraftBeerPi():
await self.step.init()
await self.actor.init()
await self.kettle.init()
await self.config2.init()
print(self.sensor.info())
print(self.actor.info())
import pprint
pprint.pprint(self.bus.dump())
#pprint.pprint(self.bus.dump())
async def load_plugins(app):
#await PluginController.load_plugin_list()
#self.plugin.load_plugins()
pass
async def call_initializer(app):

View file

@ -36,13 +36,9 @@ class DBModel(object):
@classmethod
async def test_connection(self):
async with aiosqlite.connect(TEST_DB) as db:
assert isinstance(db, aiosqlite.Connection)
this_directory = os.path.dirname(__file__)
qry = open(os.path.join(this_directory, '../sql/create_table_user.sql'), 'r').read()
cursor = await db.executescript(qry)
@ -67,7 +63,8 @@ class DBModel(object):
if cls.__as_array__ is True:
result.append(cls(row))
else:
result[row.get("id")] = cls(row)
result[row.get(cls.__priamry_key__)] = cls(row)
await cursor.close()
return result
@ -121,7 +118,7 @@ class DBModel(object):
else:
data = data + (kwargs.get(f),)
print("INSERT DATA", query, data)
cursor = await db.execute(query, data)
await db.commit()

View file

@ -1,9 +1,6 @@
import asyncio
import inspect
import logging
import json
import time
class EventBus(object):
@ -101,13 +98,13 @@ class EventBus(object):
else:
self.loop = asyncio.get_event_loop()
print(self.loop)
def sync_fire(self,topic: str,timeout=1, **kwargs):
self.loop.create_task(self.fire(topic=topic, timeout=timeout, **kwargs))
async def fire(self, topic: str, timeout=1, **kwargs):
print("FIRE")
futures = {}
async def wait(futures):

View file

@ -1,7 +1,6 @@
from aiohttp import web
from core.api.decorator import on_event, request_mapping
from core.api.extension import CBPiExtension
from cbpi_api import *
from core.controller.crud_controller import CRUDController
from core.database.orm_framework import DBModel
from core.http_endpoints.http_api import HttpAPI
@ -32,12 +31,12 @@ class MyComp(CBPiExtension, CRUDController, HttpAPI):
@on_event(topic="actor/#")
async def listen(self, **kwargs):
print("Test", kwargs)
pass
@on_event(topic="kettle/+/automatic")
async def listen2(self, **kwargs):
print("HANDLE AUTOMATIC", kwargs)
await self.cbpi.bus.fire(topic="actor/%s/toggle" % 1, id=1)

View file

@ -1,6 +1,6 @@
import logging
from core.api import CBPiActor, Property, action
from cbpi_api import *
class CustomActor(CBPiActor):
@ -9,10 +9,10 @@ class CustomActor(CBPiActor):
gpio = Property.Number(label="Test")
def init(self):
print("#########INIT MY CUSTOM ACTOR")
pass
def stop(self):
print("#########STOP MY CUSTOM ACTOR")
pass
@action(key="name", parameters={})
@ -23,14 +23,14 @@ class CustomActor(CBPiActor):
super().state()
def off(self):
print("OFF", self.gpio)
# Code to swtich the actor off goes here
self.state = False
def on(self, power=100):
print("ON", self.gpio)
# Code to swtich the actor on goes here

View file

@ -1,8 +1,6 @@
import asyncio
from core.api import Property, on_event
from core.api.kettle_logic import CBPiKettleLogic
from cbpi_api import *
class CustomLogic(CBPiKettleLogic):
@ -29,14 +27,14 @@ class CustomLogic(CBPiKettleLogic):
if timeout is not None:
try:
print("----> WAIT FOR FUTURE")
await asyncio.wait_for(future_obj, timeout=timeout)
print("------> TIMEOUT")
return future_obj.result()
except asyncio.TimeoutError:
print('timeout!')
pass
else:
print("----> WAIT FOR FUTURE")
await future_obj
return future_obj.result()
@ -51,10 +49,10 @@ class CustomLogic(CBPiKettleLogic):
self.cbpi.bus.unregister(my_callback)
kwargs["future"].set_result("AMAZING")
else:
print("OTHER VALUE", value)
pass
result = await self.wait_for_event("sensor/1", callback=my_callback)
print("THE RESULT", result)
'''
@ -68,7 +66,7 @@ class CustomLogic(CBPiKettleLogic):
break
await asyncio.sleep(1)
'''
print("YES IM FINISHED STOP LOGIC")
def setup(cbpi):

View file

@ -2,9 +2,7 @@ import asyncio
import logging
import random
from core.api import CBPiActor, Property, action, background_task
from core.api.sensor import CBPiSensor
from cbpi_api import *
class CustomSensor(CBPiSensor):
@ -38,7 +36,7 @@ class CustomSensor(CBPiSensor):
self.value = self.value + 1
await cbpi.bus.fire("sensor/%s" % self.id, value=self.value)
print("SENSOR IS RUNNING", self.value)

View file

@ -1,7 +1,6 @@
import asyncio
from core.api import Property, action
from core.api.step import CBPiSimpleStep
from cbpi_api import *
class CustomStepCBPi(CBPiSimpleStep):
@ -23,7 +22,7 @@ class CustomStepCBPi(CBPiSimpleStep):
if self.i == 20:
self.next()
self.cbpi.notify(key="step", message="HELLO FROM STEP")
print("RUN STEP", self.id, self.name, self.__dict__)
def setup(cbpi):

View file

@ -2,7 +2,9 @@ import logging
from aiohttp import web
from core.api.decorator import request_mapping
from cbpi_api import *
from core.utils.utils import json_dumps

View file

@ -1,7 +1,7 @@
from aiohttp import web
from aiohttp_auth import auth
from core.api.decorator import request_mapping
from cbpi_api import *
class Login():

View file

@ -86,7 +86,7 @@ class Scheduler(*bases):
if self._closed:
return
self._closed = True # prevent adding new jobs
print("####### CLOSE")
jobs = self._jobs
if jobs:
# cleanup pending queue
@ -113,7 +113,7 @@ class Scheduler(*bases):
def _done(self, job):
print("JOB DONE")
self.cbpi.bus.sync_fire("job/%s/done" % job.type, type=job.type, key=job.name)
self._jobs.discard(job)
if not self.pending_count:

View file

@ -55,7 +55,7 @@ class MQTTMatcher(object):
self._root = self.Node()
def __setitem__(self, key, value):
print("...",key, value)
node = self._root
for sym in key.split('/'):

View file

@ -7,15 +7,13 @@ class ComplexEncoder(JSONEncoder):
def default(self, obj):
from core.database.model import ActorModel
from core.database.orm_framework import DBModel
from core.api.kettle_logic import CBPiKettleLogic
print("OBJECT", obj)
try:
if isinstance(obj, DBModel):
return obj.__dict__
elif callable(getattr(obj, "reprJSON")):
return obj.reprJSON()
#elif callable(getattr(obj, "reprJSON")):
# return obj.reprJSON()
#elif isinstance(obj, ActorModel):
# return None
elif hasattr(obj, "callback"):

View file

@ -1,7 +1,6 @@
from pprint import pprint
from core.api.property import Property
from cbpi_api import *
from core.utils.encoder import ComplexEncoder
__all__ = ['load_config',"json_dumps", "parse_props"]
@ -16,10 +15,13 @@ from core.database.model import DBModel, ActorModel
def load_config(fname):
try:
with open(fname, 'rt') as f:
data = yaml.load(f)
return data
except:
except Exception as e:
pass
@ -63,7 +65,7 @@ def parse_props(self, cls):
key = method.__getattribute__("key")
parameters = method.__getattribute__("parameters")
result["actions"].append({"method": method_name, "label": key, "parameters": parameters})
pprint(result, width=200)

View file

@ -5,8 +5,7 @@ import json
import aiohttp
from aiohttp import web
from typing import Iterable, Callable
from core.api import on_event
from cbpi_api import *
class WebSocket:
@ -19,11 +18,12 @@ class WebSocket:
@on_event(topic="#")
async def listen(self, topic, **kwargs):
print("WS", topic)
from core.utils.encoder import ComplexEncoder
data = json.dumps(dict(topic=topic, data=dict(**kwargs)),skipkeys=True, check_circular=True, cls=ComplexEncoder)
self.logger.info("PUSH %s " % data)
self.send(json.dumps(dict(topic=topic, data=dict(**kwargs)),skipkeys=True, check_circular=True, cls=ComplexEncoder))
self.send(data)
def send(self, data):

Binary file not shown.

View file

@ -21,7 +21,7 @@ ActorController
CBPiActor
^^^^^^^^^
.. autoclass:: core.api.actor.CBPiActor
.. autoclass:: cbpi_api.CBPiActor
:members:
:private-members:
:undoc-members:

View file

@ -16,7 +16,7 @@ CBPiKettleLogic
^^^^^^^^^^^^^^^
.. autoclass:: core.api.kettle_logic.CBPiKettleLogic
.. autoclass:: cbpi_api.CBPiKettleLogic
:members:
:show-inheritance:
:inherited-members:

View file

@ -23,7 +23,7 @@ Custom Actor
.. autoclass:: core.api.property.Property
.. autoclass:: cbpi_api.Property
:members:
:private-members:
:undoc-members:

View file

@ -15,7 +15,7 @@ Sensor Controller
CBPiSensor
^^^^^^^^^^
.. autoclass:: core.api.sensor.CBPiSensor
.. autoclass:: cbpi_api.CBPiSensor
:members:
:private-members:
:undoc-members:

View file

@ -16,7 +16,7 @@ StepController
CBPiSimpleStep
^^^^^^^^^^^^^^
.. autoclass:: core.api.step.CBPiSimpleStep
.. autoclass:: cbpi_api.CBPiSimpleStep
:members:
:undoc-members:
:show-inheritance:
@ -30,7 +30,6 @@ This is an example of a custom step. The Step class need to extend Simple step.
.. literalinclude:: ../../core/extension/dummystep/__init__.py
:caption: __init__.py
:name: __init__-py
:language: python
:linenos:

2
run.py
View file

@ -1,5 +1,3 @@
from core.api.decorator import on_event
from core.controller.notification_controller import NotificationController
from core.craftbeerpi import CraftBeerPi

0
sensor_None.log Normal file
View file

56
tests/test_config.py Normal file
View file

@ -0,0 +1,56 @@
import asyncio
import time
import aiosqlite
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from core.craftbeerpi import CraftBeerPi
from cbpi_api.config import ConfigType
class ConfigTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
self.cbpi.setup()
return self.cbpi.app
@unittest_run_loop
async def test_get(self):
assert await self.cbpi.config2.get("CBPI_TEST_1", 1) == "22"
@unittest_run_loop
async def test_set_get(self):
value = str(time.time())
await self.cbpi.config2.set("CBPI_TEST_2", value)
assert await self.cbpi.config2.get("CBPI_TEST_2", 1) == value
@unittest_run_loop
async def test_add(self):
value = str(time.time())
key = "CBPI_TEST_3"
async with aiosqlite.connect("./craftbeerpi.db") as db:
await db.execute("DELETE FROM config WHERE name = ? ", (key,))
await db.commit()
await self.cbpi.config2.add(key, value, type=ConfigType.STRING, description="test")
@unittest_run_loop
async def test_http_set(self):
value = str(time.time())
key = "CBPI_TEST_3"
await self.cbpi.config2.set(key, value)
assert await self.cbpi.config2.get(key, 1) == value
resp = await self.client.request("POST", "/config/%s/" % key, json={'value': '1'})
assert resp.status == 204
assert await self.cbpi.config2.get(key, -1) == "1"
@unittest_run_loop
async def test_http_get(self):
resp = await self.client.request("GET", "/config/")
assert resp.status == 200
#print(await eresp.json())