From 296c2c69f0575acfcf2a91d554156d006e4a2c9b Mon Sep 17 00:00:00 2001 From: Manuel Fritsch Date: Fri, 22 Jan 2021 23:25:20 +0100 Subject: [PATCH] rework database --- MANIFEST.in | 3 +- cbpi/__init__.py | 1 + cbpi/api/__init__.py | 3 + cbpi/api/actor.py | 58 ++- cbpi/api/kettle_logic.py | 63 ++- cbpi/api/sensor.py | 42 +- cbpi/api/step.py | 2 +- cbpi/cli.py | 9 +- cbpi/config/config.yaml | 2 +- cbpi/controller/actor_controller.py | 180 ++------ cbpi/controller/basic_controller.py | 148 +++++++ cbpi/controller/crud_controller.py | 169 -------- cbpi/controller/dashboard_controller.py | 15 +- cbpi/controller/kettle_controller.py | 206 ++------- cbpi/controller/log_file_controller.py | 1 - cbpi/controller/plugin_controller.py | 37 +- cbpi/controller/sensor_controller.py | 85 +--- cbpi/controller/step_controller.py | 381 +++++++++------- cbpi/controller/step_controller_ng.py | 251 ----------- cbpi/controller/translation_controller.py | 28 -- cbpi/craftbeerpi.py | 39 +- cbpi/extension/dummyactor/__init__.py | 26 +- cbpi/extension/dummylogic/__init__.py | 67 +-- cbpi/extension/dummysensor/__init__.py | 38 +- cbpi/extension/dummystep/__init__.py | 62 +-- cbpi/extension/mqtt/__init__.py | 5 + cbpi/http_endpoints/http_actor.py | 141 ++---- cbpi/http_endpoints/http_dashboard.py | 108 ----- cbpi/http_endpoints/http_kettle.py | 505 +++++++++------------- cbpi/http_endpoints/http_sensor.py | 275 ++++++------ cbpi/http_endpoints/http_step.py | 320 ++++++-------- cbpi/http_endpoints/http_step2.py | 200 --------- cbpi/http_endpoints/http_system.py | 4 +- cbpi/http_endpoints/http_translation.py | 54 --- cheat_sheet.txt | 2 +- config/actor.json | 25 ++ config/dashboard/cbpi_dashboard_1.json | 30 ++ config/kettle.json | 26 ++ config/sensor.json | 21 + config/step_data.json | 43 +- craftbeerpi.db | Bin 53248 -> 53248 bytes data.txt | 1 - requirements2.txt | 40 -- samples.txt | 51 --- sampletest.py | 0 setup.py | 25 +- 46 files changed, 1402 insertions(+), 2390 deletions(-) create mode 100644 cbpi/controller/basic_controller.py delete mode 100644 cbpi/controller/crud_controller.py delete mode 100644 cbpi/controller/step_controller_ng.py delete mode 100644 cbpi/controller/translation_controller.py delete mode 100644 cbpi/http_endpoints/http_step2.py delete mode 100644 cbpi/http_endpoints/http_translation.py create mode 100644 config/actor.json create mode 100644 config/kettle.json create mode 100644 config/sensor.json delete mode 100644 data.txt delete mode 100644 requirements2.txt delete mode 100644 samples.txt delete mode 100644 sampletest.py diff --git a/MANIFEST.in b/MANIFEST.in index e6120c3..5a46f46 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ recursive-include cbpi/config * -recursive-include cbpi/extension * \ No newline at end of file +recursive-include cbpi/extension * +recursive-include cbpi/static * \ No newline at end of file diff --git a/cbpi/__init__.py b/cbpi/__init__.py index e69de29..5fada43 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -0,0 +1 @@ +__version__ = "4.0.0.8" \ No newline at end of file diff --git a/cbpi/api/__init__.py b/cbpi/api/__init__.py index d629a95..1b97489 100644 --- a/cbpi/api/__init__.py +++ b/cbpi/api/__init__.py @@ -1,4 +1,5 @@ __all__ = ["CBPiActor", + "CBPiActor2", "CBPiExtension", "Property", "PropertyType", @@ -9,12 +10,14 @@ __all__ = ["CBPiActor", "parameters", "background_task", "CBPiKettleLogic", + "CBPiKettleLogic2", "CBPiSimpleStep", "CBPiException", "KettleException", "SensorException", "ActorException", "CBPiSensor", + "CBPiSensor2", "CBPiStep"] from cbpi.api.actor import * diff --git a/cbpi/api/actor.py b/cbpi/api/actor.py index a5443fc..fa3022d 100644 --- a/cbpi/api/actor.py +++ b/cbpi/api/actor.py @@ -1,8 +1,8 @@ from abc import ABCMeta - +import asyncio from cbpi.api.extension import CBPiExtension -__all__ = ["CBPiActor"] +__all__ = ["CBPiActor", "CBPiActor2"] import logging @@ -45,3 +45,57 @@ class CBPiActor(CBPiExtension, metaclass=ABCMeta): pass +class CBPiActor2(metaclass=ABCMeta): + + def __init__(self, cbpi, id, props): + self.cbpi = cbpi + self.id = id + self.props = props + self.logger = logging.getLogger(__file__) + self.data_logger = None + self.state = False + self.running = False + + def init(self): + pass + + def log_data(self, value): + self.cbpi.log.log_data(self.id, value) + + async def run(self): + while self.running: + print("RUNNING ACTOR") + await asyncio.sleep(1) + + def get_state(self): + print("########STATE", self.state) + return dict(state=self.state) + + async def start(self): + + print("START UP ACTOR") + self.running = True + + async def stop(self): + self.running = False + + async def on(self, power): + ''' + Code to switch the actor on. Power is provided as integer value + + :param power: power value between 0 and 100 + :return: None + ''' + pass + + async def off(self): + + ''' + Code to switch the actor off + + :return: None + ''' + pass + + + diff --git a/cbpi/api/kettle_logic.py b/cbpi/api/kettle_logic.py index 210c9ae..673b79a 100644 --- a/cbpi/api/kettle_logic.py +++ b/cbpi/api/kettle_logic.py @@ -1,5 +1,7 @@ from cbpi.api.extension import CBPiExtension - +from abc import ABCMeta +import logging +import asyncio class CBPiKettleLogic(CBPiExtension): @@ -35,4 +37,61 @@ class CBPiKettleLogic(CBPiExtension): :return: None ''' - pass \ No newline at end of file + pass + + +class CBPiKettleLogic2(metaclass=ABCMeta): + + def __init__(self, cbpi, id, props): + self.cbpi = cbpi + self.id = id + self.props = props + self.logger = logging.getLogger(__file__) + self.data_logger = None + self.state = False + self.running = False + + def init(self): + pass + + def log_data(self, value): + self.cbpi.log.log_data(self.id, value) + + async def run(self): + while self.running: + print("RUNNING KETTLE") + await asyncio.sleep(1) + + def get_state(self): + print("########STATE", self.state) + return dict(state=self.state) + + async def start(self): + print("") + print("") + print("") + print("##################START UP KETTLE") + print("") + print("") + self.running = True + + async def stop(self): + self.running = False + + async def on(self, power): + ''' + Code to switch the actor on. Power is provided as integer value + + :param power: power value between 0 and 100 + :return: None + ''' + pass + + async def off(self): + + ''' + Code to switch the actor off + + :return: None + ''' + pass diff --git a/cbpi/api/sensor.py b/cbpi/api/sensor.py index 727c429..987e095 100644 --- a/cbpi/api/sensor.py +++ b/cbpi/api/sensor.py @@ -1,6 +1,5 @@ import logging -from abc import ABCMeta - +from abc import abstractmethod, ABCMeta from cbpi.api.extension import CBPiExtension @@ -31,4 +30,41 @@ class CBPiSensor(CBPiExtension, metaclass=ABCMeta): pass def get_unit(self): - pass \ No newline at end of file + pass + +class CBPiSensor2(metaclass=ABCMeta): + + def __init__(self, cbpi, id, props): + self.cbpi = cbpi + self.id = id + self.props = props + self.logger = logging.getLogger(__file__) + self.data_logger = None + self.state = False + self.running = False + + def init(self): + pass + + def log_data(self, value): + self.cbpi.log.log_data(self.id, value) + + @abstractmethod + async def run(self): + self.logger.warning("Sensor Init not implemented") + + def get_state(self): + pass + + def get_value(self): + pass + + def get_unit(self): + pass + + async def start(self): + self.running = True + + async def stop(self): + + self.running = False \ No newline at end of file diff --git a/cbpi/api/step.py b/cbpi/api/step.py index be515a0..9e09c13 100644 --- a/cbpi/api/step.py +++ b/cbpi/api/step.py @@ -39,7 +39,7 @@ class CBPiStep(metaclass=ABCMeta): pass async def update(self, props): - await self.cbpi.step2.update_props(self.id, props) + await self.cbpi.step.update_props(self.id, props) async def run(self): while self.running: diff --git a/cbpi/cli.py b/cbpi/cli.py index 33010d4..a77471f 100644 --- a/cbpi/cli.py +++ b/cbpi/cli.py @@ -14,6 +14,7 @@ import pathlib import shutil + def create_plugin_file(): import os.path if os.path.exists(os.path.join(".", 'config', "plugin_list.txt")) is False: @@ -144,10 +145,16 @@ def main(): parser = argparse.ArgumentParser(description='Welcome to CraftBeerPi 4') parser.add_argument("action", type=str, help="start,stop,restart,setup,plugins") + parser.add_argument('--debug', dest='debug', action='store_true') parser.add_argument("--name", type=str, help="Plugin name") args = parser.parse_args() + + if args.debug is True: + level =logging.DEBUG + else: + level =logging.INFO #logging.basicConfig(level=logging.INFO, filename='./logs/app.log', filemode='a', format='%(asctime)s - %(levelname)s - %(name)s - %(message)s') - logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s') + logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s') if args.action == "setup": print("Setting up CBPi") diff --git a/cbpi/config/config.yaml b/cbpi/config/config.yaml index 0e9e096..27d1a5d 100644 --- a/cbpi/config/config.yaml +++ b/cbpi/config/config.yaml @@ -1,6 +1,6 @@ name: CraftBeerPi -version: 4.0.1_alpha +version: 4.0.8 #: /myext diff --git a/cbpi/controller/actor_controller.py b/cbpi/controller/actor_controller.py index bc704bc..485902d 100644 --- a/cbpi/controller/actor_controller.py +++ b/cbpi/controller/actor_controller.py @@ -1,157 +1,43 @@ -import asyncio +from cbpi.controller.basic_controller import BasicController import logging - -from voluptuous import Schema - -from cbpi.api import * -from cbpi.controller.crud_controller import CRUDController -from cbpi.database.model import ActorModel - - -class ActorController(CRUDController): - ''' - The main actor controller - ''' - model = ActorModel +from tabulate import tabulate +class ActorController(BasicController): def __init__(self, cbpi): - super(ActorController, self).__init__(cbpi) - self.cbpi = cbpi - self.state = False; - self.logger = logging.getLogger(__name__) - self.cbpi.register(self) - self.types = {} - self.actors = {} - async def init(self): - """ - This method initializes all actors during startup. It creates actor instances - :return: - """ - await super(ActorController, self).init() - for id, value in self.cache.items(): - await self._init_actor(value) - - def get_state(self): - return dict(items=self.cache,types=self.types) - - async def _init_actor(self, actor): - + super(ActorController, self).__init__(cbpi, "actor.json") + + async def on(self, id): try: - if actor.type in self.types: - cfg = actor.config.copy() - cfg.update(dict(cbpi=self.cbpi, id=id, name=actor.name)) - clazz = self.types[actor.type]["class"]; - 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: - self.logger.error("Actor type '%s' not found (Available Actor Types: %s)" % (actor.type, ', '.join(self.types.keys()))) + item = self.find_by_id(id) + instance = item.get("instance") + await instance.on() except Exception as e: + logging.error("Faild to switch on Actor {} {}".format(id, e)) - self.logger.error("Failed to init actor %s - Reason %s" % (actor.id, str(e))) + async def off(self, id): + try: + item = self.find_by_id(id) + instance = item.get("instance") + await instance.off() + except Exception as e: + logging.error("Faild to switch on Actor {} {}".format(id, e)) - async def _stop_actor(self, actor): - actor.instance.stop() - await self.cbpi.bus.fire(topic="actor/%s/stopped" % actor.id, id=actor.id) + async def toogle(self, id): + try: + item = self.find_by_id(id) + instance = item.get("instance") + await instance.toggle() + except Exception as e: + logging.error("Faild to switch on Actor {} {}".format(id, e)) + - @on_event(topic="actor/+/switch/on") - async def on(self, actor_id, power=100, **kwargs) -> None: - """ - Method to switch an actor on. - Supporting Event Topic "actor/+/on" - - :param actor_id: the actor id - :param future - :param power: as integer value between 1 and 100 - :param kwargs: - :return: - """ - - 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) - await self.cbpi.bus.fire("actor/%s/on/ok" % actor_id) - - - @on_event(topic="actor/+/toggle") - async def toggle(self, actor_id, power=100, time=None, **kwargs) -> None: - """ - Method to toggle an actor on or off - Supporting Event Topic "actor/+/toggle" - - :param actor_id: the actor actor_id - :param power: the power as integer between 0 and 100 - :return: - """ - - - - self.logger.debug("TOGGLE %s" % actor_id) - actor_id = int(actor_id) - if actor_id in self.cache: - actor = self.cache[actor_id].instance - - if actor.state is True: - await self.off(actor_id=actor_id) - else: - await self.on(actor_id=actor_id) - - if time is not None: - async def time_toggle(cbpi, actor_id, time): - await asyncio.sleep(time) - await cbpi.bus.fire("actor/%s/off" % actor_id, actor_id = actor_id) - await self.cbpi.job.start_job(time_toggle(self.cbpi, actor_id, time), "actor_%s_time_toggle" % actor_id, "actor_toggle") - - - @on_event(topic="actor/+/off") - async def off(self, actor_id, **kwargs) -> None: - """ - Method to switch and actor off - Supporting Event Topic "actor/+/off" - - :param actor_id: the actor actor_id - :param kwargs: - """ - self.logger.debug("OFF %s" % actor_id) - actor_id = int(actor_id) - - if actor_id in self.cache: - actor = self.cache[actor_id].instance - actor.off() - await self.cbpi.bus.fire("actor/%s/off/ok" % actor_id) - - @on_event(topic="actor/+/action") - async def call_action(self, actor_id, data, **kwargs) -> None: - - schema = Schema({"name":str, "parameter":dict}) - schema(data) - name = data.get("name") - parameter = data.get("parameter") - actor = self.cache[actor_id].instance.__getattribute__(name)(**parameter) - - async def _post_add_callback(self, m): - ''' - - :param m: - :return: - ''' - - await self._init_actor(m) - pass - - async def _pre_delete_callback(self, actor_id): - if hasattr(self.cache[int(actor_id)], "instance") and self.cache[int(actor_id)].instance is not None: - await self._stop_actor(self.cache[int(actor_id)]) - - async def _pre_update_callback(self, actor): - if hasattr(actor, "instance") and actor.instance is not None: - await self._stop_actor(actor) - - async def _post_update_callback(self, actor): - - await self._init_actor(actor) + def create_dict(self, data): + try: + instance = data.get("instance") + state = state=instance.get_state() + except Exception as e: + logging.error("Faild to crate actor dict {} ".format(e)) + state = dict() + return dict(name=data.get("name"), id=data.get("id"), type=data.get("type"), state=state,props=data.get("props", [])) \ No newline at end of file diff --git a/cbpi/controller/basic_controller.py b/cbpi/controller/basic_controller.py new file mode 100644 index 0000000..a4f375b --- /dev/null +++ b/cbpi/controller/basic_controller.py @@ -0,0 +1,148 @@ + +import logging +import os.path +import json +import sys, os +import shortuuid +import asyncio + +from tabulate import tabulate + +class BasicController: + + def __init__(self, cbpi, file): + self.name = self.__class__.__name__ + self.cbpi = cbpi + self.cbpi.register(self) + self.service = self + self.types = {} + self.logger = logging.getLogger(__name__) + self.data = [] + self.autostart = True + self._loop = asyncio.get_event_loop() + self.path = os.path.join(".", 'config', file) + self.cbpi.app.on_cleanup.append(self.shutdown) + + async def init(self): + await self.load() + + async def load(self): + logging.info("{} Load ".format(self.name)) + with open(self.path) as json_file: + data = json.load(json_file) + + self.data = data["data"] + + if self.autostart is True: + for d in self.data: + logging.info("{} Starting ".format(self.name)) + await self.start(d.get("id")) + + async def save(self): + logging.info("{} Save ".format(self.name)) + data = dict(data=list(map(lambda x: self.create_dict(x), self.data))) + with open(self.path, "w") as file: + json.dump(data, file, indent=4, sort_keys=True) + await self.push_udpate() + + async def push_udpate(self): + await self.cbpi.bus.fire("sensor/update", data=list(map(lambda x: self.create_dict(x), self.data))) + + def create_dict(self, data): + return dict(name=data.get("name"), id=data.get("id"), type=data.get("type"), status=data.get("status"),props=data.get("props", [])) + + def find_by_id(self, id): + return next((item for item in self.data if item["id"] == id), None) + + def get_index_by_id(self, id): + return next((i for i, item in enumerate(self.data) if item["id"] == id), None) + + async def shutdown(self, app): + logging.info("{} Shutdown ".format(self.name)) + tasks = [] + for item in self.data: + if item.get("instance") is not None and item.get("instance").running is True: + await item.get("instance").stop() + tasks.append(item.get("instance").task) + await asyncio.gather(*tasks) + await self.save() + + async def stop(self, id): + logging.info("{} Stop Id {} ".format(self.name, id)) + + try: + item = self.find_by_id(id) + instance = item.get("instance") + await instance.stop() + await instance.task + except Exception as e: + logging.error("{} Cant stop {} - {}".format(self.name, id, e)) + + async def start(self, id): + logging.info("{} Start Id {} ".format(self.name, id)) + try: + item = self.find_by_id(id) + instance = item.get("instance") + + if instance is not None and instance.running is True: + logging.warning("{} already running {}".format(self.name, id)) + return + + type = item["type"] + print(type) + print(self.types) + clazz = self.types[type]["class"] + item["instance"] = clazz(self.cbpi, item["id"], {}) + print(item["instance"]) + await item["instance"].start() + item["instance"].task = self._loop.create_task(item["instance"].run()) + logging.info("Sensor started {}".format(id)) + except Exception as e: + logging.error("{} Cant start {} - {}".format(self.name, id, e)) + + def get_types(self): + logging.info("{} Get Types".format(self.name)) + result = {} + for key, value in self.types.items(): + result[key] = dict(name=value.get("name"), properties=value.get("properties"), actions=value.get("actions")) + return result + + def get_state(self): + logging.info("{} Get State".format(self.name)) + return {"data": list(map(lambda x: self.create_dict(x), self.data)), "types":self.get_types()} + + async def add(self, data): + logging.info("{} Add".format(self.name)) + id = shortuuid.uuid() + item = {**data, "id": id, "instance": None , "name": data.get("name"), "props": data.get("props", {})} + self.data.append(item) + if self.autostart is True: + await self.start(id) + await self.save() + return item + + async def update(self, id, data) -> dict: + logging.info("{} Get Update".format(self.name)) + await self.stop(id) + if self.autostart is True: + await self.start(id) + self.data = list(map(lambda old: {**old, **data} if old["id"] == id else old, self.data)) + await self.save() + return self.find_by_id(id) + + async def delete(self, id) -> None: + logging.info("{} Delete".format(self.name)) + await self.stop(id) + self.data = list(filter(lambda x: x["id"] != id, self.data)) + await self.save() + + async def call_action(self, id, action, parameter) -> None: + + logging.info("{} call all Action {} {}".format(self.name, id, action)) + try: + item = self.find_by_id(id) + print(item) + instance = item.get("instance") + await instance.__getattribute__(action)(**parameter) + except Exception as e: + logging.error("{} Faild to call action on {} {} {}".format(self.name, id, action, e)) diff --git a/cbpi/controller/crud_controller.py b/cbpi/controller/crud_controller.py deleted file mode 100644 index 276add4..0000000 --- a/cbpi/controller/crud_controller.py +++ /dev/null @@ -1,169 +0,0 @@ -import pprint -from abc import ABCMeta - -from cbpi.api import * - - -class CRUDController(metaclass=ABCMeta): - - - cache = {} - caching = True - name = None - - def __init__(self, cbpi): - self.cbpi = cbpi - self.cache = {} - - async def init(self): - ''' - - :return: - ''' - - if self.caching is True: - self.cache = await self.model.get_all() - - async def get_all(self, force_db_update=False): - ''' - - :param force_db_update: - :return: - ''' - if self.caching is False or force_db_update: - self.cache = await self.model.get_all() - - return self.cache - - async def get_one(self, id): - ''' - - :param id: - :return: - ''' - if id not in self.cache: - raise CBPiException("%s with id %s not found" % (self.name,id)) - return self.cache.get(id) - - async def _pre_add_callback(self, data): - ''' - - :param data: - :return: - ''' - pass - - async def _post_add_callback(self, m): - ''' - - :param m: - :return: - ''' - pass - - async def add(self, **data): - ''' - - :param data: - :return: - ''' - - - await self._pre_add_callback(data) - - - m = await self.model.insert(**data) - - self.cache[m.id] = m - await self._post_add_callback(m) - - await self.cbpi.bus.fire(topic="actor/%s/added" % m.id, actor=m) - - return m - - async def _pre_update_callback(self, m): - pass - - async def _post_update_callback(self, m): - pass - - async def update(self, id, data): - ''' - - :param id: - :param data: - :return: - ''' - - self.logger.debug("Update Sensor %s - %s " % (id, data)) - - id = int(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 - - try: - ### DELETE INSTANCE BEFORE UPDATE - del data["instance"] - except Exception as e: - pass - - if self.caching is True: - await self._pre_update_callback(self.cache[id]) - 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): - ''' - - :param m: - :return: - ''' - pass - - async def _post_delete_callback(self, id): - ''' - - :param id: - :return: - ''' - pass - - async def delete(self, id): - ''' - - :param id: - :return: - ''' - - if id not in self.cache: - raise CBPiException("%s with id %s not found" % (self.name,id)) - - await self._pre_delete_callback(id) - m = await self.model.delete(id) - await self._post_delete_callback(id) - try: - if self.caching is True: - del self.cache[int(id)] - except Exception as e: - pass - - await self.cbpi.bus.fire(topic="actor/%s/deleted" % id, id=id) - - async def delete_all(self): - ''' - - :return: - ''' - self.model.delete_all() - if self.caching is True: - self.cache = {} - #self.cbpi.push_ws("DELETE_ALL_%s" % self.key, None) \ No newline at end of file diff --git a/cbpi/controller/dashboard_controller.py b/cbpi/controller/dashboard_controller.py index 7848351..032fc22 100644 --- a/cbpi/controller/dashboard_controller.py +++ b/cbpi/controller/dashboard_controller.py @@ -1,23 +1,18 @@ import logging import json -from cbpi.controller.crud_controller import CRUDController -from cbpi.database.model import DashboardModel, DashboardContentModel import os -class DashboardController(CRUDController): +class DashboardController(): - model = DashboardModel - name = "Dashboard" def __init__(self, cbpi): self.caching = False - super(DashboardController, self).__init__(cbpi) self.cbpi = cbpi self.logger = logging.getLogger(__name__) self.cbpi.register(self) - def get_state(self): - return dict(items=self.cache) + async def init(self): + pass async def get_content(self, dashboard_id): try: @@ -26,12 +21,10 @@ class DashboardController(CRUDController): return data except: return {} - - + async def add_content(self, dashboard_id, data): with open('./config/dashboard/cbpi_dashboard_%s.json' % dashboard_id, 'w') as outfile: json.dump(data, outfile, indent=4, sort_keys=True) - return {"status": "OK"} async def delete_content(self, dashboard_id): diff --git a/cbpi/controller/kettle_controller.py b/cbpi/controller/kettle_controller.py index 550ab90..eeda472 100644 --- a/cbpi/controller/kettle_controller.py +++ b/cbpi/controller/kettle_controller.py @@ -1,179 +1,41 @@ -import re -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 +from cbpi.controller.basic_controller import BasicController import logging - -class KettleController(CRUDController): - ''' - The main kettle controller - ''' - model = KettleModel +from tabulate import tabulate +class KettleController(BasicController): def __init__(self, cbpi): - super(KettleController, self).__init__(cbpi) - self.cbpi = cbpi - self.types = {} - self.logger = logging.getLogger(__name__) - self.cbpi.register(self) - - async def init(self): - ''' - This method initializes all actors during startup. It creates actor instances - - :return: - ''' - await super(KettleController, self).init() - for key, value in self.cache.items(): - value.state = False - - - def get_state(self): - return dict(items=self.cache,types=self.types) - - async def toggle_automtic(self, id): - ''' + super(KettleController, self).__init__(cbpi, "kettle.json") + self.autostart = False - Convenience Method to toggle automatic - - :param id: kettle id as int - :return: (boolean, string) - ''' - kettle = await self.get_one(id) - if kettle is None: - raise KettleException("Kettle not found") - if kettle.logic is None: - raise CBPiExtension("Logic not found for kettle id: %s" % id) + async def on(self, id): + try: + item = self.find_by_id(id) + instance = item.get("instance") + await instance.start() + except Exception as e: + logging.error("Faild to switch on KettleLogic {} {}".format(id, e)) - await self.cbpi.bus.fire(topic="kettle/%s/automatic" % id, id=id) + async def off(self, id): + try: + item = self.find_by_id(id) + instance = item.get("instance") + await instance.stop() + except Exception as e: + logging.error("Faild to switch on KettleLogic {} {}".format(id, e)) - @on_event(topic="job/+/done") - async def job_stop(self, key, **kwargs) -> None: + async def set_target_temp(self, id, target_temp): + try: + item = self.find_by_id(id) + item["target_temp"] = target_temp + await self.save() + except Exception as e: + logging.error("Faild to set Target Temp {} {}".format(id, e)) - match = re.match("kettle_logic_(\d+)", key) - if match is not None: - kid = match.group(1) - - - kettle = self.cache[int(kid)] - kettle.instance = None - kettle.state = False - - await self.cbpi.bus.fire(topic="kettle/%s/logic/stop" % kid) - - @on_event(topic="kettle/+/automatic") - async def handle_automtic_event(self, id, **kwargs): - - ''' - Method to handle the event 'kettle/+/automatic' - - :param id: The kettle id - :param kwargs: - :return: None - ''' - id = int(id) - - - if id in self.cache: - - kettle = self.cache[id] - - if hasattr(kettle, "instance") is False: - kettle.instance = None - self._is_logic_running(id) - - - if kettle.instance is None: - - if kettle.logic in self.types: - clazz = self.types.get("CustomKettleLogic")["class"] - cfg = kettle.config.copy() - cfg.update(dict(cbpi=self.cbpi)) - kettle.instance = clazz(**cfg) - - await self.cbpi.job.start_job(kettle.instance.run(), "kettle_logic_%s" % kettle.id, "kettle_logic%s" % id) - kettle.state = True - - await self.cbpi.bus.fire(topic="kettle/%s/logic/start" % id) - else: - kettle.instance.running = False - kettle.instance = None - kettle.state = False - await self.cbpi.bus.fire(topic="kettle/%s/logic/stop" % id) - - 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) - ''' - kettle = await self.get_one(id) - if kettle is None: - raise KettleException("Kettle not found") - if kettle.sensor is None: - raise ActorException("Actor not defined for kettle id %s" % id) - heater_id = kettle.heater - await self.cbpi.bus.fire(topic="actor/%s/switch/on" % heater_id, actor_id=heater_id, power=99) - - async def heater_off(self, id): - ''' - - Convenience Method to switch the heater of a kettle off - - :param id: - :return: - ''' - kettle = await self.get_one(id) - if kettle is None: - raise KettleException("Kettle not found") - if kettle.sensor is None: - raise ActorException("Actor not defined for kettle id %s" % id) - heater_id = kettle.heater - await self.cbpi.bus.fire(topic="actor/%s/switch/off" % heater_id, actor_id=heater_id, power=99) - - async def agitator_on(self, id): - kettle = await self.get_one(id) - if kettle is None: - raise KettleException("Kettle not found") - if kettle.sensor is None: - raise ActorException("Actor not defined for kettle id %s" % id) - agitator_id = kettle.agitator - await self.cbpi.bus.fire(topic="actor/%s/switch/on" % agitator_id, actor_id=agitator_id, power=99) - - async def agitator_off(self, id): - kettle = await self.get_one(id) - if kettle is None: - raise KettleException("Kettle not found") - if kettle.sensor is None: - raise ActorException("Actor not defined for kettle id %s" % id) - agitator_id = kettle.agitator - await self.cbpi.bus.fire(topic="actor/%s/switch/off" % agitator_id, actor_id=agitator_id, power=99) - - async def get_traget_temp(self, id): - kettle = await self.get_one(id) - if kettle is None: - raise KettleException("Kettle Not Found") - return kettle.target_temp - - async def get_temp(self, id): - - kettle = await self.get_one(id) - if kettle is None: - raise KettleException("Kettle Not Found") - if kettle.sensor is None: - raise SensorException("Sensor not defined for kettle id %s" % id) - - sensor_id = kettle.sensor - return await self.cbpi.sensor.get_value(sensor_id) - - @on_event(topic="kettle/+/targettemp") - async def set_target_temp(self, kettle_id, target_temp, **kwargs) -> None: - - kettle = self.cache[int(kettle_id)] - kettle.target_temp = int(target_temp) - await self.model.update(**kettle.__dict__) - await self.cbpi.bus.fire("kettle/%s/targettemp/set" % kettle_id) \ No newline at end of file + def create_dict(self, data): + try: + instance = data.get("instance") + state = dict(state=instance.get_state()) + except Exception as e: + logging.error("Faild to create KettleLogic dict {} ".format(e)) + state = dict() + return dict(name=data.get("name"), id=data.get("id"), type=data.get("type"), sensor=data.get("sensor"), heater=data.get("heater"), agitator=data.get("agitator"), target_temp=data.get("target_temp"), state=state,props=data.get("props", [])) \ No newline at end of file diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index e5cc10c..9f3456d 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -7,7 +7,6 @@ from time import strftime, localtime import pandas as pd import zipfile - class LogController: def __init__(self, cbpi): diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index c4f98f0..baa10db 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -18,9 +18,10 @@ class PluginController(): def __init__(self, cbpi): self.cbpi = cbpi - - self.plugins = {} self.plugins = load_config("./config/plugin_list.txt") + if self.plugins is None: + self.plugins = {} + async def load_plugin_list(self): async with aiohttp.ClientSession() as session: @@ -98,7 +99,7 @@ class PluginController(): self.modules[filename] = import_module( "cbpi.extension.%s" % (filename)) self.modules[filename].setup(self.cbpi) - logger.info("Plugin %s loaded successful" % filename) + #logger.info("Plugin %s loaded successful" % filename) else: logger.warning( "Plugin %s is not supporting version 4" % filename) @@ -116,7 +117,7 @@ class PluginController(): self.modules[p] = import_module(p) self.modules[p].setup(self.cbpi) - logger.info("Plugin %s loaded successfully" % p) + #logger.info("Plugin %s loaded successfully" % p) except Exception as e: logger.error("FAILED to load plugin %s " % p) logger.error(e) @@ -128,22 +129,19 @@ class PluginController(): :param clazz: actor class :return: None ''' - logger.info("Register %s Class %s" % (name, clazz.__name__)) - if issubclass(clazz, CBPiActor): - # self.cbpi.actor.types[name] = {"class": clazz, "config": self._parse_props(clazz)} - self.cbpi.actor.types[name] = self._parse_props(clazz) + logger.debug("Register %s Class %s" % (name, clazz.__name__)) + + if issubclass(clazz, CBPiActor2): + self.cbpi.actor.types[name] = self._parse_step_props(clazz,name) - if issubclass(clazz, CBPiSensor): - self.cbpi.sensor.types[name] = self._parse_props(clazz) - - if issubclass(clazz, CBPiKettleLogic): - self.cbpi.kettle.types[name] = self._parse_props(clazz) - - if issubclass(clazz, CBPiSimpleStep): - self.cbpi.step.types[name] = self._parse_props(clazz) + if issubclass(clazz, CBPiKettleLogic2): + self.cbpi.kettle.types[name] = self._parse_step_props(clazz,name) + + if issubclass(clazz, CBPiSensor2): + self.cbpi.sensor.types[name] = self._parse_step_props(clazz,name) if issubclass(clazz, CBPiStep): - self.cbpi.step2.types[name] = self._parse_step_props(clazz,name) + self.cbpi.step.types[name] = self._parse_step_props(clazz,name) if issubclass(clazz, CBPiExtension): self.c = clazz(self.cbpi) @@ -165,8 +163,7 @@ class PluginController(): def _parse_step_props(self, cls, name): - result = {"name": name, "class": cls, - "properties": [], "actions": []} + result = {"name": name, "class": cls, "properties": [], "actions": []} if hasattr(cls, "cbpi_parameters"): parameters = [] @@ -174,7 +171,9 @@ class PluginController(): parameters.append(self._parse_property_object(p)) result["properties"] = parameters for method_name, method in cls.__dict__.items(): + if hasattr(method, "action"): + print(method_name) key = method.__getattribute__("key") parameters = [] for p in method.__getattribute__("parameters"): diff --git a/cbpi/controller/sensor_controller.py b/cbpi/controller/sensor_controller.py index 82fcde5..a542d70 100644 --- a/cbpi/controller/sensor_controller.py +++ b/cbpi/controller/sensor_controller.py @@ -1,84 +1,5 @@ +from cbpi.controller.basic_controller import BasicController -import logging -from cbpi.controller.crud_controller import CRUDController -from cbpi.database.model import SensorModel -from cbpi.job.aiohttp import get_scheduler_from_app - - - -class SensorController(CRUDController): - - model = SensorModel - +class SensorController(BasicController): def __init__(self, cbpi): - self.cbpi = cbpi - self.cbpi.register(self) - self.service = self - self.types = {} - self.logger = logging.getLogger(__name__) - self.sensors = {} - - async def init(self): - ''' - This method initializes all actors during startup. It creates actor instances - :return: - ''' - await super(SensorController, self).init() - for id, value in self.cache.items(): - await self.init_sensor(value) - - def get_state(self): - return dict(items=self.cache,types=self.types) - - async def init_sensor(self, sensor): - - - print("INIT SENSOR") - if sensor.type in self.types: - cfg = sensor.config.copy() - cfg.update(dict(cbpi=self.cbpi, id=sensor.id, name=sensor.name)) - clazz = self.types[sensor.type]["class"]; - 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 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()))) - - - - async def stop_sensor(self, sensor): - - sensor.instance.stop() - await self.cbpi.bus.fire(topic="sensor/%s/stopped" % sensor.id, id=sensor.id) - - - async def get_value(self, sensor_id): - sensor_id = int(sensor_id) - return self.cache[sensor_id].instance.value - - async def _post_add_callback(self, m): - ''' - - :param m: - :return: - ''' - await self.init_sensor(m) - pass - - async def _pre_delete_callback(self, sensor_id): - if int(sensor_id) not in self.cache: - return - if hasattr(self.cache[int(sensor_id)], "instance") and self.cache[int(sensor_id)].instance is not None: - await self.stop_sensor(self.cache[int(sensor_id)]) - - async def _pre_update_callback(self, sensor): - - if hasattr(sensor, "instance") and sensor.instance is not None: - await self.stop_sensor(sensor) - - async def _post_update_callback(self, sensor): - await self.init_sensor(sensor) \ No newline at end of file + super(SensorController, self).__init__(cbpi, "sensor.json") diff --git a/cbpi/controller/step_controller.py b/cbpi/controller/step_controller.py index b4ccd9c..d10395a 100644 --- a/cbpi/controller/step_controller.py +++ b/cbpi/controller/step_controller.py @@ -1,194 +1,245 @@ +import asyncio + +from tabulate import tabulate +import json +import copy +import shortuuid import logging -import time +import os.path -from cbpi.api import * -from cbpi.controller.crud_controller import CRUDController -from cbpi.database.model import StepModel +from ..api.step import CBPiStep -class StepController(CRUDController): - +class StepController: def __init__(self, cbpi): - self.model = StepModel - - self.caching = True - self.is_stopping = False self.cbpi = cbpi - self.current_task = None - self.is_next = False - self.types = {} - self.current_step = None - self.current_job = None - self.cbpi.register(self) + self.woohoo = "HALLLO" self.logger = logging.getLogger(__name__) - self.starttime = None + self.path = os.path.join(".", 'config', "step_data.json") + self._loop = asyncio.get_event_loop() + self.basic_data = {} + self.step = None + self.types = {} + + self.cbpi.app.on_cleanup.append(self.shutdown) + + async def init(self): + logging.info("INIT STEP Controller") + self.load(startActive=True) - def is_running(self): - if self.current_step is not None: - return True + def load(self, startActive=False): + + # create file if not exists + if os.path.exists(self.path) is False: + with open(self.path, "w") as file: + json.dump(dict(basic={}, profile=[]), file, indent=4, sort_keys=True) + + #load from json file + with open(self.path) as json_file: + data = json.load(json_file) + self.basic_data = data["basic"] + self.profile = data["profile"] + + # Start step after start up + self.profile = list(map(lambda item: {**item, "instance": self.create_step(item.get("id"), item.get("type"), item.get("name"), item.get("props", {}))}, self.profile)) + if startActive is True: + active_step = self.find_by_status("A") + if active_step is not None: + self._loop.create_task(self.start_step(active_step)) + + async def add(self, data): + logging.info("Add step") + id = shortuuid.uuid() + item = {**{"status": "I", "props": {}}, **data, "id": id, "instance": self.create_step(id, data.get("type"), data.get("name"), data.get("props", {}))} + self.profile.append(item) + await self.save() + return item + + async def update(self, id, data): + logging.info("update step") + + self.profile = list(map(lambda old: {**old, **data} if old["id"] == id else old, self.profile)) + await self.save() + return self.find_by_id(id) + + async def save(self): + logging.debug("save profile") + data = dict(basic=self.basic_data, profile=list(map(lambda x: dict(name=x["name"], type=x.get("type"), id=x["id"], status=x["status"],props=x["props"]), self.profile))) + with open(self.path, "w") as file: + json.dump(data, file, indent=4, sort_keys=True) + await self.push_udpate() + + async def start(self): + # already running + if self.find_by_status("A") is not None: + logging.error("Steps already running") + return + # Find next inactive step + step = self.find_by_status("P") + if step is not None: + + logging.info("Resume step") + + await self.start_step(step) + await self.save() + return + + step = self.find_by_status("I") + if step is not None: + logging.info("Start Step") + + await self.start_step(step) + await self.save() + return + + logging.info("BREWING COMPLETE") + + async def next(self): + logging.info("Trigger Next") + step = self.find_by_status("A") + if step is not None: + instance = step.get("instance") + if instance is not None: + logging.info("Next") + instance.next() + await instance.task else: - return False + logging.info("No Step is running") + + async def resume(self): + step = self.find_by_status("P") + if step is not None: + instance = step.get("instance") + if instance is not None: + await self.start_step(step) + else: + logging.info("Nothing to resume") + async def stop(self): + logging.info("STOP STEP") + step = self.find_by_status("A") + if step != None and step.get("instance") is not None: + logging.info("CALLING STOP STEP") + instance = step.get("instance") + instance.stop() + # wait for task to be finished + await instance.task + logging.info("STEP STOPPED") + step["status"] = "P" + await self.save() + async def reset_all(self): + step = self.find_by_status("A") + if step is not None: + logging.error("Please stop before reset") + return + for item in self.profile: + logging.info("Reset %s" % item.get("name")) + item["status"] = "I" + await item["instance"].reset() + await self.push_udpate() - def _get_manged_fields_as_array(self, type_cfg): + def create_step(self, id, type, name, props): - result = [] + try: + type_cfg = self.types.get(type) + clazz = type_cfg.get("class") + return clazz(self.cbpi, id, name, {**props}) + except: + pass - for f in type_cfg.get("properties"): - - result.append(f.get("name")) + def create_dict(self, data): + return dict(name=data["name"], id=data["id"], type=data.get("type"), status=data["status"],props=data["props"], state_text=data["instance"].get_state()) + def get_types2(self): + result = {} + for key, value in self.types.items(): + result[key] = dict(name=value.get("name"), properties=value.get("properties"), actions=value.get("actions")) return result - async def init(self): + def get_state(self): + return {"basic": self.basic_data, "profile": list(map(lambda x: self.create_dict(x), self.profile)), "types":self.get_types2()} - # 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: - 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): - - if self.is_running() is False: - 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__) - 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, step=next_step) - else: - await self.cbpi.bus.fire("step/brewing/finished") - else: - self.logger.error("Process Already Running") - - 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: + async def move(self, id, direction): + index = self.get_index_by_id(id) + if direction not in [-1, 1]: + self.logger.error("Cant move. Direction 1 and -1 allowed") return - if self.is_stopping: - self.is_stopping = False + self.profile[index], self.profile[index+direction] = self.profile[index+direction], self.profile[index] + await self.save() + await self.push_udpate() + + async def delete(self, id): + step = self.find_by_id(id) + if step.get("status") == "A": + logging.error("Cant delete active Step %s", id) + return + + self.profile = list(filter(lambda x: x["id"] != id, self.profile)) + await self.save() + + + async def shutdown(self, app): + logging.info("Mash Profile Shutdonw") + for p in self.profile: + instance = p.get("instance") + # Stopping all running task + if instance.task != None and instance.task.done() is False: + logging.info("Stop Step") + instance.stop() + await instance.task + await self.save() + + def done(self, task): + id, reason = task.result() + if reason == "MAX_EXCEPTIONS": + step_current = self.find_by_id(id) + step_current["status"] = "E" + self._loop.create_task(self.save()) return - if self.current_step is not None: + if reason == "NEXT": + step_current = self.find_by_status("A") + if step_current is not None: - self.current_step.state = "D" - await self.model.update_state(self.current_step.id, "D", int(time.time())) - await self.cbpi.bus.fire("step/%s/done" % self.current_step.id, step=self.current_step) - self.current_step = None + step_current["status"] = "D" + async def wrapper(): + ## TODO DONT CALL SAVE + await self.save() + await self.start() + self._loop.create_task(wrapper()) + - # start the next step - await self.start() + def find_by_status(self, status): + return next((item for item in self.profile if item["status"] == status), None) - @on_event("step/stop") - 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") + def find_by_id(self, id): + return next((item for item in self.profile if item["id"] == id), None) + + def get_index_by_id(self, id): + return next((i for i, item in enumerate(self.profile) if item["id"] == id), None) - @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") + async def push_udpate(self): + await self.cbpi.bus.fire("step/update", data=list(map(lambda x: self.create_dict(x), self.profile))) + + async def start_step(self,step): + logging.info("Start Step") + step.get("instance").start() + step["instance"].task = self._loop.create_task(step["instance"].run(), name=step["name"]) + step["instance"].task .add_done_callback(self.done) + step["status"] = "A" - async def _pre_add_callback(self, data): + async def update_props(self, id, props): + logging.info("SAVE PROPS") + step = self.find_by_id(id) + step["props"] = props + await self.save() + await self.push_udpate() - 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) - - @on_event(topic="step/action") - async def call_action(self, name, parameter, **kwargs) -> None: - print(name, parameter) - if self.current_step is not None: - - self.current_step.instance.__getattribute__(name)(**parameter) + async def save_basic(self, data): + logging.info("SAVE Basic Data") + self.basic_data = {**self.basic_data, **data,} + await self.save() + await self.push_udpate() diff --git a/cbpi/controller/step_controller_ng.py b/cbpi/controller/step_controller_ng.py deleted file mode 100644 index 8f6bedd..0000000 --- a/cbpi/controller/step_controller_ng.py +++ /dev/null @@ -1,251 +0,0 @@ -import asyncio - -from tabulate import tabulate -import json -import copy -import shortuuid -import logging -import os.path - -from ..api.step import CBPiStep - -class Step2(CBPiStep): - - async def execute(self): - - print(self.props) - await self.update(self.props) - print("HALLO") - #raise Exception("RROR") - -class StepControllerNg: - - def __init__(self, cbpi): - self.cbpi = cbpi - self.woohoo = "HALLLO" - self.logger = logging.getLogger(__name__) - self.path = os.path.join(".", 'config', "step_data.json") - self._loop = asyncio.get_event_loop() - self.basic_data = {} - self.step = None - self.types = {} - - self.cbpi.app.on_cleanup.append(self.shutdown) - - async def init(self): - logging.info("INIT STEP Controller") - self.load(startActive=True) - - def load(self, startActive=False): - - # create file if not exists - if os.path.exists(self.path) is False: - with open(self.path, "w") as file: - json.dump(dict(basic={}, profile=[]), file, indent=4, sort_keys=True) - - #load from json file - with open(self.path) as json_file: - data = json.load(json_file) - self.basic_data = data["basic"] - self.profile = data["profile"] - - # Start step after start up - self.profile = list(map(lambda item: {**item, "instance": self.create_step(item.get("id"), item.get("type"), item.get("name"), item.get("props", {}))}, self.profile)) - if startActive is True: - active_step = self.find_by_status("A") - if active_step is not None: - self._loop.create_task(self.start_step(active_step)) - - async def add(self, data): - logging.info("Add step") - print(data) - id = shortuuid.uuid() - item = {**{"status": "I", "props": {}}, **data, "id": id, "instance": self.create_step(id, data.get("type"), data.get("name"), data.get("props", {}))} - self.profile.append(item) - await self.save() - return item - - async def update(self, id, data): - logging.info("update step") - print(id, data) - #if "instance" in data: del data["instance"] - self.profile = list(map(lambda old: {**old, **data} if old["id"] == id else old, self.profile)) - print(tabulate(self.profile)) - await self.save() - return self.find_by_id(id) - - async def save(self): - logging.debug("save profile") - data = dict(basic=self.basic_data, profile=list(map(lambda x: dict(name=x["name"], type=x.get("type"), id=x["id"], status=x["status"],props=x["props"]), self.profile))) - with open(self.path, "w") as file: - json.dump(data, file, indent=4, sort_keys=True) - await self.push_udpate() - - async def start(self): - # already running - if self.find_by_status("A") is not None: - logging.error("Steps already running") - return - # Find next inactive step - step = self.find_by_status("P") - if step is not None: - - logging.info("Resume step") - - await self.start_step(step) - await self.save() - return - - step = self.find_by_status("I") - if step is not None: - logging.info("Start Step") - - await self.start_step(step) - await self.save() - return - - logging.info("BREWING COMPLETE") - - async def next(self): - logging.info("Trigger Next") - step = self.find_by_status("A") - if step is not None: - instance = step.get("instance") - if instance is not None: - logging.info("Next") - instance.next() - await instance.task - else: - logging.info("No Step is running") - - async def resume(self): - step = self.find_by_status("P") - if step is not None: - instance = step.get("instance") - if instance is not None: - await self.start_step(step) - else: - logging.info("Nothing to resume") - - async def stop(self): - logging.info("STOP STEP") - step = self.find_by_status("A") - if step != None and step.get("instance") is not None: - logging.info("CALLING STOP STEP") - instance = step.get("instance") - instance.stop() - # wait for task to be finished - await instance.task - logging.info("STEP STOPPED") - step["status"] = "P" - await self.save() - - async def reset_all(self): - step = self.find_by_status("A") - if step is not None: - logging.error("Please stop before reset") - return - for item in self.profile: - logging.info("Reset %s" % item.get("name")) - item["status"] = "I" - await item["instance"].reset() - await self.push_udpate() - - def create_step(self, id, type, name, props): - - type_cfg = self.types.get(type) - clazz = type_cfg.get("class") - return clazz(self.cbpi, id, name, {**props}) - - def create_dict(self, data): - return dict(name=data["name"], id=data["id"], type=data.get("type"), status=data["status"],props=data["props"], state_text=data["instance"].get_state()) - - def get_types2(self): - result = {} - for key, value in self.types.items(): - print(value) - result[key] = dict(name=value.get("name"), properties=value.get("properties"), actions=value.get("actions")) - return result - - def get_state(self): - return {"basic": self.basic_data, "profile": list(map(lambda x: self.create_dict(x), self.profile)), "types":self.get_types2()} - - async def move(self, id, direction): - index = self.get_index_by_id(id) - if direction not in [-1, 1]: - self.logger.error("Cant move. Direction 1 and -1 allowed") - return - self.profile[index], self.profile[index+direction] = self.profile[index+direction], self.profile[index] - self.save() - await self.push_udpate() - - async def delete(self, id): - step = self.find_by_id(id) - if step.get("status") == "A": - logging.error("Cant delete active Step %s", id) - return - - self.profile = list(filter(lambda x: x["id"] != id, self.profile)) - await self.save() - - - async def shutdown(self, app): - logging.info("Mash Profile Shutdonw") - for p in self.profile: - instance = p.get("instance") - # Stopping all running task - if instance.task != None and instance.task.done() is False: - logging.info("Stop Step") - instance.stop() - await instance.task - await self.save() - - def done(self, task): - id, reason = task.result() - print(id, reason) - if reason == "MAX_EXCEPTIONS": - step_current = self.find_by_id(id) - step_current["status"] = "E" - self._loop.create_task(self.save()) - return - - if reason == "NEXT": - step_current = self.find_by_status("A") - if step_current is not None: - step_current["status"] = "D" - async def wrapper(): - await self.save() - await self.start() - self._loop.create_task(wrapper()) - - - def find_by_status(self, status): - return next((item for item in self.profile if item["status"] == status), None) - - def find_by_id(self, id): - return next((item for item in self.profile if item["id"] == id), None) - - def get_index_by_id(self, id): - return next((i for i, item in enumerate(self.profile) if item["id"] == id), None) - - async def push_udpate(self): - print("PUS UPDATE") - await self.cbpi.bus.fire("step/update", data=list(map(lambda x: self.create_dict(x), self.profile))) - - async def start_step(self,step): - logging.info("############# start step") - step.get("instance").start() - step["instance"].task = self._loop.create_task(step["instance"].run(), name=step["name"]) - print(step["instance"].task) - step["instance"].task .add_done_callback(self.done) - step["status"] = "A" - - async def update_props(self, id, props): - logging.info("SAVE PROPS") - print(id, props) - step = self.find_by_id(id) - step["props"] = props - await self.save() - await self.push_udpate() - - \ No newline at end of file diff --git a/cbpi/controller/translation_controller.py b/cbpi/controller/translation_controller.py deleted file mode 100644 index 0cce8d5..0000000 --- a/cbpi/controller/translation_controller.py +++ /dev/null @@ -1,28 +0,0 @@ -import logging - -from cbpi.database.model import TranslationModel - -class TranslationController(object): - - - def __init__(self, cbpi): - self.cbpi = cbpi - self._cache = {} - self.logger = logging.getLogger(__name__) - - async def init(self): - self._cache = await TranslationModel.get_all() - - - - def get_all(self): - return self._cache - - async def add_key(self, locale, key): - - try: - if locale not in self._cache or key not in self._cache[locale]: - await TranslationModel.add_key(locale, key) - self._cache = await TranslationModel.get_all() - except Exception as e: - self.logger.error("Error during adding translation key %s - %s - %s" % (key, locale, str(e))) diff --git a/cbpi/craftbeerpi.py b/cbpi/craftbeerpi.py index 8b0ccc4..60b906c 100644 --- a/cbpi/craftbeerpi.py +++ b/cbpi/craftbeerpi.py @@ -1,7 +1,7 @@ import logging from os import urandom import os - +from cbpi import __version__ from aiohttp import web from aiohttp_auth import auth from aiohttp_session import session_middleware @@ -19,8 +19,9 @@ from cbpi.controller.notification_controller import NotificationController from cbpi.controller.plugin_controller import PluginController from cbpi.controller.sensor_controller import SensorController from cbpi.controller.step_controller import StepController -from cbpi.controller.step_controller_ng import StepControllerNg + from cbpi.controller.system_controller import SystemController + from cbpi.controller.log_file_controller import LogController from cbpi.database.model import DBModel from cbpi.eventbus import CBPiEventBus @@ -28,14 +29,14 @@ from cbpi.http_endpoints.http_login import Login from cbpi.utils import * from cbpi.websocket import CBPiWebSocket from cbpi.http_endpoints.http_actor import ActorHttpEndpoints + + from cbpi.http_endpoints.http_config import ConfigHttpEndpoints from cbpi.http_endpoints.http_dashboard import DashBoardHttpEndpoints from cbpi.http_endpoints.http_kettle import KettleHttpEndpoints from cbpi.http_endpoints.http_sensor import SensorHttpEndpoints from cbpi.http_endpoints.http_step import StepHttpEndpoints -from cbpi.http_endpoints.http_step2 import StepHttpEndpoints2 -from cbpi.controller.translation_controller import TranslationController -from cbpi.http_endpoints.http_translation import TranslationHttpEndpoint + from cbpi.http_endpoints.http_plugin import PluginHttpEndpoints from cbpi.http_endpoints.http_system import SystemHttpEndpoints from cbpi.http_endpoints.http_log import LogHttpEndpoints @@ -68,7 +69,7 @@ class CraftBeerPi(): def __init__(self): - self.version = "4.0.0.1" + self.version = __version__ self.static_config = load_config("./config/config.yaml") self.database_file = "./craftbeerpi.db" @@ -87,26 +88,24 @@ class CraftBeerPi(): self.config = ConfigController(self) self.ws = CBPiWebSocket(self) - self.translation = TranslationController(self) + self.actor = ActorController(self) self.sensor = SensorController(self) self.plugin = PluginController(self) self.log = LogController(self) self.system = SystemController(self) - self.kettle = KettleController(self) self.step = StepController(self) - self.step2 = StepControllerNg(self) + self.dashboard = DashboardController(self) + self.http_step = StepHttpEndpoints(self) - self.http_step2 = StepHttpEndpoints2(self) self.http_sensor = SensorHttpEndpoints(self) self.http_config = ConfigHttpEndpoints(self) self.http_actor = ActorHttpEndpoints(self) self.http_kettle = KettleHttpEndpoints(self) self.http_dashboard = DashBoardHttpEndpoints(self) - self.http_translation = TranslationHttpEndpoint(self) self.http_plugin = PluginHttpEndpoints(self) self.http_system = SystemHttpEndpoints(self) self.notification = NotificationController(self) @@ -156,7 +155,7 @@ class CraftBeerPi(): http_method = method.__getattribute__("method") path = method.__getattribute__("path") class_name = method.__self__.__class__.__name__ - logger.info("Register Endpoint : %s.%s %s %s%s " % (class_name, method.__name__, http_method, url_prefix, path)) + logger.debug("Register Endpoint : %s.%s %s %s%s " % (class_name, method.__name__, http_method, url_prefix, path)) def add_post(): routes.append(web.post(method.__getattribute__("path"), method)) @@ -178,7 +177,6 @@ class CraftBeerPi(): } switcher[http_method]() - print("URL PREFIX", url_prefix) if url_prefix != "/": logger.debug("URL Prefix: %s " % (url_prefix,)) sub = web.Application() @@ -199,12 +197,10 @@ class CraftBeerPi(): long_description = """ This is the api for CraftBeerPi """ - - print("SWAGGER.......") setup_swagger(self.app, description=long_description, - title=self.static_config.get("name", "CraftBeerPi 4.0"), - api_version=self.static_config.get("version", "4.0"), + title="CraftBeerPi", + api_version=self.version, contact="info@craftbeerpi.com") def notify(self, key: str, message: str, type: str = "info") -> None: @@ -227,13 +223,15 @@ class CraftBeerPi(): def _print_logo(self): from pyfiglet import Figlet f = Figlet(font='big') - logger.info("\n%s" % f.renderText("%s %s" % (self.static_config.get("name"), self.static_config.get("version")))) + logger.info("\n%s" % f.renderText("CraftBeerPi %s " % self.version)) + logger.info("www.CraftBeerPi.com") + logger.info("(c) 2021 Manuel Fritsch") def _setup_http_index(self): async def http_index(request): url = self.config.static.get("index_url") - + py if url is not None: raise web.HTTPFound(url) @@ -250,12 +248,11 @@ class CraftBeerPi(): await self.job.init() await DBModel.setup() await self.config.init() - await self.translation.init() self._setup_http_index() self.plugin.load_plugins() self.plugin.load_plugins_from_evn() await self.sensor.init() - await self.step2.init() + await self.step.init() await self.actor.init() await self.kettle.init() await self.call_initializer(self.app) diff --git a/cbpi/extension/dummyactor/__init__.py b/cbpi/extension/dummyactor/__init__.py index cac2f60..52067a0 100644 --- a/cbpi/extension/dummyactor/__init__.py +++ b/cbpi/extension/dummyactor/__init__.py @@ -19,31 +19,39 @@ except Exception: patcher.start() import RPi.GPIO as GPIO -class CustomActor(CBPiActor): +class CustomActor(CBPiActor2): + + + my_name = "" # Custom property which can be configured by the user @action("test", parameters={}) - def action1(self): + async def action1(self, **kwargs): + print("ACTION !", kwargs) + self.my_name = kwargs.get("name") pass - - - + def init(self): + print("INIT") + self.state = False pass - def on(self, power=0): - logger.info("ACTOR %s ON" % self.id) + async def on(self, power=0): + logger.info("ACTOR 1111 %s ON" % self.id) self.state = True - def off(self): + async def off(self): logger.info("ACTOR %s OFF " % self.id) self.state = False def get_state(self): - + return self.state + + async def run(self): + pass class GPIOActor(CBPiActor): diff --git a/cbpi/extension/dummylogic/__init__.py b/cbpi/extension/dummylogic/__init__.py index 0237628..88b310a 100644 --- a/cbpi/extension/dummylogic/__init__.py +++ b/cbpi/extension/dummylogic/__init__.py @@ -2,69 +2,14 @@ import asyncio from cbpi.api import * -class CustomLogic(CBPiKettleLogic): +class CustomLogic(CBPiKettleLogic2): - test = Property.Number(label="Test") + pass - running = True - - - async def wait_for_event(self, topic, callback=None, timeout=None): - - - future_obj = self.cbpi.app.loop.create_future() - - async def default_callback(id, **kwargs): - future_obj.set_result("HELLO") - - - if callback is None: - self.cbpi.bus.register(topic=topic, method=default_callback) - else: - callback.future = future_obj - self.cbpi.bus.register(topic=topic, method=callback) - - if timeout is not None: - - try: - - await asyncio.wait_for(future_obj, timeout=timeout) - - return future_obj.result() - except asyncio.TimeoutError: - pass - else: - - await future_obj - return future_obj.result() - - - - async def run(self): - - ''' - async def my_callback(value, **kwargs): - - if value == 5: - self.cbpi.bus.unregister(my_callback) - kwargs["future"].set_result("AMAZING") - else: - pass - - result = await self.wait_for_event("sensor/1/data", callback=my_callback) - ''' - - value = 0 - - - while self.running: - - value = value + 1 - print(value) - if value >= 10: - - break - await asyncio.sleep(1) + @action(key="test", parameters=[]) + async def action1(self, **kwargs): + print("ACTION") + diff --git a/cbpi/extension/dummysensor/__init__.py b/cbpi/extension/dummysensor/__init__.py index ba1e743..abfe25c 100644 --- a/cbpi/extension/dummysensor/__init__.py +++ b/cbpi/extension/dummysensor/__init__.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- import asyncio +import random +import re + from aiohttp import web from cbpi.api import * -import re -import random + class CustomSensor(CBPiSensor): # Custom Properties which will can be configured by the user @@ -48,6 +50,34 @@ class CustomSensor(CBPiSensor): self.log_data(self.value) await cbpi.bus.fire("sensor/%s/data" % self.id, value=self.value) +@parameters([Property.Number(label="Param1", configurable=True), + Property.Text(label="Param2", configurable=True, default_value="HALLO"), + Property.Select(label="Param3", options=[1,2,4]), + Property.Sensor(label="Param4"), + Property.Actor(label="Param5")]) +class CustomSensor2(CBPiSensor2): + + @action(key="Test", parameters=[]) + async def action1(self, **kwargs): + print("ACTION!", kwargs) + + @action(key="Test1", parameters=[]) + async def action2(self, **kwargs): + print("ACTION!", kwargs) + + @action(key="Test2", parameters=[]) + async def action3(self, **kwargs): + + print("ACTION!", kwargs) + + + + async def run(self): + + while self.running is True: + print("HALLO") + await asyncio.sleep(1) + def setup(cbpi): @@ -58,5 +88,5 @@ def setup(cbpi): :param cbpi: the cbpi core :return: ''' - - cbpi.plugin.register("CustomSensor", CustomSensor) + cbpi.plugin.register("CustomSensor2", CustomSensor2) + #cbpi.plugin.register("CustomSensor", CustomSensor) diff --git a/cbpi/extension/dummystep/__init__.py b/cbpi/extension/dummystep/__init__.py index 6d9244e..ae59001 100644 --- a/cbpi/extension/dummystep/__init__.py +++ b/cbpi/extension/dummystep/__init__.py @@ -4,59 +4,33 @@ import time from cbpi.api import * -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 - - if self.timer_end is None: - self.timer_end = time.time() + 10 - - if self.i == 10: - self.next() - ''' - - #self.cbpi.notify(key="step", message="HELLO FROM STEP") - -@parameters([Property.Number(label="Test", configurable=True), Property.Text(label="Test", configurable=True, default_value="HALLO")]) +@parameters([Property.Number(label="Param1", configurable=True), + Property.Text(label="Param2", configurable=True, default_value="HALLO"), + Property.Select(label="Param3", options=[1,2,4]), + Property.Sensor(label="Param4"), + Property.Actor(label="Param5")]) class Step2(CBPiStep): - i = 0 + @action(key="name2", parameters=[]) + async def action2(self, **kwargs): + print("CALL ACTION") @action(key="name", parameters=[Property.Number(label="Test", configurable=True)]) async def action(self, **kwargs): - print("HALLO") + print("CALL ACTION") async def execute(self): + count = self.props.get("count", 0) + self.state_msg = "COUNT %s" % count - print(self.props) - self.i += 1 - print(self.i) - self.state_msg = "COUNT %s" % self.i + self.props["count"] += 1 await self.update(self.props) - print("JETZT GEHTS LO") - #raise Exception("RROR") + if count >= 5: + self.next() + + async def reset(self): + self.props["count"] = 0 def setup(cbpi): ''' @@ -67,4 +41,4 @@ def setup(cbpi): :return: ''' cbpi.plugin.register("CustomStep2", Step2) - cbpi.plugin.register("CustomStepCBPi", CustomStepCBPi) + diff --git a/cbpi/extension/mqtt/__init__.py b/cbpi/extension/mqtt/__init__.py index c1f8173..48e331f 100644 --- a/cbpi/extension/mqtt/__init__.py +++ b/cbpi/extension/mqtt/__init__.py @@ -31,5 +31,10 @@ def setup(cbpi): :param cbpi: the cbpi core :return: ''' + print("MQTT") + print("###################") + print("###################") + print("###################") + print("###################") client = CBPiMqttClient(cbpi) diff --git a/cbpi/http_endpoints/http_actor.py b/cbpi/http_endpoints/http_actor.py index b005828..9f3a8cf 100644 --- a/cbpi/http_endpoints/http_actor.py +++ b/cbpi/http_endpoints/http_actor.py @@ -1,30 +1,14 @@ from aiohttp import web from cbpi.api import * - -from cbpi.http_endpoints.http_curd_endpoints import HttpCrudEndpoints - auth = False -class ActorHttpEndpoints(HttpCrudEndpoints): +class ActorHttpEndpoints(): def __init__(self, cbpi): - super().__init__(cbpi) + self.cbpi = cbpi self.controller = cbpi.actor self.cbpi.register(self, "/actor") - @request_mapping(path="/types", auth_required=False) - async def get_types(self, request): - """ - --- - description: Get all actor types - tags: - - Actor - responses: - "200": - description: successful operation - """ - return await super().get_types(request) - @request_mapping(path="/", auth_required=False) async def http_get_all(self, request): """ @@ -37,29 +21,8 @@ class ActorHttpEndpoints(HttpCrudEndpoints): "204": description: successful operation """ - return await super().http_get_all(request) - - @request_mapping(path="/{id:\d+}", auth_required=False) - async def http_get_one(self, request): - """ - --- - description: Get one Actor - tags: - - Actor - parameters: - - name: "id" - in: "path" - description: "Actor ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - "405": - description: invalid HTTP Met - """ - return await super().http_get_one(request) + return web.json_response(data=self.controller.get_state()) + @request_mapping(path="/", method="POST", auth_required=False) async def http_add(self, request): @@ -73,22 +36,31 @@ class ActorHttpEndpoints(HttpCrudEndpoints): name: body description: Created an actor required: true + schema: type: object + properties: name: type: string type: type: string - config: + props: type: object + example: + name: "Actor 1" + type: "CustomActor" + props: {} + responses: "204": description: successful operation """ + data = await request.json() + response_data = await self.controller.add(data) - return await super().http_add(request) - + return web.json_response(data=self.controller.create_dict(response_data)) + @request_mapping(path="/{id}", method="PUT", auth_required=False) async def http_update(self, request): @@ -116,15 +88,15 @@ class ActorHttpEndpoints(HttpCrudEndpoints): type: type: string config: - type: object + props: object responses: "200": description: successful operation """ - print(".........") - print(request) - return await super().http_update(request) - + id = request.match_info['id'] + data = await request.json() + return web.json_response(data=self.controller.create_dict(await self.controller.update(id, data))) + @request_mapping(path="/{id}", method="DELETE", auth_required=False) async def http_delete_one(self, request): """ @@ -137,15 +109,16 @@ class ActorHttpEndpoints(HttpCrudEndpoints): in: "path" description: "Actor ID" required: true - type: "integer" - format: "int64" + type: "string" responses: "204": description: successful operation """ - return await super().http_delete_one(request) + id = request.match_info['id'] + await self.controller.delete(id) + return web.Response(status=204) - @request_mapping(path="/{id:\d+}/on", method="POST", auth_required=auth) + @request_mapping(path="/{id}/on", method="POST", auth_required=False) async def http_on(self, request) -> web.Response: """ @@ -159,27 +132,24 @@ class ActorHttpEndpoints(HttpCrudEndpoints): in: "path" description: "Actor ID" required: true - type: "integer" - format: "int64" + type: "string" + responses: "204": description: successful operation "405": description: invalid HTTP Met """ - actor_id = int(request.match_info['id']) - result = await self.cbpi.bus.fire(topic="actor/%s/switch/on" % actor_id, actor_id=actor_id, power=99) - for key, value in result.results.items(): - pass + id = request.match_info['id'] + await self.controller.on(id) return web.Response(status=204) - - @request_mapping(path="/{id:\d+}/off", method="POST", auth_required=auth) + @request_mapping(path="/{id}/off", method="POST", auth_required=False) async def http_off(self, request) -> web.Response: """ --- - description: Switch actor off + description: Switch actor on tags: - Actor @@ -188,48 +158,20 @@ class ActorHttpEndpoints(HttpCrudEndpoints): in: "path" description: "Actor ID" required: true - type: "integer" - format: "int64" + type: "string" + responses: "204": description: successful operation "405": description: invalid HTTP Met """ - actor_id = int(request.match_info['id']) - await self.cbpi.bus.fire(topic="actor/%s/off" % actor_id, actor_id=actor_id) + id = request.match_info['id'] + await self.controller.off(id) return web.Response(status=204) + - @request_mapping(path="/{id:\d+}/toggle", method="POST", auth_required=auth) - async def http_toggle(self, request) -> web.Response: - """ - - --- - description: Toogle an actor on or off - tags: - - Actor - parameters: - - name: "id" - in: "path" - description: "Actor ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - "405": - description: invalid HTTP Met - """ - actor_id = int(request.match_info['id']) - if await request.text(): - data = await request.json() - await self.cbpi.bus.fire(topic="actor/%s/toggle" % actor_id, time=data.get("time"), actor_id=actor_id) - else: - await self.cbpi.bus.fire(topic="actor/%s/toggle" % actor_id, actor_id=actor_id) - return web.Response(status=204) - - @request_mapping(path="/{id:\d+}/action", method="POST", auth_required=auth) + @request_mapping(path="/{id}/action", method="POST", auth_required=auth) async def http_action(self, request) -> web.Response: """ @@ -253,13 +195,14 @@ class ActorHttpEndpoints(HttpCrudEndpoints): properties: name: type: string - config: + parameter: type: object responses: "204": description: successful operation """ - actor_id = int(request.match_info['id']) + actor_id = request.match_info['id'] + data = await request.json() + await self.controller.call_action(actor_id, data.get("name"), data.get("parameter")) - await self.cbpi.bus.fire(topic="actor/%s/action" % actor_id, actor_id=actor_id, data=await request.json()) return web.Response(status=204) \ No newline at end of file diff --git a/cbpi/http_endpoints/http_dashboard.py b/cbpi/http_endpoints/http_dashboard.py index 4b39991..c447b20 100644 --- a/cbpi/http_endpoints/http_dashboard.py +++ b/cbpi/http_endpoints/http_dashboard.py @@ -13,114 +13,8 @@ class DashBoardHttpEndpoints(HttpCrudEndpoints): self.controller = cbpi.dashboard self.cbpi.register(self, "/dashboard") - @request_mapping(path="/", auth_required=False) - async def http_get_all(self, request): - """ - --- - description: Get all dashboards - tags: - - Dashboard - responses: - "200": - description: successful operation - """ - return await super().http_get_all(request) - @request_mapping(path="/{id:\d+}", auth_required=False) - async def http_get_one(self, request): - """ - --- - description: Get one Dashboard by id - tags: - - Dashboard - parameters: - - name: "id" - in: "path" - description: "Actor ID" - required: true - type: "integer" - format: "int64" - responses: - "200": - description: successful operation - """ - return await super().http_get_one(request) - - @request_mapping(path="/", method="POST", auth_required=False) - async def http_add(self, request): - """ - --- - description: Create a new Dashboard - tags: - - Dashboard - parameters: - - in: body - name: body - description: Create a new Dashboard - required: false - schema: - type: object - properties: - name: - type: string - responses: - "200": - description: successful operation - """ - - return await super().http_add(request) - - @request_mapping(path="/{id:\d+}", method="PUT", auth_required=False) - async def http_update(self, request): - """ - --- - description: Update a Dashboard - tags: - - Dashboard - parameters: - - name: "id" - in: "path" - description: "Dashboard ID" - required: true - type: "integer" - format: "int64" - - in: body - name: body - description: Update a dashboard - required: false - schema: - type: object - properties: - name: - type: string - responses: - "200": - description: successful operation - """ - return await super().http_update(request) - - @request_mapping(path="/{id:\d+}", method="DELETE", auth_required=False) - async def http_delete_one(self, request): - """ - --- - description: Delete a Dashboard - tags: - - Dashboard - parameters: - - name: "id" - in: "path" - description: "Dashboard ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - id = request.match_info['id'] - await self.cbpi.dashboard.delete_dashboard(id) - return web.Response(status=204) @request_mapping(path="/{id:\d+}/content", auth_required=False) async def get_content(self, request): @@ -141,8 +35,6 @@ class DashBoardHttpEndpoints(HttpCrudEndpoints): description: successful operation """ dashboard_id = int(request.match_info['id']) - - return web.json_response(await self.cbpi.dashboard.get_content(dashboard_id), dumps=json_dumps) diff --git a/cbpi/http_endpoints/http_kettle.py b/cbpi/http_endpoints/http_kettle.py index feaf900..a31d414 100644 --- a/cbpi/http_endpoints/http_kettle.py +++ b/cbpi/http_endpoints/http_kettle.py @@ -1,34 +1,229 @@ from aiohttp import web from cbpi.api import * -from cbpi.http_endpoints.http_curd_endpoints import HttpCrudEndpoints auth = False +class KettleHttpEndpoints(): -class KettleHttpEndpoints(HttpCrudEndpoints): - - @request_mapping(path="/types", auth_required=False) - async def get_types(self, request): - return await super().get_types(request) + def __init__(self, cbpi): + self.cbpi = cbpi + self.controller = cbpi.kettle + self.cbpi.register(self, "/kettle") @request_mapping(path="/", auth_required=False) async def http_get_all(self, request): """ + --- - description: Get all kettles + description: Switch actor on tags: - Kettle responses: "204": description: successful operation """ - return await super().http_get_all(request) + return web.json_response(data=self.controller.get_state()) + - @request_mapping(path="/{id:\d+}", auth_required=False) - async def http_get_one(self, request): + @request_mapping(path="/", method="POST", auth_required=False) + async def http_add(self, request): """ --- - description: Get Kettle by Id + description: add one Actor + tags: + - Kettle + parameters: + - in: body + name: body + description: Created an actor + required: true + + schema: + type: object + + properties: + name: + type: string + sensor: + type: "integer" + format: "int64" + heater: + type: "integer" + format: "int64" + agitator: + type: "integer" + format: "int64" + target_temp: + type: "integer" + format: "int64" + type: + type: string + props: + type: object + example: + name: "Kettle 1" + type: "CustomKettleLogic" + props: {} + + responses: + "204": + description: successful operation + """ + data = await request.json() + response_data = await self.controller.add(data) + + return web.json_response(data=self.controller.create_dict(response_data)) + + + @request_mapping(path="/{id}", method="PUT", auth_required=False) + async def http_update(self, request): + """ + --- + description: Update an actor + tags: + - Kettle + parameters: + - name: "id" + in: "path" + description: "Actor ID" + required: true + type: "integer" + format: "int64" + - in: body + name: body + description: Update an actor + required: false + schema: + type: object + properties: + name: + type: string + type: + type: string + config: + props: object + responses: + "200": + description: successful operation + """ + id = request.match_info['id'] + data = await request.json() + return web.json_response(data=self.controller.create_dict(await self.controller.update(id, data))) + + @request_mapping(path="/{id}", method="DELETE", auth_required=False) + async def http_delete_one(self, request): + """ + --- + description: Delete an actor + tags: + - Kettle + parameters: + - name: "id" + in: "path" + description: "Actor ID" + required: true + type: "string" + responses: + "204": + description: successful operation + """ + id = request.match_info['id'] + await self.controller.delete(id) + return web.Response(status=204) + + @request_mapping(path="/{id}/on", method="POST", auth_required=False) + async def http_on(self, request) -> web.Response: + """ + + --- + description: Switch actor on + tags: + - Kettle + parameters: + - name: "id" + in: "path" + description: "Actor ID" + required: true + type: "string" + + responses: + "204": + description: successful operation + "405": + description: invalid HTTP Met + """ + id = request.match_info['id'] + await self.controller.start(id) + return web.Response(status=204) + + @request_mapping(path="/{id}/off", method="POST", auth_required=False) + async def http_off(self, request) -> web.Response: + """ + + --- + description: Switch actor on + tags: + - Kettle + + parameters: + - name: "id" + in: "path" + description: "Actor ID" + required: true + type: "string" + + responses: + "204": + description: successful operation + "405": + description: invalid HTTP Met + """ + id = request.match_info['id'] + await self.controller.off(id) + return web.Response(status=204) + + + @request_mapping(path="/{id}/action", method="POST", auth_required=auth) + async def http_action(self, request) -> web.Response: + """ + + --- + description: Toogle an actor on or off + tags: + - Kettle + parameters: + - name: "id" + in: "path" + description: "Actor ID" + required: true + type: "integer" + format: "int64" + - in: body + name: body + description: Update an actor + required: false + schema: + type: object + properties: + name: + type: string + parameter: + type: object + responses: + "204": + description: successful operation + """ + actor_id = request.match_info['id'] + data = await request.json() + await self.controller.call_action(actor_id, data.get("name"), data.get("parameter")) + + return web.Response(status=204) + @request_mapping(path="/{id}/target_temp", method="POST", auth_required=auth) + async def http_target(self, request) -> web.Response: + """ + + --- + description: Toogle an actor on or off tags: - Kettle parameters: @@ -42,287 +237,7 @@ class KettleHttpEndpoints(HttpCrudEndpoints): "204": description: successful operation """ - return await super().http_get_one(request) - - @request_mapping(path="/", method="POST", auth_required=False) - async def http_add(self, request): - """ - --- - description: add a kettle - tags: - - Kettle - parameters: - - in: body - name: body - description: Created an kettle - required: false - schema: - type: object - properties: - name: - type: string - sensor: - type: "integer" - format: "int64" - heater: - type: "integer" - format: "int64" - agitator: - type: "integer" - format: "int64" - target_temp: - type: "integer" - format: "int64" - logic: - type: string - config: - type: object - responses: - "204": - description: successful operation - """ - return await super().http_add(request) - - @request_mapping(path="/{id}", method="PUT", auth_required=False) - async def http_update(self, request): - """ - --- - description: Update a kettle - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "Kettle ID" - required: true - type: "integer" - format: "int64" - - in: body - name: body - description: Created an kettle - required: false - schema: - type: object - properties: - name: - type: string - sensor: - type: "integer" - format: "int64" - heater: - type: "integer" - format: "int64" - agitator: - type: "integer" - format: "int64" - target_temp: - type: "integer" - format: "int64" - logic: - type: string - config: - type: object - responses: - "204": - description: successful operation - """ - return await super().http_update(request) - - @request_mapping(path="/{id}", method="DELETE", auth_required=False) - async def http_delete_one(self, request): - """ - --- - description: Delete a kettle - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "kettle ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - return await super().http_delete_one(request) - - def __init__(self, cbpi): - super().__init__(cbpi) - self.controller = cbpi.kettle - self.cbpi.register(self, "/kettle") - - - @request_mapping(path="/{id:\d+}/automatic", method="POST", auth_required=False) - async def http_automatic(self, request): - """ - --- - description: Toggle Automatic - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "Kettle ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - await self.controller.toggle_automtic(int(request.match_info['id'])) - return web.Response(status=204) - - @request_mapping(path="/{id:\d+}/heater/on", auth_required=False) - async def http_heater_on(self, request): - """ - --- - description: Kettle Heater on - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "Kettle ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - await self.controller.heater_on(int(request.match_info['id'])) - return web.Response(status=204) - - @request_mapping(path="/{id:\d+}/heater/off", auth_required=False) - async def http_heater_off(self, request): - """ - --- - description: Kettle Heater off - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "Kettle ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - await self.controller.heater_off(int(request.match_info['id'])) - return web.Response(status=204) - - @request_mapping(path="/{id:\d+}/agitator/on", auth_required=False) - async def http_agitator_on(self, request): - """ - --- - description: Kettle Agitator on - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "Kettle ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - await self.controller.agitator_on(int(request.match_info['id'])) - return web.Response(status=204) - - @request_mapping(path="/{id:\d+}/agitator/off", auth_required=False) - async def http_agitator_off(self, request): - """ - --- - description: Kettle Agitator off - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "Kettle ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - await self.controller.agitator_off(int(request.match_info['id'])) - return web.Response(status=204) - - @request_mapping(path="/{id:\d+}/targettemp", auth_required=False) - async def http_taget_temp(self, request): - """ - --- - description: Get Target Temp of kettle - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "Kettle ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - kettle_id = int(request.match_info['id']) - temp = await self.controller.get_traget_temp(kettle_id) - return web.json_response(data=dict(target_temp=temp, kettle_id=kettle_id)) - - @request_mapping(path="/{id:\d+}/temp/{temp:\d+}", method="PUT", auth_required=False) - async def http_set_taget_temp(self, request): - """ - --- - description: Get Target Temp of kettle - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "Kettle ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - kettle_id = int(request.match_info['id']) - target_temp = int(request.match_info['temp']) - await self.cbpi.bus.fire(topic="kettle/%s/targettemp" % kettle_id, kettle_id=kettle_id, target_temp=target_temp) - return web.Response(status=204) - - @request_mapping(path="/{id:\d+}/temp", auth_required=False) - async def http_temp(self, request): - """ - --- - description: Get Temp of kettle - tags: - - Kettle - parameters: - - name: "id" - in: "path" - description: "Kettle ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - kettle_id = int(request.match_info['id']) - temp = await self.controller.get_temp(kettle_id) - - return web.Response(status=204) + id = request.match_info['id'] + #data = await request.json() + await self.controller.set_target_temp(id,999) + return web.Response(status=204) \ No newline at end of file diff --git a/cbpi/http_endpoints/http_sensor.py b/cbpi/http_endpoints/http_sensor.py index 76705f1..ac1d7d9 100644 --- a/cbpi/http_endpoints/http_sensor.py +++ b/cbpi/http_endpoints/http_sensor.py @@ -1,186 +1,72 @@ -import asyncio -import os - from aiohttp import web +from cbpi.api import * +auth = False -from cbpi.api import request_mapping - -from cbpi.http_endpoints.http_curd_endpoints import HttpCrudEndpoints - - -class SensorHttpEndpoints(HttpCrudEndpoints): +class SensorHttpEndpoints(): def __init__(self, cbpi): - super().__init__(cbpi) + self.cbpi = cbpi self.controller = cbpi.sensor self.cbpi.register(self, "/sensor") - @request_mapping(path="/types", auth_required=False) - async def get_types(self, request): - """ - --- - description: Get all sensor types - tags: - - Sensor - responses: - "200": - description: successful operation - """ - return await super().get_types(request) - @request_mapping(path="/", auth_required=False) async def http_get_all(self, request): """ --- - description: Get all sensor + description: Switch actor on tags: - Sensor responses: "204": description: successful operation """ - return await super().http_get_all(request) - - @request_mapping(path="/{id:\d+}", auth_required=False) - async def http_get_one(self, request): - """ - --- - description: Get an sensor - tags: - - Sensor - parameters: - - name: "id" - in: "path" - description: "Sensor ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - "405": - description: invalid HTTP Met - """ - return await super().http_get_one(request) + return web.json_response(data=self.controller.get_state()) + @request_mapping(path="/", method="POST", auth_required=False) async def http_add(self, request): """ --- - description: Get one sensor + description: add one Actor tags: - Sensor parameters: - in: body name: body - description: Created an sensor - required: false + description: Created an actor + required: true + schema: type: object + properties: name: type: string type: type: string - config: + props: type: object + example: + name: "Actor 1" + type: "CustomActor" + props: {} + responses: "204": description: successful operation """ - return await super().http_add(request) + data = await request.json() + response_data = await self.controller.add(data) + + return web.json_response(data=self.controller.create_dict(response_data)) + @request_mapping(path="/{id}", method="PUT", auth_required=False) async def http_update(self, request): """ --- - description: Update an sensor - tags: - - Sensor - parameters: - - name: "id" - in: "path" - description: "Sensor ID" - required: true - type: "integer" - format: "int64" - - in: body - name: body - description: Update an sensor - required: false - schema: - type: object - properties: - name: - type: string - type: - type: string - config: - type: object - responses: - "200": - description: successful operation - """ - return await super().http_update(request) - - @request_mapping(path="/{id}", method="DELETE", auth_required=False) - async def http_delete_one(self, request): - """ - --- - description: Delete an sensor - tags: - - Sensor - parameters: - - name: "id" - in: "path" - description: "Sensor ID" - required: true - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - return await super().http_delete_one(request) - - - - - @request_mapping(path="/{id:\d+}/log", auth_required=False) - async def http_get_log(self, request): - sensor_id = request.match_info['id'] - resp = web.StreamResponse(status=200, reason='OK', headers={'Content-Type': 'text/html'}) - - await resp.prepare(request) - for filename in sorted(os.listdir("./logs/sensors"), reverse=True): - if filename.startswith("sensor_%s" % sensor_id): - - with open(os.path.join("./logs/sensors/%s" % filename), 'r') as myfile: - await resp.write(str.encode(myfile.read())) - return resp - - @request_mapping(path="/{id:\d+}/log", method="DELETE", auth_required=False) - async def http_clear_log(self, request): - sensor_id = request.match_info['id'] - - for filename in sorted(os.listdir("./logs/sensors"), reverse=True): - - if filename == "sensor_%s.log" % sensor_id: - with open(os.path.join("./logs/sensors/%s" % filename), 'w'): - pass - continue - if filename.startswith("sensor_%s" % sensor_id): - os.remove(os.path.join("./logs/sensors/%s" % filename)) - - return web.Response(status=204) - - @request_mapping(path="/{id:\d+}/action", method="POST", auth_required=False) - async def http_action(self, request) -> web.Response: - """ - - --- - description: Execute action on sensor + description: Update an actor tags: - Sensor parameters: @@ -199,13 +85,122 @@ class SensorHttpEndpoints(HttpCrudEndpoints): properties: name: type: string + type: + type: string config: + props: object + responses: + "200": + description: successful operation + """ + id = request.match_info['id'] + data = await request.json() + return web.json_response(data=self.controller.create_dict(await self.controller.update(id, data))) + + @request_mapping(path="/{id}", method="DELETE", auth_required=False) + async def http_delete_one(self, request): + """ + --- + description: Delete an actor + tags: + - Sensor + parameters: + - name: "id" + in: "path" + description: "Actor ID" + required: true + type: "string" + responses: + "204": + description: successful operation + """ + id = request.match_info['id'] + await self.controller.delete(id) + return web.Response(status=204) + + @request_mapping(path="/{id}/on", method="POST", auth_required=False) + async def http_on(self, request) -> web.Response: + """ + + --- + description: Switch actor on + tags: + - Sensor + parameters: + - name: "id" + in: "path" + description: "Actor ID" + required: true + type: "string" + + responses: + "204": + description: successful operation + "405": + description: invalid HTTP Met + """ + id = request.match_info['id'] + await self.controller.on(id) + return web.Response(status=204) + + @request_mapping(path="/{id}/off", method="POST", auth_required=False) + async def http_off(self, request) -> web.Response: + """ + + --- + description: Switch actor on + tags: + - Sensor + parameters: + - name: "id" + in: "path" + description: "Actor ID" + required: true + type: "string" + + responses: + "204": + description: successful operation + "405": + description: invalid HTTP Met + """ + id = request.match_info['id'] + await self.controller.off(id) + return web.Response(status=204) + + + @request_mapping(path="/{id}/action", method="POST", auth_required=auth) + async def http_action(self, request) -> web.Response: + """ + + --- + description: Toogle an actor on or off + tags: + - Sensor + parameters: + - name: "id" + in: "path" + description: "Actor ID" + required: true + type: "integer" + format: "int64" + - in: body + name: body + description: Update an actor + required: false + schema: + type: object + properties: + name: + type: string + parameter: type: object responses: "204": description: successful operation """ - sensor_id = int(request.match_info['id']) + actor_id = request.match_info['id'] + data = await request.json() + await self.controller.call_action(actor_id, data.get("name"), data.get("parameter")) - await self.cbpi.bus.fire(topic="sensor/%s/action" % sensor_id, sensor_id=sensor_id, data=await request.json()) return web.Response(status=204) \ No newline at end of file diff --git a/cbpi/http_endpoints/http_step.py b/cbpi/http_endpoints/http_step.py index 84dcd4e..b3c4673 100644 --- a/cbpi/http_endpoints/http_step.py +++ b/cbpi/http_endpoints/http_step.py @@ -1,257 +1,211 @@ from aiohttp import web from cbpi.api import * - -from cbpi.http_endpoints.http_curd_endpoints import HttpCrudEndpoints - - -class StepHttpEndpoints(HttpCrudEndpoints): +class StepHttpEndpoints(): def __init__(self, cbpi): - super().__init__(cbpi) + self.cbpi = cbpi self.controller = cbpi.step - self.cbpi.register(self, "/step") + self.cbpi.register(self, "/step2") + + def create_dict(self, data): + return dict(name=data["name"], id=data["id"], type=data.get("type"), status=data["status"],props=data["props"], state_text=data["instance"].get_state()) - @request_mapping(path="/types", auth_required=False) - async def get_types(self, request): + @request_mapping(path="/", auth_required=False) + async def http_get_all(self, request): + """ --- - description: Get all step types + description: Get all steps tags: - Step responses: "200": description: successful operation """ - return await super().get_types(request) - - @request_mapping(path="/", auth_required=False) - async def http_get_all(self, request): - - """ - - --- - description: Get all steps - tags: - - Step - responses: - "204": - description: successful operation - """ - return await super().http_get_all(request) - - @request_mapping(path="/{id:\d+}", auth_required=False) - async def http_get_one(self, request): - """ - --- - description: Get one step - 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_one(request) - + return web.json_response(data=self.controller.get_state()) + @request_mapping(path="/", method="POST", auth_required=False) async def http_add(self, request): + """ + --- - description: Get one step + description: Add tags: - Step parameters: - in: body name: body description: Created an step - required: false - schema: - type: object - properties: - name: - type: string - type: - type: string - config: - type: object - responses: - "204": - description: successful operation - """ - return await super().http_add(request) - - @request_mapping(path="/{id}", method="PUT", auth_required=False) - async def http_update(self, request): - """ - --- - description: Update an step - tags: - - Step - parameters: - - name: "id" - in: "path" - description: "step ID" required: true - type: "integer" - format: "int64" - - in: body - name: body - description: Update an step - required: false schema: type: object - properties: - name: - type: string - type: - type: string - config: - type: object responses: "200": description: successful operation """ - return await super().http_update(request) - @request_mapping(path="/{id}", method="DELETE", auth_required=False) - async def http_delete_one(self, request): + data = await request.json() + result = await self.controller.add(data) + return web.json_response(self.create_dict(result)) + + @request_mapping(path="/{id}", method="PUT", auth_required=False) + async def http_update(self, request): + """ --- - description: Delete a step + description: Update tags: - Step parameters: - - name: "id" - in: "path" - description: "Step ID" - required: true - type: "integer" - format: "int64" + - in: body + name: body + description: Created an kettle + required: false + schema: + type: object responses: - "204": + "200": description: successful operation """ - return await super().http_delete_one(request) - - @request_mapping(path="/", method="DELETE", auth_required=False) - async def http_delete_all(self, request): - """ - --- - description: Delete all step - tags: - - Step - responses: - "204": - description: successful operation - """ - - await self.cbpi.bus.fire("step/clear") - return web.Response(status=204) - - - @request_mapping(path="/action", method="POST", auth_required=False, json_schema={"action": str, "parameter": dict}) - async def http_action(self, request): - """ - --- - description: Call Step Action - tags: - - Step - parameters: - - in: body - name: body - description: Step Action - required: true - schema: - type: object - properties: - action: - type: string - parameter: - type: object - produces: - - application/json - responses: - "204": - description: successful operation - "405": - description: invalid HTTP Method - """ + data = await request.json() - await self.cbpi.bus.fire("step/action", name=data["action"], parameter=data["parameter"]) - return web.Response(text="OK") + id = request.match_info['id'] + result = await self.controller.update(id, data) + print("RESULT", result) + return web.json_response(self.create_dict(result)) - @request_mapping(path="/start", auth_required=False) - async def http_start(self, request): + @request_mapping(path="/{id}", method="DELETE", auth_required=False) + async def http_delete(self, request): """ + --- - description: Start Brewing Process + description: Delete tags: - Step responses: "204": description: successful operation """ - if self.controller.is_running(): - raise CBPiException("Brewing Process Already Running") - print("FIRE START FROM HTTP") - await self.cbpi.bus.fire("step/start") + id = request.match_info['id'] + await self.controller.delete(id) return web.Response(status=204) - - @request_mapping(path="/reset", auth_required=False) - async def http_reset(self, request): - """ - --- - description: Reset Brewing Process - tags: - - Step - responses: - "204": - description: successful operation - """ - await self.cbpi.bus.fire("step/reset") - return web.Response(text="OK") - - @request_mapping(path="/next", auth_required=False) + @request_mapping(path="/next", method="POST", auth_required=False) async def http_next(self, request): """ + --- - description: Start next step + description: Next tags: - Step responses: "204": description: successful operation """ - await self.cbpi.bus.fire("step/next") + + await self.controller.next() return web.Response(status=204) - @request_mapping(path="/stop", auth_required=False) - async def http_stop(self, request): + + @request_mapping(path="/move", method="PUT", auth_required=False) + async def http_move(self, request): + """ --- - description: Stop next step + description: Move tags: - Step + parameters: + - in: body + name: body + description: Created an kettle + required: false + schema: + type: object + properties: + id: + type: string + direction: + type: "integer" + format: "int64" responses: "204": description: successful operation """ - await self.cbpi.bus.fire("step/stop") - return web.Response(status=204) - - @request_mapping(path="/sort", method="POST", auth_required=False) - async def http_sort(self, request): data = await request.json() - await self.cbpi.bus.fire("step/sort", data=data) - return web.Response(status=204) \ No newline at end of file + await self.controller.move(data["id"], data["direction"]) + return web.Response(status=204) + + @request_mapping(path="/start", method="POST", auth_required=False) + async def http_start(self, request): + + """ + --- + description: Move + tags: + - Step + responses: + "204": + description: successful operation + """ + + await self.controller.start() + return web.Response(status=204) + + @request_mapping(path="/stop", method="POST", auth_required=False) + async def http_stop(self, request): + + """ + + --- + description: Stop Step + tags: + - Step + responses: + "204": + description: successful operation + """ + + await self.controller.stop() + return web.Response(status=204) + + + @request_mapping(path="/reset", method="POST", auth_required=False) + async def http_reset(self, request): + + """ + + --- + description: Move + tags: + - Step + responses: + "204": + description: successful operation + """ + + await self.controller.reset_all() + + return web.Response(status=204) + + @request_mapping(path="/basic", method="PUT", auth_required=False) + async def http_save_basic(self, request): + + """ + + --- + description: Move + tags: + - Step + responses: + "204": + description: successful operation + """ + data = await request.json() + await self.controller.save_basic(data) + + return web.Response(status=204) + \ No newline at end of file diff --git a/cbpi/http_endpoints/http_step2.py b/cbpi/http_endpoints/http_step2.py deleted file mode 100644 index 78b8b59..0000000 --- a/cbpi/http_endpoints/http_step2.py +++ /dev/null @@ -1,200 +0,0 @@ -from aiohttp import web -from cbpi.api import * - - -from cbpi.http_endpoints.http_curd_endpoints import HttpCrudEndpoints - - -class StepHttpEndpoints2(): - - def __init__(self, cbpi): - self.cbpi = cbpi - self.controller = cbpi.step2 - self.cbpi.register(self, "/step2") - - def create_dict(self, data): - return dict(name=data["name"], id=data["id"], type=data.get("type"), status=data["status"],props=data["props"], state_text=data["instance"].get_state()) - - - @request_mapping(path="/", auth_required=False) - async def http_get_all(self, request): - - """ - --- - description: Get all steps - tags: - - Step2 - responses: - "200": - description: successful operation - """ - return web.json_response(data=self.controller.get_state()) - - @request_mapping(path="/", method="POST", auth_required=False) - async def http_add(self, request): - - """ - - --- - description: Add - tags: - - Step2 - parameters: - - in: body - name: body - description: Created an step - required: true - schema: - type: object - responses: - "200": - description: successful operation - """ - - data = await request.json() - result = await self.controller.add(data) - print("RESULT", result) - return web.json_response(self.create_dict(result)) - - @request_mapping(path="/{id}", method="PUT", auth_required=False) - async def http_update(self, request): - - """ - --- - description: Update - tags: - - Step2 - parameters: - - in: body - name: body - description: Created an kettle - required: false - schema: - type: object - responses: - "200": - description: successful operation - """ - - data = await request.json() - id = request.match_info['id'] - result = await self.controller.update(id, data) - print("RESULT", result) - return web.json_response(self.create_dict(result)) - - @request_mapping(path="/{id}", method="DELETE", auth_required=False) - async def http_delete(self, request): - """ - - --- - description: Delete - tags: - - Step2 - responses: - "204": - description: successful operation - """ - id = request.match_info['id'] - await self.controller.delete(id) - return web.Response(status=204) - - @request_mapping(path="/next", method="POST", auth_required=False) - async def http_next(self, request): - """ - - --- - description: Next - tags: - - Step2 - responses: - "204": - description: successful operation - """ - - await self.controller.next() - return web.Response(status=204) - - - @request_mapping(path="/move", method="PUT", auth_required=False) - async def http_move(self, request): - - """ - --- - description: Move - tags: - - Step2 - parameters: - - in: body - name: body - description: Created an kettle - required: false - schema: - type: object - properties: - id: - type: string - direction: - type: "integer" - format: "int64" - responses: - "204": - description: successful operation - """ - data = await request.json() - print("MOVE", data) - await self.controller.move(data["id"], data["direction"]) - - return web.Response(status=204) - - @request_mapping(path="/start", method="POST", auth_required=False) - async def http_start(self, request): - - """ - --- - description: Move - tags: - - Step2 - responses: - "204": - description: successful operation - """ - - await self.controller.start() - return web.Response(status=204) - - @request_mapping(path="/stop", method="POST", auth_required=False) - async def http_stop(self, request): - - """ - - --- - description: Stop Step - tags: - - Step2 - responses: - "204": - description: successful operation - """ - - await self.controller.stop() - return web.Response(status=204) - - - @request_mapping(path="/reset", method="POST", auth_required=False) - async def http_reset(self, request): - - """ - - --- - description: Move - tags: - - Step2 - responses: - "204": - description: successful operation - """ - print("RESE HTTP") - await self.controller.reset_all() - - return web.Response(status=204) - \ No newline at end of file diff --git a/cbpi/http_endpoints/http_system.py b/cbpi/http_endpoints/http_system.py index 1bd3180..e7b31ea 100644 --- a/cbpi/http_endpoints/http_system.py +++ b/cbpi/http_endpoints/http_system.py @@ -26,9 +26,7 @@ class SystemHttpEndpoints: actor=self.cbpi.actor.get_state(), sensor=self.cbpi.sensor.get_state(), kettle=self.cbpi.kettle.get_state(), - step=self.cbpi.step2.get_state(), - dashboard=self.cbpi.dashboard.get_state(), - translations=self.cbpi.translation.get_all(), + step=self.cbpi.step.get_state(), config=self.cbpi.config.get_state()) , dumps=json_dumps) diff --git a/cbpi/http_endpoints/http_translation.py b/cbpi/http_endpoints/http_translation.py deleted file mode 100644 index da4ff1f..0000000 --- a/cbpi/http_endpoints/http_translation.py +++ /dev/null @@ -1,54 +0,0 @@ -from aiohttp import web -from aiohttp_auth import auth - -from cbpi.api import * - - -class TranslationHttpEndpoint(): - - def __init__(self,cbpi): - self.cbpi = cbpi - self.cbpi.register(self, url_prefix="/translation") - - - @request_mapping(path="/missing_key", method="POST", auth_required=False) - async def missing_key(self, request): - """ - --- - description: Add missing translation key - tags: - - Translation - parameters: - - in: body - name: body - description: missing key data - required: true - schema: - type: object - properties: - locale: - type: string - key: - type: string - responses: - "204": - description: successful operation - """ - - data = await request.json() - await self.cbpi.translation.add_key(**data) - return web.Response(status=204) - - @request_mapping(path="/", auth_required=False) - async def http_get_all(self, request): - """ - --- - description: Get all translations - tags: - - Translation - responses: - "200": - description: successful operation - """ - - return web.json_response(data=self.cbpi.translation.get_all()) diff --git a/cheat_sheet.txt b/cheat_sheet.txt index 0be4da2..7a1664e 100644 --- a/cheat_sheet.txt +++ b/cheat_sheet.txt @@ -2,7 +2,7 @@ python3.7 setup.py clean --all #build -python setup.py sdist +python3 setup.py sdist #Upload twine upload dist/* diff --git a/config/actor.json b/config/actor.json new file mode 100644 index 0000000..9605966 --- /dev/null +++ b/config/actor.json @@ -0,0 +1,25 @@ +{ + "data": [ + { + "id": "9NkxVioA7FfZi6waegi246", + "name": "Manuel", + "props": {}, + "state": {}, + "type": "CustomActor" + }, + { + "id": "5hk68r3pFBe6JoRXzavLCA", + "name": "Actor 1", + "props": {}, + "state": {}, + "type": "CustomActor" + }, + { + "id": "YgVFNsXncfMMoHD7U6TvP6", + "name": "111", + "props": {}, + "state": {}, + "type": "CustomActor" + } + ] +} \ No newline at end of file diff --git a/config/dashboard/cbpi_dashboard_1.json b/config/dashboard/cbpi_dashboard_1.json index fb8c467..6447551 100644 --- a/config/dashboard/cbpi_dashboard_1.json +++ b/config/dashboard/cbpi_dashboard_1.json @@ -244,6 +244,36 @@ "type": "Text", "x": 615, "y": 280 + }, + { + "id": "3a9e422f-8d55-4360-8f16-807f9a657988", + "name": "Steps", + "props": { + "width": "400" + }, + "type": "Steps", + "x": 20, + "y": 430 + }, + { + "id": "3be00e94-4e06-4a6b-9b8d-c832be73386a", + "name": "Led", + "props": { + "actor": 1 + }, + "type": "Led", + "x": 440, + "y": 215 + }, + { + "id": "d896b230-8dab-4c33-b73a-1dd74e6de906", + "name": "Led", + "props": { + "actor": 1 + }, + "type": "Led", + "x": 625, + "y": 215 } ], "pathes": [ diff --git a/config/kettle.json b/config/kettle.json new file mode 100644 index 0000000..046d966 --- /dev/null +++ b/config/kettle.json @@ -0,0 +1,26 @@ +{ + "data": [ + { + "agitator": "9NkxVioA7FfZi6waegi246", + "heater": "9NkxVioA7FfZi6waegi246", + "id": "gJ6jCupRmpxRsweY9nANTp", + "name": "Kettle 233312312", + "props": {}, + "sensor": "TPpjzj9YXh6yYzvyJycmig", + "state": {}, + "target_temp": 22, + "type": "CustomKettleLogic" + }, + { + "agitator": "9NkxVioA7FfZi6waegi246", + "heater": "9NkxVioA7FfZi6waegi246", + "id": "RMjMvwphxt3aiMrTnHbpcB", + "name": "Test", + "props": {}, + "sensor": "TPpjzj9YXh6yYzvyJycmig", + "state": {}, + "target_temp": 22, + "type": "CustomKettleLogic" + } + ] +} \ No newline at end of file diff --git a/config/sensor.json b/config/sensor.json new file mode 100644 index 0000000..a311e6c --- /dev/null +++ b/config/sensor.json @@ -0,0 +1,21 @@ +{ + "data": [ + { + "id": "TPpjzj9YXh6yYzvyJycmig", + "name": "AMAZING22211111123123", + "props": { + "param1": "HALLO", + "param2": "Test" + }, + "status": null, + "type": "CustomSensor2" + }, + { + "id": "2rAviwweTUY27Y8yZKftWA", + "name": "Testasdfasdf", + "props": {}, + "status": null, + "type": "CustomSensor2" + } + ] +} \ No newline at end of file diff --git a/config/step_data.json b/config/step_data.json index 672898f..973f247 100644 --- a/config/step_data.json +++ b/config/step_data.json @@ -1,31 +1,56 @@ { - "basic": {}, + "basic": { + "name": "Weissbier" + }, "profile": [ { "id": "eopJy6oxGqrNuRNtiAPXvN", - "name": "AMAZING", + "name": "Step1", "props": { - "count": 5, + "Param1": "1", + "Param2": "HALLO", + "Param3": 1, + "count": 8, "wohoo": 0 }, "status": "P", "type": "CustomStep2" }, { - "id": "duxvgLknKLjGYhdm9TKqUE", - "name": "Manuel", + "id": "hyXYDBUAENgwD7yNwaeLe7", + "name": "Step2", "props": { - "count": 5, + "Param1": "123", + "Param2": "Parameter2", + "Param3": 2, + "count": 0, "wohoo": 0 }, "status": "I", "type": "CustomStep2" }, { - "id": "hyXYDBUAENgwD7yNwaeLe7", - "name": "HALLO", + "id": "iJHU9FgeGBtvDhraEHUoP2", + "name": "Step3", "props": { - "count": 5, + "Param1": 123, + "Param2": "HALLO", + "Param3": 2, + "Param5": 1, + "count": 0, + "wohoo": 0 + }, + "status": "I", + "type": "CustomStep2" + }, + { + "id": "duxvgLknKLjGYhdm9TKqUE", + "name": "Step4", + "props": { + "Param1": "1222", + "Param2": "HELLO", + "Param3": 2, + "count": 0, "wohoo": 0 }, "status": "I", diff --git a/craftbeerpi.db b/craftbeerpi.db index bae4f665c7056a63c3fa91ceb0bc75638bd45e95..93e1d661b3cab056dda1fad1035b9b6e010a8ecb 100644 GIT binary patch delta 64 zcmV-G0Kfl$paX!Q1CSd5rjZ;&0j9BFre6pQ01rVA+7Aq~5fGIQlcR2D2M?w*0|q4{ WlMinilPhm10Yj6$ZcVeHZ(m@Q0TjUi delta 65 zcmV-H0KWf#paX!Q1CSd5qLCa!0iv;Bre6pO01rVA+7Ak|5fGIQlcR2D1vLW(B`1>+ XZyEwIF_SBAC;>*3y>3mjp>JPcnNAdK diff --git a/data.txt b/data.txt deleted file mode 100644 index e2777c8..0000000 --- a/data.txt +++ /dev/null @@ -1 +0,0 @@ -{"people": [{"name": "Scott", "website": "stackabuse.com", "from": "Nebraska"}, {"name": "Larry", "website": "google.com", "from": "Michigan"}, {"name": "Tim", "website": "apple.com", "from": "Alabama"}]} \ No newline at end of file diff --git a/requirements2.txt b/requirements2.txt deleted file mode 100644 index ab246a2..0000000 --- a/requirements2.txt +++ /dev/null @@ -1,40 +0,0 @@ -aiohttp==3.7.3 -aiohttp-auth==0.1.1 -aiohttp-route-decorator==0.1.4 -aiohttp-security==0.4.0 -aiohttp-session==2.9.0 -aiohttp-swagger==1.0.15 -aiojobs==0.3.0 -aiosqlite==0.16.0 -asn1crypto==1.4.0 -astroid==2.4.2 -async-timeout==3.0.1 -attrs==20.3.0 -certifi==2020.12.5 -cffi==1.14.4 -chardet==3.0.4 -cryptography==3.3.1 -idna==2.10 -isort==5.7.0 -Jinja2==2.11.2 -lazy-object-proxy==1.4.3 -MarkupSafe==1.1.1 -mccabe==0.6.1 -multidict==4.7.6 -numpy==1.19.4 -pandas==1.2.0 -pycparser==2.20 -pyfiglet==0.8.post1 -pylint==2.6.0 -python-dateutil==2.8.1 -pytz==2020.5 -PyYAML==5.3.1 -requests==2.25.1 -six==1.15.0 -ticket-auth==0.1.4 -toml==0.10.2 -typing-extensions==3.7.4.3 -urllib3==1.26.2 -voluptuous==0.12.1 -wrapt==1.12.1 -yarl==1.6.3 diff --git a/samples.txt b/samples.txt deleted file mode 100644 index 79fd3e8..0000000 --- a/samples.txt +++ /dev/null @@ -1,51 +0,0 @@ -function noop() {} - -export default function (url, opts) { - opts = opts || {}; - - var ws, num=0, $={}; - var max = opts.maxAttempts || Infinity; - - $.open = function () { - ws = new WebSocket(url, opts.protocols || []); - - ws.onmessage = opts.onmessage || noop; - - ws.onopen = function (e) { - (opts.onopen || noop)(e); - num = 0; - }; - - ws.onclose = function (e) { - e.code === 1e3 || e.code === 1005 || $.reconnect(e); - (opts.onclose || noop)(e); - }; - - ws.onerror = function (e) { - (e && e.code==='ECONNREFUSED') ? $.reconnect(e) : (opts.onerror || noop)(e); - }; - }; - - $.reconnect = function (e) { - (num++ < max) ? setTimeout(function () { - (opts.onreconnect || noop)(e); - $.open(); - }, opts.timeout || 1e3) : (opts.onmaximum || noop)(e); - }; - - $.json = function (x) { - ws.send(JSON.stringify(x)); - }; - - $.send = function (x) { - ws.send(x); - }; - - $.close = function (x, y) { - ws.close(x || 1e3, y); - }; - - $.open(); // init - - return $; -} \ No newline at end of file diff --git a/sampletest.py b/sampletest.py deleted file mode 100644 index e69de29..0000000 diff --git a/setup.py b/setup.py index 2499821..0946b45 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,8 @@ from setuptools import setup, find_packages +from cbpi import __version__ setup(name='cbpi', - version='4.0.0.5', + version=__version__, description='CraftBeerPi', author='Manuel Fritsch', author_email='manuel@craftbeerpi.com', @@ -14,19 +15,21 @@ setup(name='cbpi', 'cbpi': ['*','*.txt', '*.rst', '*.yaml']}, install_requires=[ - "aiohttp==3.4.4", + "aiohttp==3.7.3", "aiohttp-auth==0.1.1", "aiohttp-route-decorator==0.1.4", "aiohttp-security==0.4.0", - "aiohttp-session==2.7.0", - "aiohttp-swagger==1.0.5", - "aiojobs==0.2.2", - "aiosqlite==0.7.0", - "cryptography==2.3.1", - "requests==2.22.0", - "voluptuous==0.11.5", - "pyfiglet==0.7.6", - 'pandas==0.25.0' + "aiohttp-session==2.9.0", + "aiohttp-swagger==1.0.15", + "aiojobs==0.3.0", + "aiosqlite==0.16.0", + "cryptography==3.3.1", + "requests==2.25.1", + "voluptuous==0.12.1", + "pyfiglet==0.8.post1", + 'pandas==1.2.0', + 'shortuuid==1.0.1', + 'tabulate==0.8.7' ], dependency_links=[ 'https://testpypi.python.org/pypi'