mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-27 17:28:02 +01:00
Lots of changes
This commit is contained in:
parent
0496b04608
commit
8ba6b4c506
33 changed files with 941 additions and 971 deletions
1232
.idea/workspace.xml
1232
.idea/workspace.xml
File diff suppressed because it is too large
Load diff
|
@ -45,5 +45,3 @@ class CBPiActor(CBPiExtension, metaclass=ABCMeta):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def reprJSON(self):
|
|
||||||
return dict(state=True)
|
|
|
@ -50,5 +50,3 @@ class CBPiExtension():
|
||||||
except:
|
except:
|
||||||
logger.warning("Faild to load config %s/config.yaml" % path)
|
logger.warning("Faild to load config %s/config.yaml" % path)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import json
|
||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
@ -6,22 +7,39 @@ from abc import abstractmethod,ABCMeta
|
||||||
|
|
||||||
class CBPiSimpleStep(metaclass=ABCMeta):
|
class CBPiSimpleStep(metaclass=ABCMeta):
|
||||||
|
|
||||||
__dirty = False
|
|
||||||
managed_fields = []
|
managed_fields = []
|
||||||
_interval = 1
|
|
||||||
_max_exceptions = 2
|
|
||||||
_exception_count = 0
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, cbpi="", managed_fields=[], id="", name="", *args, **kwargs):
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
print(kwargs)
|
self._exception_count = 0
|
||||||
for a in kwargs:
|
self._interval = 0.1
|
||||||
super(CBPiSimpleStep, self).__setattr__(a, kwargs.get(a))
|
self._max_exceptions = 2
|
||||||
self.id = kwargs.get("id")
|
self.__dirty = False
|
||||||
|
self.cbpi = cbpi
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
if managed_fields:
|
||||||
|
self.managed_fields = managed_fields
|
||||||
|
for a in managed_fields:
|
||||||
|
super(CBPiSimpleStep, self).__setattr__(a, kwargs.get(a, None))
|
||||||
|
|
||||||
self.is_stopped = False
|
self.is_stopped = False
|
||||||
self.is_next = False
|
self.is_next = False
|
||||||
self.start = time.time()
|
self.start = time.time()
|
||||||
|
|
||||||
|
self.logger.info(self.__repr__())
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
mf = {}
|
||||||
|
has_cbpi = True if self.cbpi is not None else False
|
||||||
|
for f in self.managed_fields:
|
||||||
|
mf[f] = super(CBPiSimpleStep, self).__getattribute__(f)
|
||||||
|
return json.dumps(dict(type=self.__class__.__name__, id=self.id, name=self.name, has_link_to_cbpi=has_cbpi, managed_fields=mf))
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def running(self):
|
def running(self):
|
||||||
'''
|
'''
|
||||||
Method checks if the step should continue running.
|
Method checks if the step should continue running.
|
||||||
|
@ -39,6 +57,9 @@ class CBPiSimpleStep(metaclass=ABCMeta):
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
|
|
||||||
|
#while self.running():
|
||||||
|
# print(".... Step %s ...." % self.id)
|
||||||
|
# await asyncio.sleep(0.1)
|
||||||
'''
|
'''
|
||||||
This method in running in the background. It invokes the run_cycle method in the configured interval
|
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
|
It checks if a managed variable was modified in the last exection cycle. If yes, the method will persisit the new value of the
|
||||||
|
@ -61,7 +82,6 @@ class CBPiSimpleStep(metaclass=ABCMeta):
|
||||||
await asyncio.sleep(self._interval)
|
await asyncio.sleep(self._interval)
|
||||||
|
|
||||||
if self.is_dirty():
|
if self.is_dirty():
|
||||||
print("DIRTY")
|
|
||||||
# Now we have to store the managed props
|
# Now we have to store the managed props
|
||||||
state = {}
|
state = {}
|
||||||
for field in self.managed_fields:
|
for field in self.managed_fields:
|
||||||
|
@ -72,6 +92,7 @@ class CBPiSimpleStep(metaclass=ABCMeta):
|
||||||
await self.cbpi.bus.fire("step/update")
|
await self.cbpi.bus.fire("step/update")
|
||||||
self.reset_dirty()
|
self.reset_dirty()
|
||||||
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def run_cycle(self):
|
async def run_cycle(self):
|
||||||
'''
|
'''
|
||||||
|
|
44
cbpi/cli.py
44
cbpi/cli.py
|
@ -1,5 +1,7 @@
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
import requests
|
||||||
|
import yaml
|
||||||
|
|
||||||
from cbpi.craftbeerpi import CraftBeerPi
|
from cbpi.craftbeerpi import CraftBeerPi
|
||||||
import os
|
import os
|
||||||
|
@ -13,6 +15,7 @@ def create_plugin_file():
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "plugin_list.txt")
|
srcfile = os.path.join(os.path.dirname(__file__), "config", "plugin_list.txt")
|
||||||
destfile = os.path.join(".", 'config')
|
destfile = os.path.join(".", 'config')
|
||||||
shutil.copy(srcfile, destfile)
|
shutil.copy(srcfile, destfile)
|
||||||
|
print("Plugin Folder created")
|
||||||
|
|
||||||
def create_config_file():
|
def create_config_file():
|
||||||
import os.path
|
import os.path
|
||||||
|
@ -20,19 +23,49 @@ def create_config_file():
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.yaml")
|
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.yaml")
|
||||||
destfile = os.path.join(".", 'config')
|
destfile = os.path.join(".", 'config')
|
||||||
shutil.copy(srcfile, destfile)
|
shutil.copy(srcfile, destfile)
|
||||||
|
print("Config Folder created")
|
||||||
|
|
||||||
def create_home_folder_structure():
|
def create_home_folder_structure():
|
||||||
pathlib.Path(os.path.join(".", 'logs/sensors')).mkdir(parents=True, exist_ok=True)
|
pathlib.Path(os.path.join(".", 'logs/sensors')).mkdir(parents=True, exist_ok=True)
|
||||||
pathlib.Path(os.path.join(".", 'config')).mkdir(parents=True, exist_ok=True)
|
pathlib.Path(os.path.join(".", 'config')).mkdir(parents=True, exist_ok=True)
|
||||||
|
print("Log Folder created")
|
||||||
|
|
||||||
def copy_splash():
|
def copy_splash():
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "splash.png")
|
srcfile = os.path.join(os.path.dirname(__file__), "config", "splash.png")
|
||||||
destfile = os.path.join(".", 'config')
|
destfile = os.path.join(".", 'config')
|
||||||
shutil.copy(srcfile, destfile)
|
shutil.copy(srcfile, destfile)
|
||||||
|
print("Splash Srceen created")
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_setup():
|
||||||
|
|
||||||
|
if os.path.exists(os.path.join(".", "config", "config.yaml")) is False:
|
||||||
|
print("***************************************************")
|
||||||
|
print("CraftBeerPi Config File not found: %s" % os.path.join(".", "config", "config.yaml"))
|
||||||
|
print("Please run 'cbpi setup' before starting the server ")
|
||||||
|
print("***************************************************")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def list_plugins():
|
||||||
|
print("***************************************************")
|
||||||
|
print("CraftBeerPi 4.x Plugin List")
|
||||||
|
print("***************************************************")
|
||||||
|
plugins_yaml = "https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins_v4.yaml"
|
||||||
|
r = requests.get(plugins_yaml)
|
||||||
|
|
||||||
|
data = yaml.load(r.content, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
|
||||||
|
for name, value in data.items():
|
||||||
|
print(name)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description='Welcome to CraftBeerPi 4')
|
parser = argparse.ArgumentParser(description='Welcome to CraftBeerPi 4')
|
||||||
parser.add_argument("action", type=str, help="start,stop,restart,setup")
|
parser.add_argument("action", type=str, help="start,stop,restart,setup,plugins")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
@ -40,15 +73,24 @@ def main():
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||||
|
|
||||||
if args.action == "setup":
|
if args.action == "setup":
|
||||||
|
print("Setting up CBPi")
|
||||||
create_home_folder_structure()
|
create_home_folder_structure()
|
||||||
create_plugin_file()
|
create_plugin_file()
|
||||||
create_config_file()
|
create_config_file()
|
||||||
copy_splash()
|
copy_splash()
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.action == "plugins":
|
||||||
|
list_plugins()
|
||||||
|
return
|
||||||
|
|
||||||
if args.action == "start":
|
if args.action == "start":
|
||||||
|
if check_for_setup() is False:
|
||||||
|
return
|
||||||
|
|
||||||
cbpi = CraftBeerPi()
|
cbpi = CraftBeerPi()
|
||||||
cbpi.start()
|
cbpi.start()
|
||||||
|
return
|
||||||
|
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
|
||||||
name: CraftBeerPi
|
name: CraftBeerPi
|
||||||
version: 4.1
|
version: 4.0.1_alpha
|
||||||
|
|
||||||
#index_url: /myext
|
#: /myext
|
||||||
|
|
||||||
port: 8080
|
port: 8080
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from asyncio import Future
|
|
||||||
from cbpi.api import *
|
|
||||||
from voluptuous import Schema
|
from voluptuous import Schema
|
||||||
|
|
||||||
|
from cbpi.api import *
|
||||||
from cbpi.controller.crud_controller import CRUDController
|
from cbpi.controller.crud_controller import CRUDController
|
||||||
from cbpi.database.model import ActorModel
|
from cbpi.database.model import ActorModel
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ class ActorController(CRUDController):
|
||||||
self.cache[actor.id].instance = clazz(**cfg)
|
self.cache[actor.id].instance = clazz(**cfg)
|
||||||
self.cache[actor.id].instance.init()
|
self.cache[actor.id].instance.init()
|
||||||
|
|
||||||
|
|
||||||
await self.cbpi.bus.fire(topic="actor/%s/initialized" % actor.id, id=actor.id)
|
await self.cbpi.bus.fire(topic="actor/%s/initialized" % actor.id, id=actor.id)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
@ -73,6 +75,7 @@ class ActorController(CRUDController):
|
||||||
|
|
||||||
actor_id = int(actor_id)
|
actor_id = int(actor_id)
|
||||||
if actor_id in self.cache:
|
if actor_id in self.cache:
|
||||||
|
|
||||||
self.logger.debug("ON %s" % actor_id)
|
self.logger.debug("ON %s" % actor_id)
|
||||||
actor = self.cache[actor_id].instance
|
actor = self.cache[actor_id].instance
|
||||||
actor.on(power)
|
actor.on(power)
|
||||||
|
@ -141,7 +144,7 @@ class ActorController(CRUDController):
|
||||||
:param m:
|
:param m:
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
print("INIT ACTION")
|
|
||||||
await self._init_actor(m)
|
await self._init_actor(m)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from cbpi.database.model import ConfigModel
|
||||||
from cbpi.utils import load_config
|
from cbpi.utils import load_config
|
||||||
|
|
||||||
|
|
||||||
class ConfigController():
|
class ConfigController:
|
||||||
'''
|
'''
|
||||||
The main actor controller
|
The main actor controller
|
||||||
'''
|
'''
|
||||||
|
@ -24,14 +24,13 @@ class ConfigController():
|
||||||
|
|
||||||
async def init(self):
|
async def init(self):
|
||||||
this_directory = os.path.dirname(__file__)
|
this_directory = os.path.dirname(__file__)
|
||||||
|
|
||||||
self.static = load_config("./config/config.yaml")
|
self.static = load_config("./config/config.yaml")
|
||||||
items = await self.model.get_all()
|
items = await self.model.get_all()
|
||||||
for key, value in items.items():
|
for key, value in items.items():
|
||||||
self.cache[value.name] = value
|
self.cache[value.name] = value
|
||||||
|
|
||||||
def get(self, name, default=None):
|
def get(self, name, default=None):
|
||||||
self.logger.info("GET CONFIG VALUE %s (default %s)" % (name, default))
|
self.logger.debug("GET CONFIG VALUE %s (default %s)" % (name, default))
|
||||||
if name in self.cache and self.cache[name].value is not None:
|
if name in self.cache and self.cache[name].value is not None:
|
||||||
return self.cache[name].value
|
return self.cache[name].value
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -70,7 +70,7 @@ class CRUDController(metaclass=ABCMeta):
|
||||||
|
|
||||||
|
|
||||||
await self._pre_add_callback(data)
|
await self._pre_add_callback(data)
|
||||||
print("INSSERT ADD", data)
|
|
||||||
|
|
||||||
m = await self.model.insert(**data)
|
m = await self.model.insert(**data)
|
||||||
|
|
||||||
|
@ -96,10 +96,12 @@ class CRUDController(metaclass=ABCMeta):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.logger.debug("Update Sensor %s - %s " % (id, data))
|
self.logger.debug("Update Sensor %s - %s " % (id, data))
|
||||||
|
|
||||||
id = int(id)
|
id = int(id)
|
||||||
|
|
||||||
if id not in self.cache:
|
if self.caching is True and id not in self.cache:
|
||||||
self.logger.debug("Sensor %s Not in Cache" % (id,))
|
|
||||||
|
self.logger.debug("%s %s Not in Cache" % (self.name, id))
|
||||||
raise CBPiException("%s with id %s not found" % (self.name,id))
|
raise CBPiException("%s with id %s not found" % (self.name,id))
|
||||||
|
|
||||||
data["id"] = id
|
data["id"] = id
|
||||||
|
@ -115,14 +117,10 @@ class CRUDController(metaclass=ABCMeta):
|
||||||
self.cache[id].__dict__.update(**data)
|
self.cache[id].__dict__.update(**data)
|
||||||
m = self.cache[id] = await self.model.update(**self.cache[id].__dict__)
|
m = self.cache[id] = await self.model.update(**self.cache[id].__dict__)
|
||||||
await self._post_update_callback(self.cache[id])
|
await self._post_update_callback(self.cache[id])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
m = await self.model.update(**data)
|
m = await self.model.update(**data)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def _pre_delete_callback(self, m):
|
async def _pre_delete_callback(self, m):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from voluptuous import Schema, MultipleInvalid
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import logging
|
||||||
from cbpi.job.aiohttp import setup, get_scheduler_from_app
|
from cbpi.job.aiohttp import setup, get_scheduler_from_app
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class JobController(object):
|
class JobController(object):
|
||||||
|
|
||||||
def __init__(self, cbpi):
|
def __init__(self, cbpi):
|
||||||
|
|
|
@ -3,7 +3,7 @@ from cbpi.api import *
|
||||||
from cbpi.controller.crud_controller import CRUDController
|
from cbpi.controller.crud_controller import CRUDController
|
||||||
from cbpi.database.model import KettleModel
|
from cbpi.database.model import KettleModel
|
||||||
from cbpi.job.aiohttp import get_scheduler_from_app
|
from cbpi.job.aiohttp import get_scheduler_from_app
|
||||||
|
import logging
|
||||||
|
|
||||||
class KettleController(CRUDController):
|
class KettleController(CRUDController):
|
||||||
'''
|
'''
|
||||||
|
@ -15,6 +15,7 @@ class KettleController(CRUDController):
|
||||||
super(KettleController, self).__init__(cbpi)
|
super(KettleController, self).__init__(cbpi)
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.types = {}
|
self.types = {}
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
self.cbpi.register(self)
|
self.cbpi.register(self)
|
||||||
|
|
||||||
async def init(self):
|
async def init(self):
|
||||||
|
@ -58,7 +59,7 @@ class KettleController(CRUDController):
|
||||||
kettle = self.cache[int(kid)]
|
kettle = self.cache[int(kid)]
|
||||||
kettle.instance = None
|
kettle.instance = None
|
||||||
kettle.state = False
|
kettle.state = False
|
||||||
print("FIRE")
|
|
||||||
await self.cbpi.bus.fire(topic="kettle/%s/logic/stop" % kid)
|
await self.cbpi.bus.fire(topic="kettle/%s/logic/stop" % kid)
|
||||||
|
|
||||||
@on_event(topic="kettle/+/automatic")
|
@on_event(topic="kettle/+/automatic")
|
||||||
|
@ -73,7 +74,7 @@ class KettleController(CRUDController):
|
||||||
'''
|
'''
|
||||||
id = int(id)
|
id = int(id)
|
||||||
|
|
||||||
print("K", id)
|
|
||||||
if id in self.cache:
|
if id in self.cache:
|
||||||
|
|
||||||
kettle = self.cache[id]
|
kettle = self.cache[id]
|
||||||
|
@ -84,7 +85,7 @@ class KettleController(CRUDController):
|
||||||
|
|
||||||
|
|
||||||
if kettle.instance is None:
|
if kettle.instance is None:
|
||||||
print("start")
|
|
||||||
if kettle.logic in self.types:
|
if kettle.logic in self.types:
|
||||||
clazz = self.types.get("CustomKettleLogic")["class"]
|
clazz = self.types.get("CustomKettleLogic")["class"]
|
||||||
cfg = kettle.config.copy()
|
cfg = kettle.config.copy()
|
||||||
|
@ -104,13 +105,9 @@ class KettleController(CRUDController):
|
||||||
def _is_logic_running(self, kettle_id):
|
def _is_logic_running(self, kettle_id):
|
||||||
scheduler = get_scheduler_from_app(self.cbpi.app)
|
scheduler = get_scheduler_from_app(self.cbpi.app)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def heater_on(self, id):
|
async def heater_on(self, id):
|
||||||
'''
|
'''
|
||||||
Convenience Method to switch the heater of a kettle on
|
Convenience Method to switch the heater of a kettle on
|
||||||
|
|
||||||
|
|
||||||
:param id: the kettle id
|
:param id: the kettle id
|
||||||
:return: (boolean, string)
|
:return: (boolean, string)
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
@ -5,15 +6,16 @@ from importlib import import_module
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import yaml
|
import yaml
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from cbpi.api import *
|
|
||||||
|
|
||||||
|
from cbpi.api import *
|
||||||
from cbpi.utils.utils import load_config, json_dumps
|
from cbpi.utils.utils import load_config, json_dumps
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class PluginController():
|
class PluginController():
|
||||||
|
|
||||||
modules = {}
|
modules = {}
|
||||||
types = {}
|
types = {}
|
||||||
|
|
||||||
|
@ -25,9 +27,7 @@ class PluginController():
|
||||||
async def load_plugin_list(self):
|
async def load_plugin_list(self):
|
||||||
async with aiohttp.ClientSession() as session:
|
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('https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins_v4.yaml') as resp:
|
||||||
|
if (resp.status == 200):
|
||||||
if(resp.status == 200):
|
|
||||||
|
|
||||||
data = yaml.load(await resp.text())
|
data = yaml.load(await resp.text())
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class PluginController():
|
||||||
|
|
||||||
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("version") == 4):
|
if (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)
|
||||||
|
@ -55,6 +55,7 @@ class PluginController():
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
def load_plugins_from_evn(self):
|
def load_plugins_from_evn(self):
|
||||||
|
@ -63,7 +64,6 @@ class PluginController():
|
||||||
this_directory = os.path.dirname(__file__)
|
this_directory = os.path.dirname(__file__)
|
||||||
with open("./config/plugin_list.txt") as f:
|
with open("./config/plugin_list.txt") as f:
|
||||||
|
|
||||||
|
|
||||||
plugins = f.read().splitlines()
|
plugins = f.read().splitlines()
|
||||||
plugins = list(set(plugins))
|
plugins = list(set(plugins))
|
||||||
|
|
||||||
|
@ -74,15 +74,42 @@ class PluginController():
|
||||||
self.modules[p] = import_module(p)
|
self.modules[p] = import_module(p)
|
||||||
self.modules[p].setup(self.cbpi)
|
self.modules[p].setup(self.cbpi)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
logger.info("Plugin %s loaded successfully" % p)
|
logger.info("Plugin %s loaded successfully" % p)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("FAILED to load plugin %s " % p)
|
logger.error("FAILED to load plugin %s " % p)
|
||||||
logger.error(e)
|
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="/plugins", method="GET", auth_required=False)
|
@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):
|
async def get_plugins(self, request):
|
||||||
"""
|
"""
|
||||||
---
|
---
|
||||||
|
@ -99,8 +126,6 @@ class PluginController():
|
||||||
"""
|
"""
|
||||||
return web.json_response(await self.load_plugin_list(), dumps=json_dumps)
|
return web.json_response(await self.load_plugin_list(), dumps=json_dumps)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def register(self, name, clazz) -> None:
|
def register(self, name, clazz) -> None:
|
||||||
'''
|
'''
|
||||||
Register a new actor type
|
Register a new actor type
|
||||||
|
@ -110,7 +135,7 @@ class PluginController():
|
||||||
'''
|
'''
|
||||||
logger.info("Register %s Class %s" % (name, clazz.__name__))
|
logger.info("Register %s Class %s" % (name, clazz.__name__))
|
||||||
if issubclass(clazz, CBPiActor):
|
if issubclass(clazz, CBPiActor):
|
||||||
#self.cbpi.actor.types[name] = {"class": clazz, "config": self._parse_props(clazz)}
|
# self.cbpi.actor.types[name] = {"class": clazz, "config": self._parse_props(clazz)}
|
||||||
self.cbpi.actor.types[name] = self._parse_props(clazz)
|
self.cbpi.actor.types[name] = self._parse_props(clazz)
|
||||||
|
|
||||||
if issubclass(clazz, CBPiSensor):
|
if issubclass(clazz, CBPiSensor):
|
||||||
|
@ -131,7 +156,7 @@ class PluginController():
|
||||||
|
|
||||||
result = {"name": name, "class": cls, "properties": [], "actions": []}
|
result = {"name": name, "class": cls, "properties": [], "actions": []}
|
||||||
|
|
||||||
tmpObj = cls()
|
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):
|
||||||
|
|
|
@ -41,7 +41,9 @@ class SensorController(CRUDController):
|
||||||
self.cache[sensor.id].instance = clazz(**cfg)
|
self.cache[sensor.id].instance = clazz(**cfg)
|
||||||
self.cache[sensor.id].instance.init()
|
self.cache[sensor.id].instance.init()
|
||||||
scheduler = get_scheduler_from_app(self.cbpi.app)
|
scheduler = get_scheduler_from_app(self.cbpi.app)
|
||||||
self.cache[sensor.id].instance.job = await scheduler.spawn(self.cache[sensor.id].instance.run(self.cbpi), sensor.name, "sensor")
|
|
||||||
|
self.cache[sensor.id].instance.job = await self.cbpi.job.start_job(self.cache[sensor.id].instance.run(self.cbpi), sensor.name, "sensor")
|
||||||
|
|
||||||
await self.cbpi.bus.fire(topic="sensor/%s/initialized" % sensor.id, id=sensor.id)
|
await self.cbpi.bus.fire(topic="sensor/%s/initialized" % sensor.id, id=sensor.id)
|
||||||
else:
|
else:
|
||||||
self.logger.error("Sensor type '%s' not found (Available Sensor Types: %s)" % (sensor.type, ', '.join(self.types.keys())))
|
self.logger.error("Sensor type '%s' not found (Available Sensor Types: %s)" % (sensor.type, ', '.join(self.types.keys())))
|
||||||
|
|
|
@ -7,16 +7,13 @@ from cbpi.database.model import StepModel
|
||||||
|
|
||||||
|
|
||||||
class StepController(CRUDController):
|
class StepController(CRUDController):
|
||||||
'''
|
|
||||||
The Step Controller. This controller is responsible to start and stop the brewing steps.
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
model = StepModel
|
|
||||||
|
|
||||||
def __init__(self, cbpi):
|
def __init__(self, cbpi):
|
||||||
super(StepController, self).__init__(cbpi)
|
self.model = StepModel
|
||||||
self.caching = False
|
|
||||||
|
self.caching = True
|
||||||
self.is_stopping = False
|
self.is_stopping = False
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.current_task = None
|
self.current_task = None
|
||||||
|
@ -28,36 +25,11 @@ class StepController(CRUDController):
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.starttime = None
|
self.starttime = None
|
||||||
|
|
||||||
async def init(self):
|
def is_running(self):
|
||||||
'''
|
if self.current_step is not None:
|
||||||
Initializer of the the Step Controller.
|
return True
|
||||||
:return:
|
|
||||||
'''
|
|
||||||
await super(StepController, self).init()
|
|
||||||
|
|
||||||
step = await self.model.get_by_state('A')
|
|
||||||
# We have an active step
|
|
||||||
if step is not None:
|
|
||||||
|
|
||||||
# get the type
|
|
||||||
step_type = self.types.get(step.type)
|
|
||||||
|
|
||||||
if step_type is None:
|
|
||||||
# step type not found. cant restart step
|
|
||||||
return
|
|
||||||
|
|
||||||
if step.stepstate is not None:
|
|
||||||
cfg = step.stepstate.copy()
|
|
||||||
else:
|
else:
|
||||||
cfg = {}
|
return False
|
||||||
cfg.update(dict(cbpi=self.cbpi, id=step.id, managed_fields=self._get_manged_fields_as_array(step_type)))
|
|
||||||
|
|
||||||
self.current_step = step_type["class"](**cfg)
|
|
||||||
self.current_job = await self.cbpi.job.start_job(self.current_step.run(), step.name, "step")
|
|
||||||
|
|
||||||
|
|
||||||
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("step/action")
|
@on_event("step/action")
|
||||||
async def handle_action(self, action, **kwargs):
|
async def handle_action(self, action, **kwargs):
|
||||||
|
@ -74,162 +46,154 @@ class StepController(CRUDController):
|
||||||
if self.current_step is not None:
|
if self.current_step is not None:
|
||||||
self.current_step.__getattribute__(action)()
|
self.current_step.__getattribute__(action)()
|
||||||
|
|
||||||
|
|
||||||
@on_event("step/next")
|
|
||||||
async def next(self, **kwargs):
|
|
||||||
'''
|
|
||||||
Event Handler for "step/next".
|
|
||||||
It start the next step
|
|
||||||
|
|
||||||
:param kwargs:
|
|
||||||
:return: None
|
|
||||||
'''
|
|
||||||
|
|
||||||
self.starttime = time.time()
|
|
||||||
if self.current_step is not None and self.is_next is False:
|
|
||||||
self.logger.info("Request Next Step to start. Stopping current step")
|
|
||||||
self.is_next = True
|
|
||||||
self.current_step.stop()
|
|
||||||
else:
|
|
||||||
self.logger.info("Can Start Next")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@on_event("job/step/done")
|
|
||||||
async def _step_done(self, topic, **kwargs):
|
|
||||||
|
|
||||||
'''
|
|
||||||
Event Handler for "step/+/done".
|
|
||||||
Starts the next step
|
|
||||||
|
|
||||||
:param topic:
|
|
||||||
:param kwargs:
|
|
||||||
:return:
|
|
||||||
'''
|
|
||||||
|
|
||||||
# SHUTDONW DO NOTHING
|
|
||||||
self.logger.info("HANDLE DONE IS SHUTDONW %s IS STOPPING %s IS NEXT %s" % ( self.cbpi.shutdown, self.is_stopping, self.is_next))
|
|
||||||
|
|
||||||
|
|
||||||
if self.cbpi.shutdown:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.is_stopping:
|
|
||||||
self.is_stopping = False
|
|
||||||
return
|
|
||||||
self.is_next = False
|
|
||||||
if self.current_step is not None:
|
|
||||||
await self.model.update_state(self.current_step.id, "D", int(time.time()))
|
|
||||||
self.current_step = None
|
|
||||||
await self.start()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _get_manged_fields_as_array(self, type_cfg):
|
def _get_manged_fields_as_array(self, type_cfg):
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
for f in type_cfg.get("properties"):
|
for f in type_cfg.get("properties"):
|
||||||
|
|
||||||
result.append(f.get("name"))
|
result.append(f.get("name"))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def is_running(self):
|
async def init(self):
|
||||||
if self.current_step is not None:
|
|
||||||
return True
|
# load all steps into cache
|
||||||
|
self.cache = await self.model.get_all()
|
||||||
|
|
||||||
|
|
||||||
|
for key, value in self.cache.items():
|
||||||
|
|
||||||
|
# get step type as string
|
||||||
|
step_type = self.types.get(value.type)
|
||||||
|
|
||||||
|
# if step has state
|
||||||
|
if value.stepstate is not None:
|
||||||
|
cfg = value.stepstate.copy()
|
||||||
else:
|
else:
|
||||||
return False
|
cfg = {}
|
||||||
|
|
||||||
|
# set managed fields
|
||||||
|
cfg.update(dict(cbpi=self.cbpi, id=value.id, managed_fields=self._get_manged_fields_as_array(step_type)))
|
||||||
|
|
||||||
|
if value.config is not None:
|
||||||
|
# set config values
|
||||||
|
cfg.update(**value.config)
|
||||||
|
# create step instance
|
||||||
|
value.instance = step_type["class"](**cfg)
|
||||||
|
|
||||||
|
async def get_all(self, force_db_update: bool = True):
|
||||||
|
return self.cache
|
||||||
|
|
||||||
|
def find_next_step(self):
|
||||||
|
# filter
|
||||||
|
inactive_steps = {k: v for k, v in self.cache.items() if v.state == 'I'}
|
||||||
|
if len(inactive_steps) == 0:
|
||||||
|
return None
|
||||||
|
return min(inactive_steps, key=lambda x: inactive_steps[x].order)
|
||||||
|
|
||||||
@on_event("step/start")
|
@on_event("step/start")
|
||||||
async def start(self, **kwargs):
|
async def start(self, **kwargs):
|
||||||
|
|
||||||
'''
|
|
||||||
Start the first step
|
|
||||||
|
|
||||||
:return:None
|
|
||||||
'''
|
|
||||||
|
|
||||||
if self.is_running() is False:
|
if self.is_running() is False:
|
||||||
next_step = await self.model.get_by_state("I")
|
next_step_id = self.find_next_step()
|
||||||
|
if next_step_id:
|
||||||
if next_step is not None:
|
next_step = self.cache[next_step_id]
|
||||||
step_type = self.types[next_step.type]
|
|
||||||
print(step_type)
|
|
||||||
managed_fields = self._get_manged_fields_as_array(step_type)
|
|
||||||
config = dict(cbpi=self.cbpi, id=next_step.id, name=next_step.name, managed_fields=managed_fields)
|
|
||||||
config.update(**next_step.config)
|
|
||||||
self._set_default(step_type, config, managed_fields)
|
|
||||||
|
|
||||||
self.current_step = step_type["class"](**config)
|
|
||||||
|
|
||||||
next_step.state = 'A'
|
next_step.state = 'A'
|
||||||
next_step.stepstate = next_step.config
|
next_step.stepstate = next_step.config
|
||||||
next_step.start = int(time.time())
|
next_step.start = int(time.time())
|
||||||
await self.model.update(**next_step.__dict__)
|
await self.model.update(**next_step.__dict__)
|
||||||
if self.starttime is not None:
|
self.current_step = next_step
|
||||||
end = time.time()
|
# start the step job
|
||||||
d = end - self.starttime
|
self.current_job = await self.cbpi.job.start_job(self.current_step.instance.run(), next_step.name, "step")
|
||||||
print("DURATION", d)
|
|
||||||
else:
|
|
||||||
print("NORMAL START")
|
|
||||||
self.current_job = await self.cbpi.job.start_job(self.current_step.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)
|
||||||
else:
|
else:
|
||||||
await self.cbpi.bus.fire("step/brewing/finished")
|
await self.cbpi.bus.fire("step/brewing/finished")
|
||||||
else:
|
else:
|
||||||
self.logger.error("Process Already Running")
|
self.logger.error("Process Already Running")
|
||||||
print("----------- END")
|
|
||||||
|
|
||||||
def _set_default(self, step_type, config, managed_fields):
|
async def next(self, **kwargs):
|
||||||
for key in managed_fields:
|
|
||||||
if key not in config:
|
|
||||||
config[key] = None
|
|
||||||
|
|
||||||
|
if self.current_step is not None:
|
||||||
|
|
||||||
|
self.is_next = True
|
||||||
|
self.current_step.instance.stop()
|
||||||
|
|
||||||
|
@on_event("job/step/done")
|
||||||
|
async def step_done(self, **kwargs):
|
||||||
|
|
||||||
|
if self.cbpi.shutdown:
|
||||||
|
return
|
||||||
|
if self.is_stopping:
|
||||||
|
self.is_stopping = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.current_step is not None:
|
||||||
|
|
||||||
|
self.current_step.state = "D"
|
||||||
|
await self.model.update_state(self.current_step.id, "D", int(time.time()))
|
||||||
|
self.current_step = None
|
||||||
|
|
||||||
|
# start the next step
|
||||||
|
await self.start()
|
||||||
|
|
||||||
@on_event("step/stop")
|
@on_event("step/stop")
|
||||||
async def stop(self, **kwargs):
|
async def stop_all(self, **kwargs):
|
||||||
|
# RESET DB
|
||||||
if self.current_step is not None:
|
|
||||||
self.current_step.stop()
|
|
||||||
self.is_stopping = True
|
|
||||||
self.current_step = None
|
|
||||||
|
|
||||||
await self.model.reset_all_steps()
|
await self.model.reset_all_steps()
|
||||||
|
# RELOAD all Steps from DB into cache and initialize Instances
|
||||||
|
await self.init()
|
||||||
await self.cbpi.bus.fire("step/brewing/stopped")
|
await self.cbpi.bus.fire("step/brewing/stopped")
|
||||||
|
|
||||||
@on_event("step/reset")
|
|
||||||
async def handle_reset(self, **kwargs):
|
|
||||||
'''
|
|
||||||
Event Handler for "step/reset".
|
|
||||||
Resets the current step
|
|
||||||
|
|
||||||
:param kwargs:
|
|
||||||
:return: None
|
|
||||||
'''
|
|
||||||
if self.current_step is not None:
|
|
||||||
|
|
||||||
await self.stop()
|
|
||||||
self.current_step = None
|
|
||||||
self.is_stopping = True
|
|
||||||
|
|
||||||
await self.model.reset_all_steps()
|
|
||||||
|
|
||||||
@on_event("step/clear")
|
@on_event("step/clear")
|
||||||
async def clear_all(self, **kwargs):
|
async def clear_all(self, **kwargs):
|
||||||
await self.model.delete_all()
|
await self.model.delete_all()
|
||||||
self.cbpi.notify(key="Steps Cleared", message="Steps cleared successfully", type="success")
|
self.cbpi.notify(key="Steps Cleared", message="Steps cleared successfully", type="success")
|
||||||
|
|
||||||
@on_event("step/sort")
|
|
||||||
async def sort(self, topic, data, **kwargs):
|
|
||||||
await self.model.sort(data)
|
|
||||||
|
|
||||||
async def _pre_add_callback(self, data):
|
async def _pre_add_callback(self, data):
|
||||||
|
|
||||||
order = await self.model.get_max_order()
|
order = await self.model.get_max_order()
|
||||||
data["order"] = 1 if order is None else order + 1
|
data["order"] = 1 if order is None else order + 1
|
||||||
data["state"] = "I"
|
data["state"] = "I"
|
||||||
|
data["stepstate"] = {}
|
||||||
return await super()._pre_add_callback(data)
|
return await super()._pre_add_callback(data)
|
||||||
|
|
||||||
|
async def init_step(self, value: StepModel):
|
||||||
|
step_type = self.types.get(value.type)
|
||||||
|
|
||||||
|
# if step has state
|
||||||
|
if value.stepstate is not None:
|
||||||
|
cfg = value.stepstate.copy()
|
||||||
|
else:
|
||||||
|
cfg = {}
|
||||||
|
|
||||||
|
# set managed fields
|
||||||
|
cfg.update(dict(cbpi=self.cbpi, id=value.id, managed_fields=self._get_manged_fields_as_array(step_type)))
|
||||||
|
# set config values
|
||||||
|
cfg.update(**value.config)
|
||||||
|
# create step instance
|
||||||
|
value.instance = step_type["class"](**cfg)
|
||||||
|
return value
|
||||||
|
|
||||||
|
async def _post_add_callback(self, m: StepModel) -> None:
|
||||||
|
self.cache[m.id] = await self.init_step(m)
|
||||||
|
|
||||||
|
async def _post_update_callback(self, m: StepModel) -> None:
|
||||||
|
'''
|
||||||
|
:param m: step model
|
||||||
|
:return: None
|
||||||
|
'''
|
||||||
|
self.cache[m.id] = await self.init_step(m)
|
||||||
|
|
||||||
|
@on_event("step/sort")
|
||||||
|
async def sort(self, topic: 'str', data: 'dict', **kwargs):
|
||||||
|
|
||||||
|
# update order in cache
|
||||||
|
for id, order in data.items():
|
||||||
|
self.cache[int(id)].order = order
|
||||||
|
|
||||||
|
# update oder in database
|
||||||
|
await self.model.sort(data)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
|
@ -52,6 +52,9 @@ async def error_middleware(request, handler):
|
||||||
return web.json_response(status=500, data={'error': message})
|
return web.json_response(status=500, data={'error': message})
|
||||||
except MultipleInvalid as ex:
|
except MultipleInvalid as ex:
|
||||||
return web.json_response(status=500, data={'error': str(ex)})
|
return web.json_response(status=500, data={'error': str(ex)})
|
||||||
|
except Exception as ex:
|
||||||
|
return web.json_response(status=500, data={'error': str(ex)})
|
||||||
|
|
||||||
return web.json_response(status=500, data={'error': message})
|
return web.json_response(status=500, data={'error': message})
|
||||||
|
|
||||||
class CraftBeerPi():
|
class CraftBeerPi():
|
||||||
|
@ -187,7 +190,7 @@ class CraftBeerPi():
|
||||||
api_version=self.static_config.get("version", ""),
|
api_version=self.static_config.get("version", ""),
|
||||||
contact="info@craftbeerpi.com")
|
contact="info@craftbeerpi.com")
|
||||||
|
|
||||||
def notify(self, key, message, type="info"):
|
def notify(self, key: str, message: str, type: str = "info") -> None:
|
||||||
'''
|
'''
|
||||||
This is a convinience method to send notification to the client
|
This is a convinience method to send notification to the client
|
||||||
|
|
||||||
|
@ -216,7 +219,7 @@ class CraftBeerPi():
|
||||||
if url is not None:
|
if url is not None:
|
||||||
raise web.HTTPFound(url)
|
raise web.HTTPFound(url)
|
||||||
else:
|
else:
|
||||||
return web.Response(text="Hello, world")
|
return web.Response(text="Hello from CraftbeerPi!")
|
||||||
|
|
||||||
self.app.add_routes([web.get('/', http_index)])
|
self.app.add_routes([web.get('/', http_index)])
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,13 @@ class ActorModel(DBModel):
|
||||||
'config': dict
|
'config': dict
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
data = dict(**self.__dict__)
|
||||||
|
if hasattr(self,"instance"):
|
||||||
|
data["state"] = self.instance.get_state()
|
||||||
|
del data["instance"]
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class SensorModel(DBModel):
|
class SensorModel(DBModel):
|
||||||
__fields__ = ["name", "type", "config"]
|
__fields__ = ["name", "type", "config"]
|
||||||
|
@ -32,6 +39,15 @@ class SensorModel(DBModel):
|
||||||
'config': dict
|
'config': dict
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
data = dict(**self.__dict__)
|
||||||
|
if hasattr(self,"instance"):
|
||||||
|
data["value"] = self.instance.get_value()
|
||||||
|
data["unit"] = self.instance.get_unit()
|
||||||
|
data["state"] = self.instance.get_state()
|
||||||
|
del data["instance"]
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class ConfigModel(DBModel):
|
class ConfigModel(DBModel):
|
||||||
__fields__ = ["type", "value", "description", "options"]
|
__fields__ = ["type", "value", "description", "options"]
|
||||||
|
@ -54,7 +70,7 @@ class StepModel(DBModel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def update_step_state(cls, step_id, state):
|
async def update_step_state(cls, step_id, state):
|
||||||
print("NOW UPDATE", state)
|
|
||||||
async with aiosqlite.connect(DATABASE_FILE) as db:
|
async with aiosqlite.connect(DATABASE_FILE) as db:
|
||||||
cursor = await db.execute("UPDATE %s SET stepstate = ? WHERE id = ?" % cls.__table_name__, (json.dumps(state), step_id))
|
cursor = await db.execute("UPDATE %s SET stepstate = ? WHERE id = ?" % cls.__table_name__, (json.dumps(state), step_id))
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
@ -93,7 +109,7 @@ class StepModel(DBModel):
|
||||||
|
|
||||||
async with aiosqlite.connect(DATABASE_FILE) as db:
|
async with aiosqlite.connect(DATABASE_FILE) as db:
|
||||||
for key, value in new_order.items():
|
for key, value in new_order.items():
|
||||||
print("ORDER", key, value)
|
|
||||||
await db.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (value, key))
|
await db.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (value, key))
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
|
@ -111,6 +127,19 @@ class StepModel(DBModel):
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
data = dict(**self.__dict__)
|
||||||
|
if hasattr(self,"instance"):
|
||||||
|
data["state_msg"] = self.instance.get_status()
|
||||||
|
del data["instance"]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s, %s, %s, %s, %s" % (self.name, self.start, self.end, self.state, self.order)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "Steps(%s, %s, %s, %s, %s)" % (self.name, self.start, self.end, self.state, self.order)
|
||||||
|
|
||||||
|
|
||||||
class DashboardModel(DBModel):
|
class DashboardModel(DBModel):
|
||||||
__fields__ = ["name"]
|
__fields__ = ["name"]
|
||||||
|
|
|
@ -167,3 +167,7 @@ class DBModel(object):
|
||||||
for idx, col in enumerate(cursor.description):
|
for idx, col in enumerate(cursor.description):
|
||||||
d[col[0]] = row[idx]
|
d[col[0]] = row[idx]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
|
||||||
|
return self.__dict__
|
||||||
|
|
|
@ -24,7 +24,7 @@ class CustomActor(CBPiActor):
|
||||||
# Custom property which can be configured by the user
|
# Custom property which can be configured by the user
|
||||||
@action("test", parameters={})
|
@action("test", parameters={})
|
||||||
def action1(self):
|
def action1(self):
|
||||||
print("EOOOHOOO")
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import asyncio
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from cbpi.api import *
|
from cbpi.api import *
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
class CustomSensor(CBPiSensor):
|
class CustomSensor(CBPiSensor):
|
||||||
|
|
||||||
|
@ -61,6 +62,7 @@ class HTTPSensor(CBPiSensor):
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
super().init()
|
super().init()
|
||||||
|
|
||||||
self.state = True
|
self.state = True
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
|
@ -80,10 +82,12 @@ class HTTPSensor(CBPiSensor):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
value = cache.pop(self.key, None)
|
value = cache.pop(self.key, None)
|
||||||
|
print("HTTP SENSOR READ", value)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
self.log_data(value)
|
self.log_data(value)
|
||||||
await cbpi.bus.fire("sensor/%s" % self.id, value=value)
|
await cbpi.bus.fire("sensor/%s/data" % self.id, value=value)
|
||||||
except:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class HTTPSensorEndpoint(CBPiExtension):
|
class HTTPSensorEndpoint(CBPiExtension):
|
||||||
|
@ -95,6 +99,7 @@ class HTTPSensorEndpoint(CBPiExtension):
|
||||||
|
|
||||||
:param cbpi:
|
:param cbpi:
|
||||||
'''
|
'''
|
||||||
|
self.pattern_check = re.compile("^[a-zA-Z0-9,.]{0,10}$")
|
||||||
|
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
# register component for http, events
|
# register component for http, events
|
||||||
|
@ -125,10 +130,17 @@ class HTTPSensorEndpoint(CBPiExtension):
|
||||||
"204":
|
"204":
|
||||||
description: successful operation
|
description: successful operation
|
||||||
"""
|
"""
|
||||||
print("HALLO")
|
|
||||||
global cache
|
global cache
|
||||||
key = request.match_info['key']
|
key = request.match_info['key']
|
||||||
value = request.match_info['value']
|
value = request.match_info['value']
|
||||||
|
if self.pattern_check.match(key) is None:
|
||||||
|
return web.json_response(status=422, data={'error': "Key not matching pattern ^[a-zA-Z0-9,.]{0,10}$"})
|
||||||
|
|
||||||
|
if self.pattern_check.match(value) is None:
|
||||||
|
return web.json_response(status=422, data={'error': "Data not matching pattern ^[a-zA-Z0-9,.]{0,10}$"})
|
||||||
|
|
||||||
|
print("HTTP SENSOR ", key, value)
|
||||||
cache[key] = value
|
cache[key] = value
|
||||||
|
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
|
@ -9,15 +9,23 @@ class CustomStepCBPi(CBPiSimpleStep):
|
||||||
name1 = Property.Number(label="Test", configurable=True)
|
name1 = Property.Number(label="Test", configurable=True)
|
||||||
timer_end = Property.Number(label="Test", default_value=None)
|
timer_end = Property.Number(label="Test", default_value=None)
|
||||||
temp = Property.Number(label="Temperature", default_value=50, configurable=True)
|
temp = Property.Number(label="Temperature", default_value=50, configurable=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
@action(key="name", parameters=None)
|
@action(key="name", parameters=None)
|
||||||
def test(self, **kwargs):
|
def test(self, **kwargs):
|
||||||
self.name="WOOHOO"
|
self.name="WOOHOO"
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return "Status: %s Temp" % self.temp
|
||||||
|
|
||||||
async def run_cycle(self):
|
async def run_cycle(self):
|
||||||
|
|
||||||
|
self.next()
|
||||||
|
|
||||||
|
'''
|
||||||
print("RUN", self.name1, self.managed_fields, self.timer_end)
|
print("RUN", self.name1, self.managed_fields, self.timer_end)
|
||||||
self.i = self.i + 1
|
self.i = self.i + 1
|
||||||
|
|
||||||
|
@ -26,7 +34,7 @@ class CustomStepCBPi(CBPiSimpleStep):
|
||||||
|
|
||||||
if self.i == 10:
|
if self.i == 10:
|
||||||
self.next()
|
self.next()
|
||||||
|
'''
|
||||||
|
|
||||||
#self.cbpi.notify(key="step", message="HELLO FROM STEP")
|
#self.cbpi.notify(key="step", message="HELLO FROM STEP")
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ class ConfigHttpEndpoints(HttpCrudEndpoints):
|
||||||
"204":
|
"204":
|
||||||
description: successful operation
|
description: successful operation
|
||||||
"""
|
"""
|
||||||
print("HALLO PARA")
|
|
||||||
name = request.match_info['name']
|
name = request.match_info['name']
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
await self.controller.set(name=name, value=data.get("value"))
|
await self.controller.set(name=name, value=data.get("value"))
|
||||||
|
|
|
@ -22,7 +22,7 @@ class HttpCrudEndpoints():
|
||||||
|
|
||||||
@request_mapping(path="/", auth_required=False)
|
@request_mapping(path="/", auth_required=False)
|
||||||
async def http_get_all(self, request):
|
async def http_get_all(self, request):
|
||||||
return web.json_response(await self.controller.get_all(force_db_update=True), dumps=json_dumps)
|
return web.json_response(await self.controller.get_all(), dumps=json_dumps)
|
||||||
|
|
||||||
@request_mapping(path="/{id:\d+}", auth_required=False)
|
@request_mapping(path="/{id:\d+}", auth_required=False)
|
||||||
async def http_get_one(self, request):
|
async def http_get_one(self, request):
|
||||||
|
@ -39,6 +39,8 @@ class HttpCrudEndpoints():
|
||||||
async def http_update(self, request):
|
async def http_update(self, request):
|
||||||
id = int(request.match_info['id'])
|
id = int(request.match_info['id'])
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
|
|
||||||
|
|
||||||
obj = await self.controller.update(id, data)
|
obj = await self.controller.update(id, data)
|
||||||
return web.json_response(obj, dumps=json_dumps)
|
return web.json_response(obj, dumps=json_dumps)
|
||||||
|
|
||||||
|
|
|
@ -257,6 +257,6 @@ class DashBoardHttpEndpoints(HttpCrudEndpoints):
|
||||||
schema = Schema({"id": int, "x": int, "y": int})
|
schema = Schema({"id": int, "x": int, "y": int})
|
||||||
schema(data)
|
schema(data)
|
||||||
content_id = int(request.match_info['content_id'])
|
content_id = int(request.match_info['content_id'])
|
||||||
print("MOVE",content_id)
|
|
||||||
return web.json_response(await self.cbpi.dashboard.move_content(content_id,data["x"], data["y"]), dumps=json_dumps)
|
return web.json_response(await self.cbpi.dashboard.move_content(content_id,data["x"], data["y"]), dumps=json_dumps)
|
||||||
|
|
||||||
|
|
|
@ -28,24 +28,16 @@ class StepHttpEndpoints(HttpCrudEndpoints):
|
||||||
|
|
||||||
@request_mapping(path="/", auth_required=False)
|
@request_mapping(path="/", auth_required=False)
|
||||||
async def http_get_all(self, request):
|
async def http_get_all(self, request):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
---
|
---
|
||||||
description: Switch step on
|
description: Get all steps
|
||||||
tags:
|
tags:
|
||||||
- Step
|
- Step
|
||||||
parameters:
|
|
||||||
- name: "id"
|
|
||||||
in: "path"
|
|
||||||
description: "step ID"
|
|
||||||
required: true
|
|
||||||
type: "integer"
|
|
||||||
format: "int64"
|
|
||||||
responses:
|
responses:
|
||||||
"204":
|
"204":
|
||||||
description: successful operation
|
description: successful operation
|
||||||
"405":
|
|
||||||
description: invalid HTTP Met
|
|
||||||
"""
|
"""
|
||||||
return await super().http_get_all(request)
|
return await super().http_get_all(request)
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,7 @@ class Job:
|
||||||
self._started.set_result(None)
|
self._started.set_result(None)
|
||||||
|
|
||||||
def _done_callback(self, task):
|
def _done_callback(self, task):
|
||||||
|
|
||||||
scheduler = self._scheduler
|
scheduler = self._scheduler
|
||||||
scheduler._done(self)
|
scheduler._done(self)
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,38 +1,13 @@
|
||||||
from json import JSONEncoder
|
from json import JSONEncoder
|
||||||
|
|
||||||
from cbpi.database.model import ActorModel, SensorModel
|
|
||||||
|
|
||||||
|
|
||||||
class ComplexEncoder(JSONEncoder):
|
class ComplexEncoder(JSONEncoder):
|
||||||
|
|
||||||
def default(self, obj):
|
def default(self, obj):
|
||||||
|
|
||||||
from cbpi.database.orm_framework import DBModel
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if isinstance(obj, ActorModel):
|
if hasattr(obj, "to_json") and callable(getattr(obj, "to_json")):
|
||||||
|
return obj.to_json()
|
||||||
|
|
||||||
data = dict(**obj.__dict__)
|
|
||||||
data["state"] = obj.instance.get_state()
|
|
||||||
del data["instance"]
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
elif isinstance(obj, SensorModel):
|
|
||||||
data = dict(**obj.__dict__)
|
|
||||||
data["value"] = value=obj.instance.get_value()
|
|
||||||
data["unit"] = value = obj.instance.get_unit()
|
|
||||||
data["state"] = obj.instance.get_state()
|
|
||||||
del data["instance"]
|
|
||||||
return data
|
|
||||||
#elif callable(getattr(obj, "reprJSON")):
|
|
||||||
# return obj.reprJSON()
|
|
||||||
elif isinstance(obj, DBModel):
|
|
||||||
return obj.__dict__
|
|
||||||
#elif hasattr(obj, "callback"):
|
|
||||||
# return obj()
|
|
||||||
else:
|
else:
|
||||||
raise TypeError()
|
raise TypeError()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
11
cheat_sheet.txt
Normal file
11
cheat_sheet.txt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#clean
|
||||||
|
python3.7 setup.py clean --all
|
||||||
|
|
||||||
|
#build
|
||||||
|
python setup.py sdist
|
||||||
|
|
||||||
|
#Upload
|
||||||
|
twine upload dist/*
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
craftbeerpi.db
BIN
craftbeerpi.db
Binary file not shown.
13
sample.py
13
sample.py
|
@ -1,13 +1,8 @@
|
||||||
import re
|
|
||||||
|
|
||||||
pattern = "(actor)\/([\d])\/(on|toggle|off)$"
|
|
||||||
|
|
||||||
p = re.compile(pattern)
|
|
||||||
result = p.match("actor/1/toggle")
|
|
||||||
|
|
||||||
print(result, result.group(3))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test123(name: str) -> str:
|
||||||
|
|
||||||
|
print(name)
|
||||||
|
|
||||||
|
|
||||||
|
test123("HALLO")
|
5
setup.py
5
setup.py
|
@ -1,8 +1,8 @@
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
setup(name='cbpi',
|
setup(name='cbpi',
|
||||||
version='4.0.0.1',
|
version='4.0.0.4',
|
||||||
description='CraftBeerPi API',
|
description='CraftBeerPi',
|
||||||
author='Manuel Fritsch',
|
author='Manuel Fritsch',
|
||||||
author_email='manuel@craftbeerpi.com',
|
author_email='manuel@craftbeerpi.com',
|
||||||
url='http://web.craftbeerpi.com',
|
url='http://web.craftbeerpi.com',
|
||||||
|
@ -23,6 +23,7 @@ setup(name='cbpi',
|
||||||
"aiojobs==0.2.2",
|
"aiojobs==0.2.2",
|
||||||
"aiosqlite==0.7.0",
|
"aiosqlite==0.7.0",
|
||||||
"cryptography==2.3.1",
|
"cryptography==2.3.1",
|
||||||
|
"requests==2.22.0",
|
||||||
"voluptuous==0.11.5",
|
"voluptuous==0.11.5",
|
||||||
"pyfiglet==0.7.6"
|
"pyfiglet==0.7.6"
|
||||||
],
|
],
|
||||||
|
|
|
@ -68,9 +68,9 @@ class KettleTestCase(AioHTTPTestCase):
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
m = await resp.json()
|
m = await resp.json()
|
||||||
print(m)
|
|
||||||
sensor_id = m["id"]
|
|
||||||
|
|
||||||
|
sensor_id = m["id"]
|
||||||
|
print("KETTLE", m["id"], m)
|
||||||
# Get sensor
|
# Get sensor
|
||||||
resp = await self.client.get(path="/kettle/%s" % sensor_id)
|
resp = await self.client.get(path="/kettle/%s" % sensor_id)
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
|
@ -17,9 +17,11 @@ class StepTestCase(AioHTTPTestCase):
|
||||||
async def test_get(self):
|
async def test_get(self):
|
||||||
|
|
||||||
resp = await self.client.request("GET", "/step")
|
resp = await self.client.request("GET", "/step")
|
||||||
|
print(resp)
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
resp = await self.client.request("GET", "/step/types")
|
resp = await self.client.request("GET", "/step/types")
|
||||||
|
print(resp)
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,14 +30,15 @@ class StepTestCase(AioHTTPTestCase):
|
||||||
data = {
|
data = {
|
||||||
"name": "Test",
|
"name": "Test",
|
||||||
"type": "CustomStepCBPi",
|
"type": "CustomStepCBPi",
|
||||||
|
"config": {}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add new sensor
|
# Add new step
|
||||||
resp = await self.client.post(path="/step/", json=data)
|
resp = await self.client.post(path="/step/", json=data)
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
m = await resp.json()
|
m = await resp.json()
|
||||||
print(m)
|
print("Step", m)
|
||||||
sensor_id = m["id"]
|
sensor_id = m["id"]
|
||||||
|
|
||||||
# Get sensor
|
# Get sensor
|
||||||
|
@ -68,31 +71,37 @@ class StepTestCase(AioHTTPTestCase):
|
||||||
if future in done:
|
if future in done:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_process(self):
|
async def test_process(self):
|
||||||
await self.cbpi.step.stop()
|
|
||||||
|
|
||||||
with mock.patch.object(self.cbpi.step, 'start', wraps=self.cbpi.step.start) as mock_obj:
|
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}})
|
||||||
|
await step_ctlr.add(**{"name": "Kettle1", "type": "CustomStepCBPi", "config": {"name1": "1", "temp": 99}})
|
||||||
|
|
||||||
|
await step_ctlr.stop_all()
|
||||||
|
|
||||||
future = self.create_wait_callback("step/+/started")
|
future = self.create_wait_callback("step/+/started")
|
||||||
await self.cbpi.step.start()
|
await step_ctlr.start()
|
||||||
await self.wait(future)
|
await self.wait(future)
|
||||||
|
|
||||||
|
for i in range(len(step_ctlr.cache)-1):
|
||||||
future = self.create_wait_callback("step/+/started")
|
future = self.create_wait_callback("step/+/started")
|
||||||
await self.cbpi.step.next()
|
await step_ctlr.next()
|
||||||
await self.wait(future)
|
await self.wait(future)
|
||||||
|
|
||||||
future = self.create_wait_callback("step/+/started")
|
await self.print_steps()
|
||||||
await self.cbpi.step.next()
|
|
||||||
await self.wait(future)
|
|
||||||
|
|
||||||
future = self.create_wait_callback("step/+/started")
|
|
||||||
await self.cbpi.step.next()
|
|
||||||
await self.wait(future)
|
|
||||||
|
|
||||||
future = self.create_wait_callback("job/step/done")
|
|
||||||
await self.cbpi.step.stop()
|
|
||||||
await self.wait(future)
|
|
||||||
print("COUNT", mock_obj.call_count)
|
|
||||||
print("ARGS", mock_obj.call_args_list)
|
|
||||||
|
|
||||||
|
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())
|
Loading…
Reference in a new issue