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
|
||||
|
||||
def reprJSON(self):
|
||||
return dict(state=True)
|
|
@ -50,5 +50,3 @@ class CBPiExtension():
|
|||
except:
|
||||
logger.warning("Faild to load config %s/config.yaml" % path)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
import time
|
||||
import asyncio
|
||||
import logging
|
||||
|
@ -6,22 +7,39 @@ 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):
|
||||
def __init__(self, cbpi="", managed_fields=[], id="", name="", *args, **kwargs):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
print(kwargs)
|
||||
for a in kwargs:
|
||||
super(CBPiSimpleStep, self).__setattr__(a, kwargs.get(a))
|
||||
self.id = kwargs.get("id")
|
||||
self._exception_count = 0
|
||||
self._interval = 0.1
|
||||
self._max_exceptions = 2
|
||||
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_next = False
|
||||
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):
|
||||
'''
|
||||
Method checks if the step should continue running.
|
||||
|
@ -39,6 +57,9 @@ class CBPiSimpleStep(metaclass=ABCMeta):
|
|||
|
||||
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
|
||||
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)
|
||||
|
||||
if self.is_dirty():
|
||||
print("DIRTY")
|
||||
# Now we have to store the managed props
|
||||
state = {}
|
||||
for field in self.managed_fields:
|
||||
|
@ -72,6 +92,7 @@ class CBPiSimpleStep(metaclass=ABCMeta):
|
|||
await self.cbpi.bus.fire("step/update")
|
||||
self.reset_dirty()
|
||||
|
||||
|
||||
@abstractmethod
|
||||
async def run_cycle(self):
|
||||
'''
|
||||
|
|
44
cbpi/cli.py
44
cbpi/cli.py
|
@ -1,5 +1,7 @@
|
|||
import argparse
|
||||
import logging
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
import os
|
||||
|
@ -13,6 +15,7 @@ def create_plugin_file():
|
|||
srcfile = os.path.join(os.path.dirname(__file__), "config", "plugin_list.txt")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
print("Plugin Folder created")
|
||||
|
||||
def create_config_file():
|
||||
import os.path
|
||||
|
@ -20,19 +23,49 @@ def create_config_file():
|
|||
srcfile = os.path.join(os.path.dirname(__file__), "config", "config.yaml")
|
||||
destfile = os.path.join(".", 'config')
|
||||
shutil.copy(srcfile, destfile)
|
||||
print("Config Folder created")
|
||||
|
||||
def create_home_folder_structure():
|
||||
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)
|
||||
print("Log Folder created")
|
||||
|
||||
def copy_splash():
|
||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "splash.png")
|
||||
destfile = os.path.join(".", 'config')
|
||||
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():
|
||||
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()
|
||||
|
||||
|
@ -40,15 +73,24 @@ def main():
|
|||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
|
||||
if args.action == "setup":
|
||||
print("Setting up CBPi")
|
||||
create_home_folder_structure()
|
||||
create_plugin_file()
|
||||
create_config_file()
|
||||
copy_splash()
|
||||
return
|
||||
|
||||
if args.action == "plugins":
|
||||
list_plugins()
|
||||
return
|
||||
|
||||
if args.action == "start":
|
||||
if check_for_setup() is False:
|
||||
return
|
||||
|
||||
cbpi = CraftBeerPi()
|
||||
cbpi.start()
|
||||
return
|
||||
|
||||
parser.print_help()
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
name: CraftBeerPi
|
||||
version: 4.1
|
||||
version: 4.0.1_alpha
|
||||
|
||||
#index_url: /myext
|
||||
#: /myext
|
||||
|
||||
port: 8080
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import asyncio
|
||||
import logging
|
||||
from asyncio import Future
|
||||
from cbpi.api import *
|
||||
|
||||
from voluptuous import Schema
|
||||
|
||||
from cbpi.api import *
|
||||
from cbpi.controller.crud_controller import CRUDController
|
||||
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.init()
|
||||
|
||||
|
||||
await self.cbpi.bus.fire(topic="actor/%s/initialized" % actor.id, id=actor.id)
|
||||
else:
|
||||
|
||||
|
@ -73,6 +75,7 @@ class ActorController(CRUDController):
|
|||
|
||||
actor_id = int(actor_id)
|
||||
if actor_id in self.cache:
|
||||
|
||||
self.logger.debug("ON %s" % actor_id)
|
||||
actor = self.cache[actor_id].instance
|
||||
actor.on(power)
|
||||
|
@ -141,7 +144,7 @@ class ActorController(CRUDController):
|
|||
:param m:
|
||||
:return:
|
||||
'''
|
||||
print("INIT ACTION")
|
||||
|
||||
await self._init_actor(m)
|
||||
pass
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from cbpi.database.model import ConfigModel
|
|||
from cbpi.utils import load_config
|
||||
|
||||
|
||||
class ConfigController():
|
||||
class ConfigController:
|
||||
'''
|
||||
The main actor controller
|
||||
'''
|
||||
|
@ -24,14 +24,13 @@ class ConfigController():
|
|||
|
||||
async def init(self):
|
||||
this_directory = os.path.dirname(__file__)
|
||||
|
||||
self.static = load_config("./config/config.yaml")
|
||||
items = await self.model.get_all()
|
||||
for key, value in items.items():
|
||||
self.cache[value.name] = value
|
||||
|
||||
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:
|
||||
return self.cache[name].value
|
||||
else:
|
||||
|
|
|
@ -70,7 +70,7 @@ class CRUDController(metaclass=ABCMeta):
|
|||
|
||||
|
||||
await self._pre_add_callback(data)
|
||||
print("INSSERT ADD", data)
|
||||
|
||||
|
||||
m = await self.model.insert(**data)
|
||||
|
||||
|
@ -96,10 +96,12 @@ class CRUDController(metaclass=ABCMeta):
|
|||
'''
|
||||
|
||||
self.logger.debug("Update Sensor %s - %s " % (id, data))
|
||||
|
||||
id = int(id)
|
||||
|
||||
if id not in self.cache:
|
||||
self.logger.debug("Sensor %s Not in Cache" % (id,))
|
||||
if self.caching is True and id not in self.cache:
|
||||
|
||||
self.logger.debug("%s %s Not in Cache" % (self.name, id))
|
||||
raise CBPiException("%s with id %s not found" % (self.name,id))
|
||||
|
||||
data["id"] = id
|
||||
|
@ -115,14 +117,10 @@ class CRUDController(metaclass=ABCMeta):
|
|||
self.cache[id].__dict__.update(**data)
|
||||
m = self.cache[id] = await self.model.update(**self.cache[id].__dict__)
|
||||
await self._post_update_callback(self.cache[id])
|
||||
|
||||
else:
|
||||
|
||||
m = await self.model.update(**data)
|
||||
return m
|
||||
|
||||
|
||||
|
||||
async def _pre_delete_callback(self, m):
|
||||
'''
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import logging
|
||||
|
||||
from voluptuous import Schema, MultipleInvalid
|
||||
|
||||
from cbpi.controller.crud_controller import CRUDController
|
||||
from cbpi.database.model import DashboardModel, DashboardContentModel
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import logging
|
|||
from cbpi.job.aiohttp import setup, get_scheduler_from_app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class JobController(object):
|
||||
|
||||
def __init__(self, cbpi):
|
||||
|
|
|
@ -3,7 +3,7 @@ from cbpi.api import *
|
|||
from cbpi.controller.crud_controller import CRUDController
|
||||
from cbpi.database.model import KettleModel
|
||||
from cbpi.job.aiohttp import get_scheduler_from_app
|
||||
|
||||
import logging
|
||||
|
||||
class KettleController(CRUDController):
|
||||
'''
|
||||
|
@ -15,6 +15,7 @@ class KettleController(CRUDController):
|
|||
super(KettleController, self).__init__(cbpi)
|
||||
self.cbpi = cbpi
|
||||
self.types = {}
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.cbpi.register(self)
|
||||
|
||||
async def init(self):
|
||||
|
@ -58,7 +59,7 @@ class KettleController(CRUDController):
|
|||
kettle = self.cache[int(kid)]
|
||||
kettle.instance = None
|
||||
kettle.state = False
|
||||
print("FIRE")
|
||||
|
||||
await self.cbpi.bus.fire(topic="kettle/%s/logic/stop" % kid)
|
||||
|
||||
@on_event(topic="kettle/+/automatic")
|
||||
|
@ -73,7 +74,7 @@ class KettleController(CRUDController):
|
|||
'''
|
||||
id = int(id)
|
||||
|
||||
print("K", id)
|
||||
|
||||
if id in self.cache:
|
||||
|
||||
kettle = self.cache[id]
|
||||
|
@ -84,7 +85,7 @@ class KettleController(CRUDController):
|
|||
|
||||
|
||||
if kettle.instance is None:
|
||||
print("start")
|
||||
|
||||
if kettle.logic in self.types:
|
||||
clazz = self.types.get("CustomKettleLogic")["class"]
|
||||
cfg = kettle.config.copy()
|
||||
|
@ -104,13 +105,9 @@ class KettleController(CRUDController):
|
|||
def _is_logic_running(self, kettle_id):
|
||||
scheduler = get_scheduler_from_app(self.cbpi.app)
|
||||
|
||||
|
||||
|
||||
async def heater_on(self, id):
|
||||
'''
|
||||
Convenience Method to switch the heater of a kettle on
|
||||
|
||||
|
||||
:param id: the kettle id
|
||||
:return: (boolean, string)
|
||||
'''
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from importlib import import_module
|
||||
|
@ -5,15 +6,16 @@ from importlib import import_module
|
|||
import aiohttp
|
||||
import yaml
|
||||
from aiohttp import web
|
||||
from cbpi.api import *
|
||||
|
||||
from cbpi.api import *
|
||||
from cbpi.utils.utils import load_config, json_dumps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
class PluginController():
|
||||
|
||||
modules = {}
|
||||
types = {}
|
||||
|
||||
|
@ -25,9 +27,7 @@ class PluginController():
|
|||
async def load_plugin_list(self):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get('https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins_v4.yaml') as resp:
|
||||
|
||||
if (resp.status == 200):
|
||||
|
||||
data = yaml.load(await resp.text())
|
||||
return data
|
||||
|
||||
|
@ -55,6 +55,7 @@ class PluginController():
|
|||
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
logger.error(e)
|
||||
|
||||
def load_plugins_from_evn(self):
|
||||
|
@ -63,7 +64,6 @@ class PluginController():
|
|||
this_directory = os.path.dirname(__file__)
|
||||
with open("./config/plugin_list.txt") as f:
|
||||
|
||||
|
||||
plugins = f.read().splitlines()
|
||||
plugins = list(set(plugins))
|
||||
|
||||
|
@ -74,15 +74,42 @@ class PluginController():
|
|||
self.modules[p] = import_module(p)
|
||||
self.modules[p].setup(self.cbpi)
|
||||
|
||||
|
||||
|
||||
logger.info("Plugin %s loaded successfully" % p)
|
||||
except Exception as e:
|
||||
logger.error("FAILED to load plugin %s " % p)
|
||||
logger.error(e)
|
||||
|
||||
@on_event("job/plugins_install/done")
|
||||
async def done(self, **kwargs):
|
||||
self.cbpi.notify(key="p", message="Plugin installed ", type="success")
|
||||
print("DONE INSTALL PLUGIN", kwargs)
|
||||
|
||||
@request_mapping(path="/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):
|
||||
"""
|
||||
---
|
||||
|
@ -99,8 +126,6 @@ class PluginController():
|
|||
"""
|
||||
return web.json_response(await self.load_plugin_list(), dumps=json_dumps)
|
||||
|
||||
|
||||
|
||||
def register(self, name, clazz) -> None:
|
||||
'''
|
||||
Register a new actor type
|
||||
|
@ -131,7 +156,7 @@ class PluginController():
|
|||
|
||||
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("__")]
|
||||
for m in members:
|
||||
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.init()
|
||||
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)
|
||||
else:
|
||||
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):
|
||||
'''
|
||||
The Step Controller. This controller is responsible to start and stop the brewing steps.
|
||||
|
||||
'''
|
||||
|
||||
model = StepModel
|
||||
|
||||
def __init__(self, cbpi):
|
||||
super(StepController, self).__init__(cbpi)
|
||||
self.caching = False
|
||||
self.model = StepModel
|
||||
|
||||
self.caching = True
|
||||
self.is_stopping = False
|
||||
self.cbpi = cbpi
|
||||
self.current_task = None
|
||||
|
@ -28,36 +25,11 @@ class StepController(CRUDController):
|
|||
self.logger = logging.getLogger(__name__)
|
||||
self.starttime = None
|
||||
|
||||
async def init(self):
|
||||
'''
|
||||
Initializer of the the Step Controller.
|
||||
: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()
|
||||
def is_running(self):
|
||||
if self.current_step is not None:
|
||||
return True
|
||||
else:
|
||||
cfg = {}
|
||||
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)
|
||||
return False
|
||||
|
||||
@on_event("step/action")
|
||||
async def handle_action(self, action, **kwargs):
|
||||
|
@ -74,162 +46,154 @@ class StepController(CRUDController):
|
|||
if self.current_step is not None:
|
||||
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):
|
||||
|
||||
result = []
|
||||
|
||||
for f in type_cfg.get("properties"):
|
||||
|
||||
result.append(f.get("name"))
|
||||
|
||||
return result
|
||||
|
||||
def is_running(self):
|
||||
if self.current_step is not None:
|
||||
return True
|
||||
async def init(self):
|
||||
|
||||
# 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:
|
||||
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")
|
||||
async def start(self, **kwargs):
|
||||
|
||||
'''
|
||||
Start the first step
|
||||
|
||||
:return:None
|
||||
'''
|
||||
|
||||
if self.is_running() is False:
|
||||
next_step = await self.model.get_by_state("I")
|
||||
|
||||
if next_step is not None:
|
||||
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_id = self.find_next_step()
|
||||
if next_step_id:
|
||||
next_step = self.cache[next_step_id]
|
||||
next_step.state = 'A'
|
||||
next_step.stepstate = next_step.config
|
||||
next_step.start = int(time.time())
|
||||
await self.model.update(**next_step.__dict__)
|
||||
if self.starttime is not None:
|
||||
end = time.time()
|
||||
d = end - self.starttime
|
||||
print("DURATION", d)
|
||||
else:
|
||||
print("NORMAL START")
|
||||
self.current_job = await self.cbpi.job.start_job(self.current_step.run(), next_step.name, "step")
|
||||
self.current_step = next_step
|
||||
# start the step job
|
||||
self.current_job = await self.cbpi.job.start_job(self.current_step.instance.run(), next_step.name, "step")
|
||||
await self.cbpi.bus.fire("step/%s/started" % self.current_step.id)
|
||||
else:
|
||||
await self.cbpi.bus.fire("step/brewing/finished")
|
||||
else:
|
||||
self.logger.error("Process Already Running")
|
||||
print("----------- END")
|
||||
|
||||
def _set_default(self, step_type, config, managed_fields):
|
||||
for key in managed_fields:
|
||||
if key not in config:
|
||||
config[key] = None
|
||||
async def next(self, **kwargs):
|
||||
|
||||
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")
|
||||
async def stop(self, **kwargs):
|
||||
|
||||
if self.current_step is not None:
|
||||
self.current_step.stop()
|
||||
self.is_stopping = True
|
||||
self.current_step = None
|
||||
|
||||
async def stop_all(self, **kwargs):
|
||||
# RESET DB
|
||||
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")
|
||||
|
||||
@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")
|
||||
async def clear_all(self, **kwargs):
|
||||
await self.model.delete_all()
|
||||
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):
|
||||
|
||||
order = await self.model.get_max_order()
|
||||
data["order"] = 1 if order is None else order + 1
|
||||
data["state"] = "I"
|
||||
data["stepstate"] = {}
|
||||
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})
|
||||
except MultipleInvalid as 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})
|
||||
|
||||
class CraftBeerPi():
|
||||
|
@ -187,7 +190,7 @@ class CraftBeerPi():
|
|||
api_version=self.static_config.get("version", ""),
|
||||
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
|
||||
|
||||
|
@ -216,7 +219,7 @@ class CraftBeerPi():
|
|||
if url is not None:
|
||||
raise web.HTTPFound(url)
|
||||
else:
|
||||
return web.Response(text="Hello, world")
|
||||
return web.Response(text="Hello from CraftbeerPi!")
|
||||
|
||||
self.app.add_routes([web.get('/', http_index)])
|
||||
|
||||
|
|
|
@ -20,6 +20,13 @@ class ActorModel(DBModel):
|
|||
'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):
|
||||
__fields__ = ["name", "type", "config"]
|
||||
|
@ -32,6 +39,15 @@ class SensorModel(DBModel):
|
|||
'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):
|
||||
__fields__ = ["type", "value", "description", "options"]
|
||||
|
@ -54,7 +70,7 @@ class StepModel(DBModel):
|
|||
|
||||
@classmethod
|
||||
async def update_step_state(cls, step_id, state):
|
||||
print("NOW UPDATE", state)
|
||||
|
||||
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))
|
||||
await db.commit()
|
||||
|
@ -93,7 +109,7 @@ class StepModel(DBModel):
|
|||
|
||||
async with aiosqlite.connect(DATABASE_FILE) as db:
|
||||
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.commit()
|
||||
|
||||
|
@ -111,6 +127,19 @@ class StepModel(DBModel):
|
|||
else:
|
||||
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):
|
||||
__fields__ = ["name"]
|
||||
|
|
|
@ -167,3 +167,7 @@ class DBModel(object):
|
|||
for idx, col in enumerate(cursor.description):
|
||||
d[col[0]] = row[idx]
|
||||
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
|
||||
@action("test", parameters={})
|
||||
def action1(self):
|
||||
print("EOOOHOOO")
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import asyncio
|
|||
from aiohttp import web
|
||||
from cbpi.api import *
|
||||
|
||||
import re
|
||||
|
||||
class CustomSensor(CBPiSensor):
|
||||
|
||||
|
@ -61,6 +62,7 @@ class HTTPSensor(CBPiSensor):
|
|||
|
||||
def init(self):
|
||||
super().init()
|
||||
|
||||
self.state = True
|
||||
|
||||
def get_state(self):
|
||||
|
@ -80,10 +82,12 @@ class HTTPSensor(CBPiSensor):
|
|||
|
||||
try:
|
||||
value = cache.pop(self.key, None)
|
||||
print("HTTP SENSOR READ", value)
|
||||
if value is not None:
|
||||
self.log_data(value)
|
||||
await cbpi.bus.fire("sensor/%s" % self.id, value=value)
|
||||
except:
|
||||
await cbpi.bus.fire("sensor/%s/data" % self.id, value=value)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
class HTTPSensorEndpoint(CBPiExtension):
|
||||
|
@ -95,6 +99,7 @@ class HTTPSensorEndpoint(CBPiExtension):
|
|||
|
||||
:param cbpi:
|
||||
'''
|
||||
self.pattern_check = re.compile("^[a-zA-Z0-9,.]{0,10}$")
|
||||
|
||||
self.cbpi = cbpi
|
||||
# register component for http, events
|
||||
|
@ -125,10 +130,17 @@ class HTTPSensorEndpoint(CBPiExtension):
|
|||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
print("HALLO")
|
||||
|
||||
global cache
|
||||
key = request.match_info['key']
|
||||
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
|
||||
|
||||
return web.Response(status=204)
|
||||
|
|
|
@ -9,15 +9,23 @@ class CustomStepCBPi(CBPiSimpleStep):
|
|||
name1 = Property.Number(label="Test", configurable=True)
|
||||
timer_end = Property.Number(label="Test", default_value=None)
|
||||
temp = Property.Number(label="Temperature", default_value=50, configurable=True)
|
||||
|
||||
|
||||
|
||||
i = 0
|
||||
|
||||
@action(key="name", parameters=None)
|
||||
def test(self, **kwargs):
|
||||
self.name="WOOHOO"
|
||||
|
||||
|
||||
def get_status(self):
|
||||
return "Status: %s Temp" % self.temp
|
||||
|
||||
async def run_cycle(self):
|
||||
|
||||
self.next()
|
||||
|
||||
'''
|
||||
print("RUN", self.name1, self.managed_fields, self.timer_end)
|
||||
self.i = self.i + 1
|
||||
|
||||
|
@ -26,7 +34,7 @@ class CustomStepCBPi(CBPiSimpleStep):
|
|||
|
||||
if self.i == 10:
|
||||
self.next()
|
||||
|
||||
'''
|
||||
|
||||
#self.cbpi.notify(key="step", message="HELLO FROM STEP")
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ class ConfigHttpEndpoints(HttpCrudEndpoints):
|
|||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
print("HALLO PARA")
|
||||
|
||||
name = request.match_info['name']
|
||||
data = await request.json()
|
||||
await self.controller.set(name=name, value=data.get("value"))
|
||||
|
|
|
@ -22,7 +22,7 @@ class HttpCrudEndpoints():
|
|||
|
||||
@request_mapping(path="/", auth_required=False)
|
||||
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)
|
||||
async def http_get_one(self, request):
|
||||
|
@ -39,6 +39,8 @@ class HttpCrudEndpoints():
|
|||
async def http_update(self, request):
|
||||
id = int(request.match_info['id'])
|
||||
data = await request.json()
|
||||
|
||||
|
||||
obj = await self.controller.update(id, data)
|
||||
return web.json_response(obj, dumps=json_dumps)
|
||||
|
||||
|
|
|
@ -257,6 +257,6 @@ class DashBoardHttpEndpoints(HttpCrudEndpoints):
|
|||
schema = Schema({"id": int, "x": int, "y": int})
|
||||
schema(data)
|
||||
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)
|
||||
|
||||
|
|
|
@ -28,24 +28,16 @@ class StepHttpEndpoints(HttpCrudEndpoints):
|
|||
|
||||
@request_mapping(path="/", auth_required=False)
|
||||
async def http_get_all(self, request):
|
||||
|
||||
"""
|
||||
|
||||
---
|
||||
description: Switch step on
|
||||
description: Get all steps
|
||||
tags:
|
||||
- Step
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
description: "step ID"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"405":
|
||||
description: invalid HTTP Met
|
||||
"""
|
||||
return await super().http_get_all(request)
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ class Job:
|
|||
self._started.set_result(None)
|
||||
|
||||
def _done_callback(self, task):
|
||||
|
||||
scheduler = self._scheduler
|
||||
scheduler._done(self)
|
||||
try:
|
||||
|
|
|
@ -1,38 +1,13 @@
|
|||
from json import JSONEncoder
|
||||
|
||||
from cbpi.database.model import ActorModel, SensorModel
|
||||
|
||||
|
||||
class ComplexEncoder(JSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
|
||||
from cbpi.database.orm_framework import DBModel
|
||||
|
||||
try:
|
||||
|
||||
if isinstance(obj, ActorModel):
|
||||
|
||||
|
||||
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()
|
||||
if hasattr(obj, "to_json") and callable(getattr(obj, "to_json")):
|
||||
return obj.to_json()
|
||||
else:
|
||||
raise TypeError()
|
||||
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
|
||||
|
||||
setup(name='cbpi',
|
||||
version='4.0.0.1',
|
||||
description='CraftBeerPi API',
|
||||
version='4.0.0.4',
|
||||
description='CraftBeerPi',
|
||||
author='Manuel Fritsch',
|
||||
author_email='manuel@craftbeerpi.com',
|
||||
url='http://web.craftbeerpi.com',
|
||||
|
@ -23,6 +23,7 @@ setup(name='cbpi',
|
|||
"aiojobs==0.2.2",
|
||||
"aiosqlite==0.7.0",
|
||||
"cryptography==2.3.1",
|
||||
"requests==2.22.0",
|
||||
"voluptuous==0.11.5",
|
||||
"pyfiglet==0.7.6"
|
||||
],
|
||||
|
|
|
@ -68,9 +68,9 @@ class KettleTestCase(AioHTTPTestCase):
|
|||
assert resp.status == 200
|
||||
|
||||
m = await resp.json()
|
||||
print(m)
|
||||
sensor_id = m["id"]
|
||||
|
||||
sensor_id = m["id"]
|
||||
print("KETTLE", m["id"], m)
|
||||
# Get sensor
|
||||
resp = await self.client.get(path="/kettle/%s" % sensor_id)
|
||||
assert resp.status == 200
|
||||
|
|
|
@ -17,9 +17,11 @@ class StepTestCase(AioHTTPTestCase):
|
|||
async def test_get(self):
|
||||
|
||||
resp = await self.client.request("GET", "/step")
|
||||
print(resp)
|
||||
assert resp.status == 200
|
||||
|
||||
resp = await self.client.request("GET", "/step/types")
|
||||
print(resp)
|
||||
assert resp.status == 200
|
||||
|
||||
|
||||
|
@ -28,14 +30,15 @@ class StepTestCase(AioHTTPTestCase):
|
|||
data = {
|
||||
"name": "Test",
|
||||
"type": "CustomStepCBPi",
|
||||
"config": {}
|
||||
}
|
||||
|
||||
# Add new sensor
|
||||
# Add new step
|
||||
resp = await self.client.post(path="/step/", json=data)
|
||||
assert resp.status == 200
|
||||
|
||||
m = await resp.json()
|
||||
print(m)
|
||||
print("Step", m)
|
||||
sensor_id = m["id"]
|
||||
|
||||
# Get sensor
|
||||
|
@ -68,31 +71,37 @@ class StepTestCase(AioHTTPTestCase):
|
|||
if future in done:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@unittest_run_loop
|
||||
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")
|
||||
await self.cbpi.step.start()
|
||||
await step_ctlr.start()
|
||||
await self.wait(future)
|
||||
|
||||
for i in range(len(step_ctlr.cache)-1):
|
||||
future = self.create_wait_callback("step/+/started")
|
||||
await self.cbpi.step.next()
|
||||
await step_ctlr.next()
|
||||
await self.wait(future)
|
||||
|
||||
future = self.create_wait_callback("step/+/started")
|
||||
await self.cbpi.step.next()
|
||||
await self.wait(future)
|
||||
await self.print_steps()
|
||||
|
||||
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