From 7fa738d240d34d52b5b5b4a872dfd140c3b5c825 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 1 Apr 2023 14:13:04 +0200 Subject: [PATCH 01/34] test --- cbpi/__init__.py | 2 +- cbpi/api/dataclasses.py | 3 ++- cbpi/controller/config_controller.py | 9 +++------ cbpi/controller/plugin_controller.py | 24 ++++++++++++++++++++++++ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index be197c8..7b27d51 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.7" +__version__ = "4.1.8.a1" __codename__ = "Groundhog Day" diff --git a/cbpi/api/dataclasses.py b/cbpi/api/dataclasses.py index 5007a64..c840f5c 100644 --- a/cbpi/api/dataclasses.py +++ b/cbpi/api/dataclasses.py @@ -199,11 +199,12 @@ class Config: description: str = None type: ConfigType = ConfigType.STRING options: Any = None + source: str = None def __str__(self): return "....name={} value={}".format(self.name, self.value) def to_dict(self): - return dict(name=self.name, value=self.value, type=self.type.value, description=self.description, options=self.options) + return dict(name=self.name, value=self.value, type=self.type.value, description=self.description, options=self.options, source=self.source) @dataclass class NotificationAction: diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index 6d62d79..2159706 100644 --- a/cbpi/controller/config_controller.py +++ b/cbpi/controller/config_controller.py @@ -19,21 +19,18 @@ class ConfigController: self.path_static = cbpi.config_folder.get_file_path("config.yaml") self.logger.info("Config folder path : " + os.path.join(Path(self.cbpi.config_folder.configFolderPath).absolute())) - def get_state(self): - + def get_state(self): result = {} for key, value in self.cache.items(): result[key] = value.to_dict() - - return result - + return result async def init(self): self.static = load_config(self.path_static) with open(self.path) as json_file: data = json.load(json_file) for key, value in data.items(): - self.cache[key] = Config(name=value.get("name"), value=value.get("value"), description=value.get("description"), type=ConfigType(value.get("type", "string")), options=value.get("options", None) ) + self.cache[key] = Config(name=value.get("name"), value=value.get("value"), description=value.get("description"), type=ConfigType(value.get("type", "string")), options=value.get("options", None), source=value.get("source", "craftbeerpi") ) def get(self, name, default=None): self.logger.debug("GET CONFIG VALUE %s (default %s)" % (name, default)) diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index e958371..23da582 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -205,6 +205,7 @@ class PluginController(): from importlib.metadata import (distribution, metadata, version) meta = metadata(key) + logging.warning(key) result.append({row: meta[row] for row in list(metadata(key))}) except Exception as e: @@ -215,3 +216,26 @@ class PluginController(): logger.error(e) return [] return result + + async def load_plugin_names(self, filter="cbpi"): + result = [] + result.append(dict(Name="craftbeerpi")) + try: + discovered_plugins = { + name: importlib.import_module(name) + for finder, name, ispkg + in pkgutil.iter_modules() + if name.startswith('cbpi') and len(name) > 4 + } + for key, module in discovered_plugins.items(): + try: + meta = metadata(key) + result.append(dict(Name=meta["Name"])) + + except Exception as e: + logger.error("FAILED to load plugin {} ".format(key)) + logger.error(e) + except Exception as e: + logger.error(e) + return result + return result From a70c63edf4d15f94719fde5943985ffc4447db50 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 1 Apr 2023 14:18:44 +0200 Subject: [PATCH 02/34] add http api to retreive only plugin names --- cbpi/controller/plugin_controller.py | 3 ++- cbpi/http_endpoints/http_plugin.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index 23da582..6ac1f00 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -230,7 +230,8 @@ class PluginController(): for key, module in discovered_plugins.items(): try: meta = metadata(key) - result.append(dict(Name=meta["Name"])) + if meta["Name"] != "cbpi4gui": + result.append(dict(Name=meta["Name"])) except Exception as e: logger.error("FAILED to load plugin {} ".format(key)) diff --git a/cbpi/http_endpoints/http_plugin.py b/cbpi/http_endpoints/http_plugin.py index fc3ae2c..9a36beb 100644 --- a/cbpi/http_endpoints/http_plugin.py +++ b/cbpi/http_endpoints/http_plugin.py @@ -86,3 +86,21 @@ class PluginHttpEndpoints: """ plugin_list = await self.cbpi.plugin.load_plugin_list() return web.json_response(plugin_list, dumps=json_dumps) + + @request_mapping(path="/names", method="GET", auth_required=False) + async def list(self, request): + """ + --- + description: Get a list of avialable plugin names + tags: + - Plugin + produces: + - application/json + responses: + "200": + description: successful operation. Return "pong" text + "405": + description: invalid HTTP Method + """ + plugin_names = await self.cbpi.plugin.load_plugin_names() + return web.json_response(plugin_names, dumps=json_dumps) From 5f3e3ea60d7cefadc32eea3079ad04e44e9a504e Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 2 Apr 2023 13:05:48 +0200 Subject: [PATCH 03/34] Add 'All' and 'steps' as categories --- cbpi/__init__.py | 2 +- cbpi/controller/plugin_controller.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 7b27d51..5b89767 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a1" +__version__ = "4.1.8.a2" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index 6ac1f00..5bfb8fd 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -219,7 +219,9 @@ class PluginController(): async def load_plugin_names(self, filter="cbpi"): result = [] - result.append(dict(Name="craftbeerpi")) + result.append(dict(label="All", value="All")) + result.append(dict(label="craftbeerpi", value="craftbeerpi")) + result.append(dict(label="steps", value="steps")) try: discovered_plugins = { name: importlib.import_module(name) @@ -231,7 +233,7 @@ class PluginController(): try: meta = metadata(key) if meta["Name"] != "cbpi4gui": - result.append(dict(Name=meta["Name"])) + result.append(dict(label=meta["Name"], value=meta["Name"])) except Exception as e: logger.error("FAILED to load plugin {} ".format(key)) From be77d90c7eb2ef93f34e313677eca8b3c458155a Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 2 Apr 2023 16:14:33 +0200 Subject: [PATCH 04/34] automatic config update test --- cbpi/__init__.py | 2 +- cbpi/api/base.py | 4 +- cbpi/api/dataclasses.py | 2 +- cbpi/config/config.json | 121 +------------- cbpi/controller/config_controller.py | 4 +- cbpi/extension/ConfigUpdate/__init__.py | 199 +++++++++++++++++------- 6 files changed, 151 insertions(+), 181 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 5b89767..5f83b3a 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a2" +__version__ = "4.1.8.a3" __codename__ = "Groundhog Day" diff --git a/cbpi/api/base.py b/cbpi/api/base.py index 837b4a2..9fd4692 100644 --- a/cbpi/api/base.py +++ b/cbpi/api/base.py @@ -17,8 +17,8 @@ class CBPiBase(metaclass=ABCMeta): async def set_config_value(self,name,value): return await self.cbpi.config.set(name,value) - async def add_config_value(self, name, value, type: ConfigType, description, options=None): - await self.cbpi.config.add(name, value, type, description, options=None) + async def add_config_value(self, name, value, type: ConfigType, description, source, options=None): + await self.cbpi.config.add(name, value, type, description, source,options=None) def get_kettle(self,id): return self.cbpi.kettle.find_by_id(id) diff --git a/cbpi/api/dataclasses.py b/cbpi/api/dataclasses.py index c840f5c..6885912 100644 --- a/cbpi/api/dataclasses.py +++ b/cbpi/api/dataclasses.py @@ -198,8 +198,8 @@ class Config: value: Any = None description: str = None type: ConfigType = ConfigType.STRING - options: Any = None source: str = None + options: Any = None def __str__(self): return "....name={} value={}".format(self.name, self.value) diff --git a/cbpi/config/config.json b/cbpi/config/config.json index 56f98c5..6359b99 100644 --- a/cbpi/config/config.json +++ b/cbpi/config/config.json @@ -4,6 +4,7 @@ "name": "AUTHOR", "options": null, "type": "string", + "source": "craftbeerpi", "value": "John Doe" }, "BREWERY_NAME": { @@ -11,52 +12,9 @@ "name": "BREWERY_NAME", "options": null, "type": "string", + "source": "craftbeerpi", "value": "CraftBeerPi Brewery" }, - "MASH_TUN": { - "description": "Default Mash Tun", - "name": "MASH_TUN", - "options": null, - "type": "kettle", - "value": "" - }, - "AddMashInStep": { - "description": "Add MashIn Step automatically if not defined in recipe", - "name": "AddMashInStep", - "options": [ - { - "label": "Yes", - "value": "Yes" - }, - { - "label": "No", - "value": "No" - } - ], - "type": "select", - "value": "Yes" - }, - "RECIPE_CREATION_PATH": { - "description": "API path to creation plugin. Default: empty", - "name": "RECIPE_CREATION_PATH", - "options": null, - "type": "string", - "value": "" - }, - "brewfather_api_key": { - "description": "Brewfather API Kay", - "name": "brewfather_api_key", - "options": null, - "type": "string", - "value": "" - }, - "brewfather_user_id": { - "description": "Brewfather User ID", - "name": "brewfather_user_id", - "options": null, - "type": "string", - "value": "" - }, "TEMP_UNIT": { "description": "Temperature Unit", "name": "TEMP_UNIT", @@ -71,78 +29,9 @@ } ], "type": "select", + "source": "craftbeerpi", "value": "C" - }, - "AutoMode": { - "description": "Use AutoMode in steps", - "name": "AutoMode", - "options": [ - { - "label": "Yes", - "value": "Yes" - }, - { - "label": "No", - "value": "No" - } - ], - "type": "select", - "value": "Yes" - }, - "steps_boil": { - "description": "Boil step type", - "name": "steps_boil", - "options": null, - "type": "step", - "value": "BoilStep" - }, - "steps_boil_temp": { - "description": "Default Boil Temperature for Recipe Creation", - "name": "steps_boil_temp", - "options": null, - "type": "number", - "value": "99" - }, - "steps_cooldown": { - "description": "Cooldown step type", - "name": "steps_cooldown", - "options": null, - "type": "step", - "value": "CooldownStep" - }, - "steps_cooldown_sensor": { - "description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)", - "name": "steps_cooldown_sensor", - "options": null, - "type": "sensor", - "value": "" - }, - "steps_cooldown_temp": { - "description": "Cooldown temp will send notification when this temeprature is reached", - "name": "steps_cooldown_temp", - "options": null, - "type": "number", - "value": "20" - }, - "steps_mash": { - "description": "Mash step type", - "name": "steps_mash", - "options": null, - "type": "step", - "value": "MashStep" - }, - "steps_mashin": { - "description": "MashIn step type", - "name": "steps_mashin", - "options": null, - "type": "step", - "value": "MashInStep" - }, - "steps_mashout": { - "description": "MashOut step type", - "name": "steps_mashout", - "options": null, - "type": "step", - "value": "NotificationStep" } + + } diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index 2159706..e0c5587 100644 --- a/cbpi/controller/config_controller.py +++ b/cbpi/controller/config_controller.py @@ -50,8 +50,8 @@ class ConfigController: with open(self.path, "w") as file: json.dump(data, file, indent=4, sort_keys=True) - async def add(self, name, value, type: ConfigType, description, options=None): - self.cache[name] = Config(name,value,description,type,options) + async def add(self, name, value, type: ConfigType, description, source="", options=None): + self.cache[name] = Config(name,value,description,type,source,options) data = {} for key, value in self.cache.items(): data[key] = value.to_dict() diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index 0aab218..a8c52ab 100644 --- a/cbpi/extension/ConfigUpdate/__init__.py +++ b/cbpi/extension/ConfigUpdate/__init__.py @@ -40,7 +40,6 @@ class ConfigUpdate(CBPiExtension): logfiles = self.cbpi.config.get("CSVLOGFILES", None) influxdb = self.cbpi.config.get("INFLUXDB", None) influxdbaddr = self.cbpi.config.get("INFLUXDBADDR", None) - #influxdbport = self.cbpi.config.get("INFLUXDBPORT", None) influxdbname = self.cbpi.config.get("INFLUXDBNAME", None) influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None) influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None) @@ -54,76 +53,112 @@ class ConfigUpdate(CBPiExtension): NOTIFY_ON_ERROR = self.cbpi.config.get("NOTIFY_ON_ERROR", None) PLAY_BUZZER = self.cbpi.config.get("PLAY_BUZZER", None) BoilAutoTimer = self.cbpi.config.get("BoilAutoTimer", None) - - + MASH_TUN = self.cbpi.config.get("MASH_TUN", None) + AutoMode = self.cbpi.config.get("AutoMode", None) + AddMashIn = self.cbpi.config.get("AddMashInStep", None) + bfuserid = self.cbpi.config.get("brewfather_user_id", None) + bfapikey = self.cbpi.config.get("brewfather_api_key", None) + RecipeCreationPath = self.cbpi.config.get("RECIPE_CREATION_PATH", None) + BoilKettle = self.cbpi.config.get("BoilKettle", None) + CONFIG_STATUS = self.cbpi.config.get("CONFIG_STATUS", None) + + if boil_temp is None: logger.info("INIT Boil Temp Setting") try: - await self.cbpi.config.add("steps_boil_temp", default_boil_temp, ConfigType.NUMBER, "Default Boil Temperature for Recipe Creation") + await self.cbpi.config.add("steps_boil_temp", default_boil_temp, type=ConfigType.NUMBER, description="Default Boil Temperature for Recipe Creation", source="steps") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("steps_boil_temp", boil_temp, type=ConfigType.NUMBER, description="Default Boil Temperature for Recipe Creation", source="steps") if cooldown_sensor is None: logger.info("INIT Cooldown Sensor Setting") try: - await self.cbpi.config.add("steps_cooldown_sensor", "", ConfigType.SENSOR, "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)") + await self.cbpi.config.add("steps_cooldown_sensor", "", type=ConfigType.SENSOR, description="Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)", source="steps") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("steps_cooldown_sensor", cooldown_sensor, type=ConfigType.SENSOR, description="Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)", source="steps") if cooldown_actor is None: logger.info("INIT Cooldown Actor Setting") try: - await self.cbpi.config.add("steps_cooldown_actor", "", ConfigType.ACTOR, "Actor to trigger cooldown water on and off (default: None)") + await self.cbpi.config.add("steps_cooldown_actor", "", type=ConfigType.ACTOR, description="Actor to trigger cooldown water on and off (default: None)", source="steps") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("steps_cooldown_actor", cooldown_actor, type=ConfigType.ACTOR, description="Actor to trigger cooldown water on and off (default: None)", source="steps") if cooldown_temp is None: logger.info("INIT Cooldown Temp Setting") try: - await self.cbpi.config.add("steps_cooldown_temp", default_cool_temp, ConfigType.NUMBER, "Cooldown temp will send notification when this temeprature is reached") + await self.cbpi.config.add("steps_cooldown_temp", default_cool_temp, type=ConfigType.NUMBER, description="Cooldown temp will send notification when this temeprature is reached", source="steps") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("steps_cooldown_temp", cooldown_temp, type=ConfigType.NUMBER, description="Cooldown temp will send notification when this temeprature is reached", source="steps") if cooldown_step is None: logger.info("INIT Cooldown Step Type") try: - await self.cbpi.config.add("steps_cooldown", "", ConfigType.STEP, "Cooldown step type") + await self.cbpi.config.add("steps_cooldown", "", type=ConfigType.STEP, description="Cooldown step type", source="steps") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("steps_cooldown", cooldown_step, type=ConfigType.STEP, description="Cooldown step type", source="steps") if mashin_step is None: logger.info("INIT MashIn Step Type") try: - await self.cbpi.config.add("steps_mashin", "", ConfigType.STEP, "MashIn step type") + await self.cbpi.config.add("steps_mashin", "", type=ConfigType.STEP, description="MashIn step type", source="steps") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("steps_mashin", mashin_step, type=ConfigType.STEP, description="MashIn step type", source="steps") if mash_step is None: logger.info("INIT Mash Step Type") try: - await self.cbpi.config.add("steps_mash", "", ConfigType.STEP, "Mash step type") + await self.cbpi.config.add("steps_mash", "", type=ConfigType.STEP, description="Mash step type", source="steps") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("steps_mash", mash_step, type=ConfigType.STEP, description="Mash step type", source="steps") if mashout_step is None: logger.info("INIT MashOut Step Type") try: - await self.cbpi.config.add("steps_mashout", "", ConfigType.STEP, "MashOut step type") + await self.cbpi.config.add("steps_mashout", "", type=ConfigType.STEP, description="MashOut step type", source="steps") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("steps_mashout", mashout_step, type=ConfigType.STEP, description="MashOut step type", source="steps") if boil_step is None: logger.info("INIT Boil Step Type") try: - await self.cbpi.config.add("steps_boil", "", ConfigType.STEP, "Boil step type") + await self.cbpi.config.add("steps_boil", "", type=ConfigType.STEP, description="Boil step type", source="steps") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("steps_boil", boil_step, type=ConfigType.STEP, description="Boil step type", source="steps") if max_dashboard_number is None: logger.info("INIT Max Dashboard Numbers for multiple dashboards") try: - await self.cbpi.config.add("max_dashboard_number", 4, ConfigType.SELECT, "Max Number of Dashboards", - [{"label": "1", "value": 1}, + await self.cbpi.config.add("max_dashboard_number", 4, type=ConfigType.SELECT, description="Max Number of Dashboards", + + options= [{"label": "1", "value": 1}, {"label": "2", "value": 2}, {"label": "3", "value": 3}, {"label": "4", "value": 4}, @@ -132,81 +167,109 @@ class ConfigUpdate(CBPiExtension): {"label": "7", "value": 7}, {"label": "8", "value": 8}, {"label": "9", "value": 9}, - {"label": "10", "value": 10}]) + {"label": "10", "value": 10}], + source="craftbeerpi") except: logger.warning('Unable to update database') if current_dashboard_number is None: logger.info("INIT Current Dashboard Number") try: - await self.cbpi.config.add("current_dashboard_number", 1, ConfigType.NUMBER, "Number of current Dashboard") + await self.cbpi.config.add("current_dashboard_number", 1, type=ConfigType.NUMBER, description="Number of current Dashboard",source="hidden") except: logger.warning('Unable to update database') ## Check if AtuoMode for Steps is in config - AutoMode = self.cbpi.config.get("AutoMode", None) + if AutoMode is None: logger.info("INIT AutoMode") try: - await self.cbpi.config.add("AutoMode", "Yes", ConfigType.SELECT, "Use AutoMode in steps", - [{"label": "Yes", "value": "Yes"}, - {"label": "No", "value": "No"}]) + await self.cbpi.config.add("AutoMode", "Yes", type=ConfigType.SELECT, description="Use AutoMode in steps", + options=[{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}], + source="steps") except: logger.warning('Unable to update config') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("AutoMode", AutoMode, type=ConfigType.SELECT, description="Use AutoMode in steps", options= + [{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}], + source="steps") ## Check if AddMashInStep for Steps is in config - AddMashIn = self.cbpi.config.get("AddMashInStep", None) + if AddMashIn is None: logger.info("INIT AddMashInStep") try: - await self.cbpi.config.add("AddMashInStep", "Yes", ConfigType.SELECT, "Add MashIn Step automatically if not defined in recipe", - [{"label": "Yes", "value": "Yes"}, - {"label": "No", "value": "No"}]) + await self.cbpi.config.add("AddMashInStep", "Yes", type=ConfigType.SELECT, description= "Add MashIn Step automatically if not defined in recipe", + options = [{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}], + source="steps") except: logger.warning('Unable to update config') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("AddMashInStep", AddMashIn, type=ConfigType.SELECT, description="Add MashIn Step automatically if not defined in recipe", + options= [{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}], + source="steps") ## Check if Brewfather UserID is in config - bfuserid = self.cbpi.config.get("brewfather_user_id", None) + if bfuserid is None: logger.info("INIT Brewfather User ID") try: - await self.cbpi.config.add("brewfather_user_id", "", ConfigType.STRING, "Brewfather User ID") + await self.cbpi.config.add("brewfather_user_id", "", type=ConfigType.STRING, description="Brewfather User ID") except: logger.warning('Unable to update config') ## Check if Brewfather API Key is in config - bfapikey = self.cbpi.config.get("brewfather_api_key", None) + if bfapikey is None: logger.info("INIT Brewfather API Key") try: - await self.cbpi.config.add("brewfather_api_key", "", ConfigType.STRING, "Brewfather API Key") + await self.cbpi.config.add("brewfather_api_key", "", type=ConfigType.STRING, description="Brewfather API Key") except: logger.warning('Unable to update config') ## Check if Brewfather API Key is in config - RecipeCreationPath = self.cbpi.config.get("RECIPE_CREATION_PATH", None) + if RecipeCreationPath is None: logger.info("INIT Recipe Creation Path") try: - await self.cbpi.config.add("RECIPE_CREATION_PATH", "upload", ConfigType.STRING, "API path to creation plugin. Default: upload . CHANGE ONLY IF USING A RECIPE CREATION PLUGIN") + await self.cbpi.config.add("RECIPE_CREATION_PATH", "upload", type=ConfigType.STRING, description="API path to creation plugin. Default: upload . CHANGE ONLY IF USING A RECIPE CREATION PLUGIN") except: logger.warning('Unable to update config') ## Check if Kettle for Boil, Whirlpool and Cooldown is in config - BoilKettle = self.cbpi.config.get("BoilKettle", None) + if BoilKettle is None: logger.info("INIT BoilKettle") try: - await self.cbpi.config.add("BoilKettle", "", ConfigType.KETTLE, "Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used") + await self.cbpi.config.add("BoilKettle", "", type=ConfigType.KETTLE, description="Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used",source="steps") except: logger.warning('Unable to update config') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("BoilKettle", BoilKettle, type=ConfigType.KETTLE, description="Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used",source="steps") + + if MASH_TUN is None: + logger.info("INIT MASH_TUN") + try: + await self.cbpi.config.add("MASH_TUN", "", type=ConfigType.KETTLE, description="Default Mash Tun",source="steps") + except: + logger.warning('Unable to update config') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("MASH_TUN", MASH_TUN, type=ConfigType.KETTLE, description="Default Mash Tun",source="steps") ## Check if CSV logfiles is on config if logfiles is None: logger.info("INIT CSV logfiles") try: - await self.cbpi.config.add("CSVLOGFILES", "Yes", ConfigType.SELECT, "Write sensor data to csv logfiles", - [{"label": "Yes", "value": "Yes"}, + await self.cbpi.config.add("CSVLOGFILES", "Yes", type=ConfigType.SELECT, description="Write sensor data to csv logfiles", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -215,8 +278,8 @@ class ConfigUpdate(CBPiExtension): if influxdb is None: logger.info("INIT Influxdb") try: - await self.cbpi.config.add("INFLUXDB", "No", ConfigType.SELECT, "Write sensor data to influxdb", - [{"label": "Yes", "value": "Yes"}, + await self.cbpi.config.add("INFLUXDB", "No", type=ConfigType.SELECT, description="Write sensor data to influxdb", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -225,7 +288,7 @@ class ConfigUpdate(CBPiExtension): if influxdbaddr is None: logger.info("INIT Influxdbaddr") try: - await self.cbpi.config.add("INFLUXDBADDR", "http://localhost:8086", ConfigType.STRING, "URL Address of your influxdb server (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)") + await self.cbpi.config.add("INFLUXDBADDR", "http://localhost:8086", type=ConfigType.STRING, description="URL Address of your influxdb server (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)") except: logger.warning('Unable to update config') @@ -233,7 +296,7 @@ class ConfigUpdate(CBPiExtension): if influxdbname is None: logger.info("INIT Influxdbname") try: - await self.cbpi.config.add("INFLUXDBNAME", "cbpi4", ConfigType.STRING, "Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)") + await self.cbpi.config.add("INFLUXDBNAME", "cbpi4", type=ConfigType.STRING, description="Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)") except: logger.warning('Unable to update config') @@ -241,7 +304,7 @@ class ConfigUpdate(CBPiExtension): if influxdbuser is None: logger.info("INIT Influxdbuser") try: - await self.cbpi.config.add("INFLUXDBUSER", " ", ConfigType.STRING, "User name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)") + await self.cbpi.config.add("INFLUXDBUSER", " ", type=ConfigType.STRING, description="User name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)") except: logger.warning('Unable to update config') @@ -249,7 +312,7 @@ class ConfigUpdate(CBPiExtension): if influxdbpwd is None: logger.info("INIT Influxdbpwd") try: - await self.cbpi.config.add("INFLUXDBPWD", " ", ConfigType.STRING, "Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)") + await self.cbpi.config.add("INFLUXDBPWD", " ", type=ConfigType.STRING, description="Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)") except: logger.warning('Unable to update config') @@ -257,8 +320,8 @@ class ConfigUpdate(CBPiExtension): if influxdbcloud is None: logger.info("INIT influxdbcloud") try: - await self.cbpi.config.add("INFLUXDBCLOUD", "No", ConfigType.SELECT, "Write sensor data to influxdb cloud (INFLUXDB must set to Yes)", - [{"label": "Yes", "value": "Yes"}, + await self.cbpi.config.add("INFLUXDBCLOUD", "No", type=ConfigType.SELECT, description="Write sensor data to influxdb cloud (INFLUXDB must set to Yes)", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -267,15 +330,15 @@ class ConfigUpdate(CBPiExtension): if influxdbmeasurement is None: logger.info("INIT Influxdb measurementname") try: - await self.cbpi.config.add("INFLUXDBMEASUREMENT", "measurement", ConfigType.STRING, "Name of the measurement in your INFLUXDB database (default: measurement)") + await self.cbpi.config.add("INFLUXDBMEASUREMENT", "measurement", type=ConfigType.STRING, description="Name of the measurement in your INFLUXDB database (default: measurement)") except: logger.warning('Unable to update config') if mqttupdate is None: logger.info("INIT MQTT update frequency for Kettles and Fermenters") try: - await self.cbpi.config.add("MQTTUpdate", 0, ConfigType.SELECT, "Forced MQTT Update frequency in s for Kettle and Fermenter (no changes in payload required). Restart required after change", - [{"label": "30", "value": 30}, + await self.cbpi.config.add("MQTTUpdate", 0, type=ConfigType.SELECT, description="Forced MQTT Update frequency in s for Kettle and Fermenter (no changes in payload required). Restart required after change", + options= [{"label": "30", "value": 30}, {"label": "60", "value": 60}, {"label": "120", "value": 120}, {"label": "300", "value": 300}, @@ -287,8 +350,8 @@ class ConfigUpdate(CBPiExtension): if PRESSURE_UNIT is None: logger.info("INIT PRESSURE_UNIT") try: - await self.cbpi.config.add("PRESSURE_UNIT", "kPa", ConfigType.SELECT, "Set unit for pressure", - [{"label": "kPa", "value": "kPa"}, + await self.cbpi.config.add("PRESSURE_UNIT", "kPa", type=ConfigType.SELECT, description="Set unit for pressure", + options= [{"label": "kPa", "value": "kPa"}, {"label": "PSI", "value": "PSI"}]) except: logger.warning('Unable to update config') @@ -297,7 +360,7 @@ class ConfigUpdate(CBPiExtension): if SENSOR_LOG_BACKUP_COUNT is None: logger.info("INIT SENSOR_LOG_BACKUP_COUNT") try: - await self.cbpi.config.add("SENSOR_LOG_BACKUP_COUNT", 3, ConfigType.NUMBER, "Max. number of backup logs") + await self.cbpi.config.add("SENSOR_LOG_BACKUP_COUNT", 3, type=ConfigType.NUMBER, description="Max. number of backup logs") except: logger.warning('Unable to update database') @@ -305,7 +368,7 @@ class ConfigUpdate(CBPiExtension): if SENSOR_LOG_MAX_BYTES is None: logger.info("Init maximum size of sensor logfiles") try: - await self.cbpi.config.add("SENSOR_LOG_MAX_BYTES", 100000, ConfigType.NUMBER, "Max. number of bytes in sensor logs") + await self.cbpi.config.add("SENSOR_LOG_MAX_BYTES", 100000, type=ConfigType.NUMBER, description="Max. number of bytes in sensor logs") except: logger.warning('Unable to update database') @@ -313,8 +376,8 @@ class ConfigUpdate(CBPiExtension): if slow_pipe_animation is None: logger.info("INIT slow_pipe_animation") try: - await self.cbpi.config.add("slow_pipe_animation", "Yes", ConfigType.SELECT, "Slow down dashboard pipe animation taking up close to 100% of the CPU's capacity", - [{"label": "Yes", "value": "Yes"}, + await self.cbpi.config.add("slow_pipe_animation", "Yes", type=ConfigType.SELECT, description="Slow down dashboard pipe animation taking up close to 100% of the CPU's capacity", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -323,8 +386,8 @@ class ConfigUpdate(CBPiExtension): if NOTIFY_ON_ERROR is None: logger.info("INIT NOTIFY_ON_ERROR") try: - await self.cbpi.config.add("NOTIFY_ON_ERROR", "No", ConfigType.SELECT, "Send Notification on Logging Error", - [{"label": "Yes", "value": "Yes"}, + await self.cbpi.config.add("NOTIFY_ON_ERROR", "No", type=ConfigType.SELECT, description="Send Notification on Logging Error", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -333,8 +396,8 @@ class ConfigUpdate(CBPiExtension): if PLAY_BUZZER is None: logger.info("INIT PLAY_BUZZER") try: - await self.cbpi.config.add("PLAY_BUZZER", "No", ConfigType.SELECT, "Play buzzer sound in Web interface on Notifications", - [{"label": "Yes", "value": "Yes"}, + await self.cbpi.config.add("PLAY_BUZZER", "No", type=ConfigType.SELECT, description="Play buzzer sound in Web interface on Notifications", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -342,13 +405,31 @@ class ConfigUpdate(CBPiExtension): if BoilAutoTimer is None: logging.info("INIT BoilAutoTimer") try: - await self.cbpi.config.add('BoilAutoTimer', 'No', ConfigType.SELECT, - 'Start Boil timer automatically if Temp does not change for 5 Minutes and is above 95C/203F', - [{"label": "Yes", "value": "Yes"}, - {"label": "No", "value": "No"}]) + await self.cbpi.config.add('BoilAutoTimer', 'No', type=ConfigType.SELECT, + description='Start Boil timer automatically if Temp does not change for 5 Minutes and is above 95C/203F', + options= [{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}], + source="steps") BoilAutoTimer = self.cbpi.config.get("BoilAutoTimer", "No") except: logging.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add('BoilAutoTimer', BoilAutoTimer, type=ConfigType.SELECT, + description='Start Boil timer automatically if Temp does not change for 5 Minutes and is above 95C/203F', + options=[{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}], + source="steps") + + + ## Check if influxdbname is in config + if CONFIG_STATUS is None: + logger.warning("Setting Config Status") + try: + await self.cbpi.config.add("CONFIG_STATUS", "4.1.8", type=ConfigType.STRING, description="Status of the cofig file. Internal use for maintenance", source="hidden") + except: + logger.warning('Unable to update config') + def setup(cbpi): cbpi.plugin.register("ConfigUpdate", ConfigUpdate) From 754d8be21fae2ce43aff0226e4d8e41b9ed9a057 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 2 Apr 2023 16:32:10 +0200 Subject: [PATCH 05/34] update currentdashbardnumber to hidden --- cbpi/__init__.py | 2 +- cbpi/extension/ConfigUpdate/__init__.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 5f83b3a..212d1ee 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a3" +__version__ = "4.1.8.a4" __codename__ = "Groundhog Day" diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index a8c52ab..a12ee88 100644 --- a/cbpi/extension/ConfigUpdate/__init__.py +++ b/cbpi/extension/ConfigUpdate/__init__.py @@ -178,6 +178,9 @@ class ConfigUpdate(CBPiExtension): await self.cbpi.config.add("current_dashboard_number", 1, type=ConfigType.NUMBER, description="Number of current Dashboard",source="hidden") except: logger.warning('Unable to update database') + else: + if CONFIG_STATUS is None: + await self.cbpi.config.add("current_dashboard_number", current_dashboard_number, type=ConfigType.NUMBER, description="Number of current Dashboard",source="hidden") ## Check if AtuoMode for Steps is in config From 73200890a1feb9e2aa0e849d20eda03f56f1746c Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:01:56 +0200 Subject: [PATCH 06/34] removed logging item --- cbpi/controller/plugin_controller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index 5bfb8fd..7c24332 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -205,7 +205,6 @@ class PluginController(): from importlib.metadata import (distribution, metadata, version) meta = metadata(key) - logging.warning(key) result.append({row: meta[row] for row in list(metadata(key))}) except Exception as e: From 1357aa2a0e3d5eb3e195bfbb9340c24b8f3bd67b Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 2 Apr 2023 18:27:15 +0200 Subject: [PATCH 07/34] fix in http_plugin endpoint --- cbpi/http_endpoints/http_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi/http_endpoints/http_plugin.py b/cbpi/http_endpoints/http_plugin.py index 9a36beb..27b5149 100644 --- a/cbpi/http_endpoints/http_plugin.py +++ b/cbpi/http_endpoints/http_plugin.py @@ -88,7 +88,7 @@ class PluginHttpEndpoints: return web.json_response(plugin_list, dumps=json_dumps) @request_mapping(path="/names", method="GET", auth_required=False) - async def list(self, request): + async def names(self, request): """ --- description: Get a list of avialable plugin names From 7d9d010e0c79d5ae5ae862bff9085dba5ecfb605 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Mon, 3 Apr 2023 20:39:35 +0200 Subject: [PATCH 08/34] keyword 'globalsettings' in plugin setup.py required to show up on settings page --- cbpi/controller/plugin_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index 7c24332..b361d89 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -231,7 +231,7 @@ class PluginController(): for key, module in discovered_plugins.items(): try: meta = metadata(key) - if meta["Name"] != "cbpi4gui": + if meta["Name"] != "cbpi4gui" and meta["Keywords"] == "globalsettings": result.append(dict(label=meta["Name"], value=meta["Name"])) except Exception as e: From 5e2dc35b30a0ca44eb7c93a74b9d7fd8c49848c7 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Wed, 5 Apr 2023 06:45:33 +0200 Subject: [PATCH 09/34] test newer cryptography version as 40.0.0 may cuase issues on older 32 bit systems --- cbpi/__init__.py | 2 +- cbpi/controller/plugin_controller.py | 3 +-- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 212d1ee..0b47676 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a4" +__version__ = "4.1.8.a5" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index b361d89..9bd32b4 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -233,9 +233,8 @@ class PluginController(): meta = metadata(key) if meta["Name"] != "cbpi4gui" and meta["Keywords"] == "globalsettings": result.append(dict(label=meta["Name"], value=meta["Name"])) - except Exception as e: - logger.error("FAILED to load plugin {} ".format(key)) + logger.error("FAILED to read metadata for plugin {} ".format(key)) logger.error(e) except Exception as e: logger.error(e) diff --git a/requirements.txt b/requirements.txt index bce7636..c02ae4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ aiohttp-session==2.12.0 aiohttp-swagger==1.0.16 aiojobs==1.1.0 aiosqlite==0.17.0 -cryptography==40.0.0 +cryptography==40.0.1 requests==2.28.1 voluptuous==0.13.1 pyfiglet==0.8.post1 diff --git a/setup.py b/setup.py index 4b173cc..c1d3129 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ setup(name='cbpi4', "aiohttp-swagger==1.0.16", "aiojobs==1.1.0 ", "aiosqlite==0.17.0", - "cryptography==40.0.0", + "cryptography==40.0.1", "requests==2.28.1", "voluptuous==0.13.1", "pyfiglet==0.8.post1", From 662f29d0947a9e122a88fb97109639ffe8ecde0d Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Wed, 5 Apr 2023 07:02:11 +0200 Subject: [PATCH 10/34] change dependency -> trying to fix potential issue wit latest release and armv6 devices (Pi Zero W) -> Illegal Instruction PiBrewing/craftbeerpi4#108 --- cbpi/__init__.py | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index be197c8..9109e84 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.7" +__version__ = "4.1.9" __codename__ = "Groundhog Day" diff --git a/requirements.txt b/requirements.txt index bce7636..c02ae4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ aiohttp-session==2.12.0 aiohttp-swagger==1.0.16 aiojobs==1.1.0 aiosqlite==0.17.0 -cryptography==40.0.0 +cryptography==40.0.1 requests==2.28.1 voluptuous==0.13.1 pyfiglet==0.8.post1 diff --git a/setup.py b/setup.py index 4b173cc..c1d3129 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ setup(name='cbpi4', "aiohttp-swagger==1.0.16", "aiojobs==1.1.0 ", "aiosqlite==0.17.0", - "cryptography==40.0.0", + "cryptography==40.0.1", "requests==2.28.1", "voluptuous==0.13.1", "pyfiglet==0.8.post1", From dc36cc1ed34cf9d217f01df07162745559d93957 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Thu, 6 Apr 2023 12:40:49 +0200 Subject: [PATCH 11/34] test withconfig parameter removal --- cbpi/__init__.py | 2 +- cbpi/api/base.py | 3 +++ cbpi/controller/config_controller.py | 19 ++++++++++++++++++- cbpi/http_endpoints/http_config.py | 25 ++++++++++++++++++++++++- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 0b47676..622871d 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a5" +__version__ = "4.1.8.a6" __codename__ = "Groundhog Day" diff --git a/cbpi/api/base.py b/cbpi/api/base.py index 9fd4692..51b7c46 100644 --- a/cbpi/api/base.py +++ b/cbpi/api/base.py @@ -16,6 +16,9 @@ class CBPiBase(metaclass=ABCMeta): async def set_config_value(self,name,value): return await self.cbpi.config.set(name,value) + + async def remove_config_parameter(self,name): + return await self.cbpi.config.remove(name) async def add_config_value(self, name, value, type: ConfigType, description, source, options=None): await self.cbpi.config.add(name, value, type, description, source,options=None) diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index e0c5587..2000b13 100644 --- a/cbpi/controller/config_controller.py +++ b/cbpi/controller/config_controller.py @@ -50,10 +50,27 @@ class ConfigController: with open(self.path, "w") as file: json.dump(data, file, indent=4, sort_keys=True) - async def add(self, name, value, type: ConfigType, description, source="", options=None): + async def add(self, name, value, type: ConfigType, description, source="craftbeerpi", options=None): self.cache[name] = Config(name,value,description,type,source,options) data = {} for key, value in self.cache.items(): data[key] = value.to_dict() with open(self.path, "w") as file: json.dump(data, file, indent=4, sort_keys=True) + + async def remove(self, name): + data = {} + self.testcache={} + for key, value in self.cache.items(): + try: + if key != name: + data[key] = value.to_dict() + self.testcache[key] = Config(name=data[key].get("name"), value=data[key].get("value"), description=data[key].get("description"), + type=ConfigType(data[key].get("type", "string")), options=data[key].get("options", None), + source=data[key].get("source", "craftbeerpi") ) + except Exception as e: + print(e) + with open(self.path, "w") as file: + json.dump(data, file, indent=4, sort_keys=True) + self.cache=self.testcache + diff --git a/cbpi/http_endpoints/http_config.py b/cbpi/http_endpoints/http_config.py index 5e233d2..fc2d643 100644 --- a/cbpi/http_endpoints/http_config.py +++ b/cbpi/http_endpoints/http_config.py @@ -78,5 +78,28 @@ class ConfigHttpEndpoints: name = request.match_info['name'] # if name not in self.cache: # raise CBPiException("Parameter %s not found" % name) - data = self.controller.get(name) +# data = self.controller.get(name) return web.json_response(self.controller.get(name), dumps=json_dumps) + + @request_mapping(path="/remove/{name}/", method="PUT", auth_required=False) + async def http_remove(self, request) -> web.Response: + + """ + --- + description: Remove config parameter + tags: + - Config + parameters: + - name: "name" + in: "path" + description: "Parameter name" + required: true + type: "string" + responses: + "200": + description: successful operation + """ + + name = request.match_info['name'] + await self.controller.remove(name=name) + return web.Response(status=200) \ No newline at end of file From 5e69ce4c40a7b2abc310bcd453060a551c8b73a7 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Fri, 7 Apr 2023 16:26:03 +0200 Subject: [PATCH 12/34] handling in case of error (config controller -> remove) --- cbpi/__init__.py | 2 +- cbpi/controller/config_controller.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 622871d..7fcced5 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a6" +__version__ = "4.1.8.a7" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index 2000b13..bb1b5b0 100644 --- a/cbpi/controller/config_controller.py +++ b/cbpi/controller/config_controller.py @@ -61,16 +61,20 @@ class ConfigController: async def remove(self, name): data = {} self.testcache={} + success=False for key, value in self.cache.items(): try: if key != name: data[key] = value.to_dict() self.testcache[key] = Config(name=data[key].get("name"), value=data[key].get("value"), description=data[key].get("description"), type=ConfigType(data[key].get("type", "string")), options=data[key].get("options", None), - source=data[key].get("source", "craftbeerpi") ) + source=data[key].get("source", "craftbeerpi") ) + success=True except Exception as e: print(e) - with open(self.path, "w") as file: - json.dump(data, file, indent=4, sort_keys=True) - self.cache=self.testcache + success=False + if success == True: + with open(self.path, "w") as file: + json.dump(data, file, indent=4, sort_keys=True) + self.cache=self.testcache From 9041ad7daa7fafdc88900d83e3fec9b7d1dd9ff3 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 8 Apr 2023 11:55:49 +0200 Subject: [PATCH 13/34] added error handling in case of corrupt json config files. --- cbpi/__init__.py | 2 +- cbpi/controller/basic_controller2.py | 42 ++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 7fcced5..8d7482d 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a7" +__version__ = "4.1.8.a8" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/basic_controller2.py b/cbpi/controller/basic_controller2.py index 7ef8b4f..6a4a64f 100644 --- a/cbpi/controller/basic_controller2.py +++ b/cbpi/controller/basic_controller2.py @@ -34,19 +34,39 @@ class BasicController: return self.resource(data.get("id"), data.get("name"), type=data.get("type"), props=Props(data.get("props", {})) ) async def load(self): - logging.info("{} Load ".format(self.name)) - with open(self.path) as json_file: - data = json.load(json_file) - data['data'].sort(key=lambda x: x.get('name').upper()) + try: + logging.info("{} Load ".format(self.name)) + with open(self.path) as json_file: + data = json.load(json_file) + data['data'].sort(key=lambda x: x.get('name').upper()) - for i in data["data"]: - self.data.append(self.create(i)) + for i in data["data"]: + self.data.append(self.create(i)) - if self.autostart is True: - for item in self.data: - logging.info("{} Starting ".format(self.name)) - await self.start(item.id) - await self.push_udpate() + if self.autostart is True: + for item in self.data: + logging.info("{} Starting ".format(self.name)) + await self.start(item.id) + await self.push_udpate() + except Exception as e: + logging.warning("Invalid {} file - Creating empty file".format(self.path)) + os.remove(self.path) + with open(self.path, "w") as file: + json.dump(dict( data=[]), file, indent=4, sort_keys=True) + + with open(self.path) as json_file: + data = json.load(json_file) + data['data'].sort(key=lambda x: x.get('name').upper()) + + for i in data["data"]: + self.data.append(self.create(i)) + + if self.autostart is True: + for item in self.data: + logging.info("{} Starting ".format(self.name)) + await self.start(item.id) + await self.push_udpate() + async def save(self): logging.info("{} Save ".format(self.name)) From efc3e3737c873c7877cb7802104b38c98a8dea8f Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 8 Apr 2023 12:26:10 +0200 Subject: [PATCH 14/34] variable CONFIG_STATUS (cbpi version) --- cbpi/__init__.py | 2 +- cbpi/extension/ConfigUpdate/__init__.py | 36 +++++++++++++------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 8d7482d..6e25b40 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a8" +__version__ = "4.1.8.a9" __codename__ = "Groundhog Day" diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index a12ee88..e7fca81 100644 --- a/cbpi/extension/ConfigUpdate/__init__.py +++ b/cbpi/extension/ConfigUpdate/__init__.py @@ -8,6 +8,7 @@ import json from cbpi.api import * from cbpi.api.config import ConfigType from cbpi.api.base import CBPiBase +from cbpi import __version__ logger = logging.getLogger(__name__) @@ -61,6 +62,7 @@ class ConfigUpdate(CBPiExtension): RecipeCreationPath = self.cbpi.config.get("RECIPE_CREATION_PATH", None) BoilKettle = self.cbpi.config.get("BoilKettle", None) CONFIG_STATUS = self.cbpi.config.get("CONFIG_STATUS", None) + self.version=__version__ if boil_temp is None: @@ -70,7 +72,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("steps_boil_temp", boil_temp, type=ConfigType.NUMBER, description="Default Boil Temperature for Recipe Creation", source="steps") if cooldown_sensor is None: @@ -80,7 +82,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("steps_cooldown_sensor", cooldown_sensor, type=ConfigType.SENSOR, description="Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)", source="steps") if cooldown_actor is None: @@ -90,7 +92,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("steps_cooldown_actor", cooldown_actor, type=ConfigType.ACTOR, description="Actor to trigger cooldown water on and off (default: None)", source="steps") if cooldown_temp is None: @@ -100,7 +102,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("steps_cooldown_temp", cooldown_temp, type=ConfigType.NUMBER, description="Cooldown temp will send notification when this temeprature is reached", source="steps") if cooldown_step is None: @@ -110,7 +112,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("steps_cooldown", cooldown_step, type=ConfigType.STEP, description="Cooldown step type", source="steps") if mashin_step is None: @@ -120,7 +122,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("steps_mashin", mashin_step, type=ConfigType.STEP, description="MashIn step type", source="steps") if mash_step is None: @@ -130,7 +132,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("steps_mash", mash_step, type=ConfigType.STEP, description="Mash step type", source="steps") if mashout_step is None: @@ -140,7 +142,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("steps_mashout", mashout_step, type=ConfigType.STEP, description="MashOut step type", source="steps") if boil_step is None: @@ -150,7 +152,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("steps_boil", boil_step, type=ConfigType.STEP, description="Boil step type", source="steps") if max_dashboard_number is None: @@ -179,7 +181,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("current_dashboard_number", current_dashboard_number, type=ConfigType.NUMBER, description="Number of current Dashboard",source="hidden") ## Check if AtuoMode for Steps is in config @@ -194,7 +196,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update config') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("AutoMode", AutoMode, type=ConfigType.SELECT, description="Use AutoMode in steps", options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}], @@ -212,7 +214,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update config') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("AddMashInStep", AddMashIn, type=ConfigType.SELECT, description="Add MashIn Step automatically if not defined in recipe", options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}], @@ -254,7 +256,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update config') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("BoilKettle", BoilKettle, type=ConfigType.KETTLE, description="Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used",source="steps") if MASH_TUN is None: @@ -264,7 +266,7 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update config') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("MASH_TUN", MASH_TUN, type=ConfigType.KETTLE, description="Default Mash Tun",source="steps") ## Check if CSV logfiles is on config @@ -417,7 +419,7 @@ class ConfigUpdate(CBPiExtension): except: logging.warning('Unable to update database') else: - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add('BoilAutoTimer', BoilAutoTimer, type=ConfigType.SELECT, description='Start Boil timer automatically if Temp does not change for 5 Minutes and is above 95C/203F', options=[{"label": "Yes", "value": "Yes"}, @@ -426,10 +428,10 @@ class ConfigUpdate(CBPiExtension): ## Check if influxdbname is in config - if CONFIG_STATUS is None: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: logger.warning("Setting Config Status") try: - await self.cbpi.config.add("CONFIG_STATUS", "4.1.8", type=ConfigType.STRING, description="Status of the cofig file. Internal use for maintenance", source="hidden") + await self.cbpi.config.add("CONFIG_STATUS", self.version, type=ConfigType.STRING, description="Status of the config file. Internal use for maintenance", source="hidden") except: logger.warning('Unable to update config') From 668705e1e084a28bea06941b6b936c947a4f903a Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 8 Apr 2023 14:15:08 +0200 Subject: [PATCH 15/34] api function to remove oboslete settings parameters --- cbpi/__init__.py | 2 +- cbpi/controller/config_controller.py | 12 ++++++++++++ cbpi/http_endpoints/http_config.py | 15 ++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 6e25b40..29df26c 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a9" +__version__ = "4.1.8.a10" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index bb1b5b0..b0324b2 100644 --- a/cbpi/controller/config_controller.py +++ b/cbpi/controller/config_controller.py @@ -78,3 +78,15 @@ class ConfigController: json.dump(data, file, indent=4, sort_keys=True) self.cache=self.testcache + async def remove_obsolete(self): + result = {} + for key, value in self.cache.items(): + if (value.source not in ('craftbeerpi','steps','hidden')): + test = await self.cbpi.plugin.load_plugin_list(value.source) + if test == []: + update=self.get(str(value.source)+'_update') + if update: + await self.remove(str(value.source)+'_update') + await self.remove(key) + result[key] = value.to_dict() + return result \ No newline at end of file diff --git a/cbpi/http_endpoints/http_config.py b/cbpi/http_endpoints/http_config.py index fc2d643..ace5f17 100644 --- a/cbpi/http_endpoints/http_config.py +++ b/cbpi/http_endpoints/http_config.py @@ -102,4 +102,17 @@ class ConfigHttpEndpoints: name = request.match_info['name'] await self.controller.remove(name=name) - return web.Response(status=200) \ No newline at end of file + return web.Response(status=200) + + @request_mapping(path="/obsolete", auth_required=False) + async def http_remove_obsolete(self, request) -> web.Response: + """ + --- + description: Get all config parameters + tags: + - Config + responses: + "200": + description: successful operation + """ + return web.json_response(await self.controller.remove_obsolete(), dumps=json_dumps) \ No newline at end of file From f036a2f97260161d0181363c0d364ed558c498e6 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 8 Apr 2023 15:12:26 +0200 Subject: [PATCH 16/34] fix api description --- cbpi/__init__.py | 2 +- cbpi/http_endpoints/http_config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 29df26c..e5432a1 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a10" +__version__ = "4.1.8.a11" __codename__ = "Groundhog Day" diff --git a/cbpi/http_endpoints/http_config.py b/cbpi/http_endpoints/http_config.py index ace5f17..8ba88f0 100644 --- a/cbpi/http_endpoints/http_config.py +++ b/cbpi/http_endpoints/http_config.py @@ -108,7 +108,7 @@ class ConfigHttpEndpoints: async def http_remove_obsolete(self, request) -> web.Response: """ --- - description: Get all config parameters + description: Remove obsolete config parameters tags: - Config responses: From f990e0e0a35ba9b4259d254abcdbfeef0ebade13 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 8 Apr 2023 15:21:17 +0200 Subject: [PATCH 17/34] requirement aiohttp -> 3.8.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c1d3129..d9478a4 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ setup(name='cbpi4', long_description_content_type='text/markdown', install_requires=[ "typing-extensions>=4", - "aiohttp==3.8.3", + "aiohttp==3.8.4", "aiohttp-auth==0.1.1", "aiohttp-route-decorator==0.1.4", "aiohttp-security==0.4.0", From 1d6cd75f8caf49e3f234f6f15be89c0cb57878d8 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 16 Apr 2023 17:22:33 +0200 Subject: [PATCH 18/34] fixed bug in parameter generation -> source | options order --- cbpi/__init__.py | 2 +- cbpi/api/base.py | 2 +- cbpi/api/dataclasses.py | 2 +- cbpi/controller/config_controller.py | 12 ++-- cbpi/extension/ConfigUpdate/__init__.py | 70 +++++++++++-------- cbpi/http_endpoints/http_config.py | 18 ++++- tests/cbpi-test-config/config.json | 62 ++++++++++++++++ .../dashboard/cbpi_dashboard_1.json | 6 +- 8 files changed, 136 insertions(+), 38 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index e5432a1..4d52643 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a11" +__version__ = "4.1.8.a12" __codename__ = "Groundhog Day" diff --git a/cbpi/api/base.py b/cbpi/api/base.py index 51b7c46..7e6365c 100644 --- a/cbpi/api/base.py +++ b/cbpi/api/base.py @@ -21,7 +21,7 @@ class CBPiBase(metaclass=ABCMeta): return await self.cbpi.config.remove(name) async def add_config_value(self, name, value, type: ConfigType, description, source, options=None): - await self.cbpi.config.add(name, value, type, description, source,options=None) + await self.cbpi.config.add(name, value, type, description, source, options=None) def get_kettle(self,id): return self.cbpi.kettle.find_by_id(id) diff --git a/cbpi/api/dataclasses.py b/cbpi/api/dataclasses.py index 6885912..eb545db 100644 --- a/cbpi/api/dataclasses.py +++ b/cbpi/api/dataclasses.py @@ -204,7 +204,7 @@ class Config: def __str__(self): return "....name={} value={}".format(self.name, self.value) def to_dict(self): - return dict(name=self.name, value=self.value, type=self.type.value, description=self.description, options=self.options, source=self.source) + return dict(name=self.name, value=self.value, type=self.type.value, description=self.description, source=self.source, options=self.options) @dataclass class NotificationAction: diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index b0324b2..16c7c02 100644 --- a/cbpi/controller/config_controller.py +++ b/cbpi/controller/config_controller.py @@ -30,7 +30,8 @@ class ConfigController: with open(self.path) as json_file: data = json.load(json_file) for key, value in data.items(): - self.cache[key] = Config(name=value.get("name"), value=value.get("value"), description=value.get("description"), type=ConfigType(value.get("type", "string")), options=value.get("options", None), source=value.get("source", "craftbeerpi") ) + self.cache[key] = Config(name=value.get("name"), value=value.get("value"), description=value.get("description"), type=ConfigType(value.get("type", "string")), source=value.get("source", "craftbeerpi"), options=value.get("options", None)) + logging.error(self.cache) def get(self, name, default=None): self.logger.debug("GET CONFIG VALUE %s (default %s)" % (name, default)) @@ -78,7 +79,7 @@ class ConfigController: json.dump(data, file, indent=4, sort_keys=True) self.cache=self.testcache - async def remove_obsolete(self): + async def obsolete(self, remove=False): result = {} for key, value in self.cache.items(): if (value.source not in ('craftbeerpi','steps','hidden')): @@ -86,7 +87,10 @@ class ConfigController: if test == []: update=self.get(str(value.source)+'_update') if update: - await self.remove(str(value.source)+'_update') - await self.remove(key) + logging.warning(update) + if remove: + await self.remove(str(value.source)+'_update') + if remove: + await self.remove(key) result[key] = value.to_dict() return result \ No newline at end of file diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index e7fca81..56eefab 100644 --- a/cbpi/extension/ConfigUpdate/__init__.py +++ b/cbpi/extension/ConfigUpdate/__init__.py @@ -159,7 +159,7 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT Max Dashboard Numbers for multiple dashboards") try: await self.cbpi.config.add("max_dashboard_number", 4, type=ConfigType.SELECT, description="Max Number of Dashboards", - + source="craftbeerpi", options= [{"label": "1", "value": 1}, {"label": "2", "value": 2}, {"label": "3", "value": 3}, @@ -169,8 +169,8 @@ class ConfigUpdate(CBPiExtension): {"label": "7", "value": 7}, {"label": "8", "value": 8}, {"label": "9", "value": 9}, - {"label": "10", "value": 10}], - source="craftbeerpi") + {"label": "10", "value": 10}]) + except: logger.warning('Unable to update database') @@ -190,17 +190,19 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT AutoMode") try: await self.cbpi.config.add("AutoMode", "Yes", type=ConfigType.SELECT, description="Use AutoMode in steps", + source="steps", options=[{"label": "Yes", "value": "Yes"}, - {"label": "No", "value": "No"}], - source="steps") + {"label": "No", "value": "No"}]) + except: logger.warning('Unable to update config') else: if CONFIG_STATUS is None or CONFIG_STATUS != self.version: - await self.cbpi.config.add("AutoMode", AutoMode, type=ConfigType.SELECT, description="Use AutoMode in steps", options= - [{"label": "Yes", "value": "Yes"}, - {"label": "No", "value": "No"}], - source="steps") + await self.cbpi.config.add("AutoMode", AutoMode, type=ConfigType.SELECT, description="Use AutoMode in steps", + source="steps", + options=[{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}]) + ## Check if AddMashInStep for Steps is in config @@ -208,24 +210,26 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT AddMashInStep") try: await self.cbpi.config.add("AddMashInStep", "Yes", type=ConfigType.SELECT, description= "Add MashIn Step automatically if not defined in recipe", + source="steps", options = [{"label": "Yes", "value": "Yes"}, - {"label": "No", "value": "No"}], - source="steps") + {"label": "No", "value": "No"}]) + except: logger.warning('Unable to update config') else: if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add("AddMashInStep", AddMashIn, type=ConfigType.SELECT, description="Add MashIn Step automatically if not defined in recipe", + source="steps", options= [{"label": "Yes", "value": "Yes"}, - {"label": "No", "value": "No"}], - source="steps") + {"label": "No", "value": "No"}]) + ## Check if Brewfather UserID is in config if bfuserid is None: logger.info("INIT Brewfather User ID") try: - await self.cbpi.config.add("brewfather_user_id", "", type=ConfigType.STRING, description="Brewfather User ID") + await self.cbpi.config.add("brewfather_user_id", "", type=ConfigType.STRING, description="Brewfather User ID", source="craftbeerpi") except: logger.warning('Unable to update config') @@ -234,7 +238,7 @@ class ConfigUpdate(CBPiExtension): if bfapikey is None: logger.info("INIT Brewfather API Key") try: - await self.cbpi.config.add("brewfather_api_key", "", type=ConfigType.STRING, description="Brewfather API Key") + await self.cbpi.config.add("brewfather_api_key", "", type=ConfigType.STRING, description="Brewfather API Key", source="craftbeerpi") except: logger.warning('Unable to update config') @@ -243,7 +247,7 @@ class ConfigUpdate(CBPiExtension): if RecipeCreationPath is None: logger.info("INIT Recipe Creation Path") try: - await self.cbpi.config.add("RECIPE_CREATION_PATH", "upload", type=ConfigType.STRING, description="API path to creation plugin. Default: upload . CHANGE ONLY IF USING A RECIPE CREATION PLUGIN") + await self.cbpi.config.add("RECIPE_CREATION_PATH", "upload", type=ConfigType.STRING, description="API path to creation plugin. Default: upload . CHANGE ONLY IF USING A RECIPE CREATION PLUGIN", source="craftbeerpi") except: logger.warning('Unable to update config') @@ -274,6 +278,7 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT CSV logfiles") try: await self.cbpi.config.add("CSVLOGFILES", "Yes", type=ConfigType.SELECT, description="Write sensor data to csv logfiles", + source="craftbeerpi", options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: @@ -284,6 +289,7 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT Influxdb") try: await self.cbpi.config.add("INFLUXDB", "No", type=ConfigType.SELECT, description="Write sensor data to influxdb", + source="craftbeerpi", options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: @@ -293,7 +299,7 @@ class ConfigUpdate(CBPiExtension): if influxdbaddr is None: logger.info("INIT Influxdbaddr") try: - await self.cbpi.config.add("INFLUXDBADDR", "http://localhost:8086", type=ConfigType.STRING, description="URL Address of your influxdb server (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)") + await self.cbpi.config.add("INFLUXDBADDR", "http://localhost:8086", type=ConfigType.STRING, description="URL Address of your influxdb server (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)", source="craftbeerpi") except: logger.warning('Unable to update config') @@ -301,7 +307,7 @@ class ConfigUpdate(CBPiExtension): if influxdbname is None: logger.info("INIT Influxdbname") try: - await self.cbpi.config.add("INFLUXDBNAME", "cbpi4", type=ConfigType.STRING, description="Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)") + await self.cbpi.config.add("INFLUXDBNAME", "cbpi4", type=ConfigType.STRING, description="Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)", source="craftbeerpi") except: logger.warning('Unable to update config') @@ -309,7 +315,7 @@ class ConfigUpdate(CBPiExtension): if influxdbuser is None: logger.info("INIT Influxdbuser") try: - await self.cbpi.config.add("INFLUXDBUSER", " ", type=ConfigType.STRING, description="User name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)") + await self.cbpi.config.add("INFLUXDBUSER", " ", type=ConfigType.STRING, description="User name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)", source="craftbeerpi") except: logger.warning('Unable to update config') @@ -317,7 +323,7 @@ class ConfigUpdate(CBPiExtension): if influxdbpwd is None: logger.info("INIT Influxdbpwd") try: - await self.cbpi.config.add("INFLUXDBPWD", " ", type=ConfigType.STRING, description="Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)") + await self.cbpi.config.add("INFLUXDBPWD", " ", type=ConfigType.STRING, description="Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)", source="craftbeerpi") except: logger.warning('Unable to update config') @@ -326,6 +332,7 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT influxdbcloud") try: await self.cbpi.config.add("INFLUXDBCLOUD", "No", type=ConfigType.SELECT, description="Write sensor data to influxdb cloud (INFLUXDB must set to Yes)", + source="craftbeerpi", options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: @@ -335,7 +342,7 @@ class ConfigUpdate(CBPiExtension): if influxdbmeasurement is None: logger.info("INIT Influxdb measurementname") try: - await self.cbpi.config.add("INFLUXDBMEASUREMENT", "measurement", type=ConfigType.STRING, description="Name of the measurement in your INFLUXDB database (default: measurement)") + await self.cbpi.config.add("INFLUXDBMEASUREMENT", "measurement", type=ConfigType.STRING, description="Name of the measurement in your INFLUXDB database (default: measurement)", source="craftbeerpi") except: logger.warning('Unable to update config') @@ -343,6 +350,7 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT MQTT update frequency for Kettles and Fermenters") try: await self.cbpi.config.add("MQTTUpdate", 0, type=ConfigType.SELECT, description="Forced MQTT Update frequency in s for Kettle and Fermenter (no changes in payload required). Restart required after change", + source="craftbeerpi", options= [{"label": "30", "value": 30}, {"label": "60", "value": 60}, {"label": "120", "value": 120}, @@ -356,6 +364,7 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT PRESSURE_UNIT") try: await self.cbpi.config.add("PRESSURE_UNIT", "kPa", type=ConfigType.SELECT, description="Set unit for pressure", + source="craftbeerpi", options= [{"label": "kPa", "value": "kPa"}, {"label": "PSI", "value": "PSI"}]) except: @@ -365,7 +374,7 @@ class ConfigUpdate(CBPiExtension): if SENSOR_LOG_BACKUP_COUNT is None: logger.info("INIT SENSOR_LOG_BACKUP_COUNT") try: - await self.cbpi.config.add("SENSOR_LOG_BACKUP_COUNT", 3, type=ConfigType.NUMBER, description="Max. number of backup logs") + await self.cbpi.config.add("SENSOR_LOG_BACKUP_COUNT", 3, type=ConfigType.NUMBER, description="Max. number of backup logs", source="craftbeerpi") except: logger.warning('Unable to update database') @@ -373,7 +382,7 @@ class ConfigUpdate(CBPiExtension): if SENSOR_LOG_MAX_BYTES is None: logger.info("Init maximum size of sensor logfiles") try: - await self.cbpi.config.add("SENSOR_LOG_MAX_BYTES", 100000, type=ConfigType.NUMBER, description="Max. number of bytes in sensor logs") + await self.cbpi.config.add("SENSOR_LOG_MAX_BYTES", 100000, type=ConfigType.NUMBER, description="Max. number of bytes in sensor logs", source="craftbeerpi") except: logger.warning('Unable to update database') @@ -382,6 +391,7 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT slow_pipe_animation") try: await self.cbpi.config.add("slow_pipe_animation", "Yes", type=ConfigType.SELECT, description="Slow down dashboard pipe animation taking up close to 100% of the CPU's capacity", + source="craftbeerpi", options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: @@ -392,6 +402,7 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT NOTIFY_ON_ERROR") try: await self.cbpi.config.add("NOTIFY_ON_ERROR", "No", type=ConfigType.SELECT, description="Send Notification on Logging Error", + source="craftbeerpi", options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: @@ -402,6 +413,7 @@ class ConfigUpdate(CBPiExtension): logger.info("INIT PLAY_BUZZER") try: await self.cbpi.config.add("PLAY_BUZZER", "No", type=ConfigType.SELECT, description="Play buzzer sound in Web interface on Notifications", + source="craftbeerpi", options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: @@ -411,10 +423,11 @@ class ConfigUpdate(CBPiExtension): logging.info("INIT BoilAutoTimer") try: await self.cbpi.config.add('BoilAutoTimer', 'No', type=ConfigType.SELECT, - description='Start Boil timer automatically if Temp does not change for 5 Minutes and is above 95C/203F', + description='Start Boil timer automatically if Temp does not change for 5 Minutes and is above 95C/203F', + source="steps", options= [{"label": "Yes", "value": "Yes"}, - {"label": "No", "value": "No"}], - source="steps") + {"label": "No", "value": "No"}]) + BoilAutoTimer = self.cbpi.config.get("BoilAutoTimer", "No") except: logging.warning('Unable to update database') @@ -422,9 +435,10 @@ class ConfigUpdate(CBPiExtension): if CONFIG_STATUS is None or CONFIG_STATUS != self.version: await self.cbpi.config.add('BoilAutoTimer', BoilAutoTimer, type=ConfigType.SELECT, description='Start Boil timer automatically if Temp does not change for 5 Minutes and is above 95C/203F', + source="steps", options=[{"label": "Yes", "value": "Yes"}, - {"label": "No", "value": "No"}], - source="steps") + {"label": "No", "value": "No"}]) + ## Check if influxdbname is in config diff --git a/cbpi/http_endpoints/http_config.py b/cbpi/http_endpoints/http_config.py index 8ba88f0..8efd20f 100644 --- a/cbpi/http_endpoints/http_config.py +++ b/cbpi/http_endpoints/http_config.py @@ -104,7 +104,20 @@ class ConfigHttpEndpoints: await self.controller.remove(name=name) return web.Response(status=200) - @request_mapping(path="/obsolete", auth_required=False) + @request_mapping(path="/getobsolete", auth_required=False) + async def http_get_obsolete(self, request) -> web.Response: + """ + --- + description: Get obsolete config parameters + tags: + - Config + responses: + "List of Obsolete Parameters": + description: successful operation + """ + return web.json_response(await self.controller.obsolete(False), dumps=json_dumps) + + @request_mapping(path="/removeobsolete", auth_required=False) async def http_remove_obsolete(self, request) -> web.Response: """ --- @@ -115,4 +128,5 @@ class ConfigHttpEndpoints: "200": description: successful operation """ - return web.json_response(await self.controller.remove_obsolete(), dumps=json_dumps) \ No newline at end of file + await self.controller.obsolete(True) + return web.Response(status=200) \ No newline at end of file diff --git a/tests/cbpi-test-config/config.json b/tests/cbpi-test-config/config.json index 8fef776..da7fab4 100644 --- a/tests/cbpi-test-config/config.json +++ b/tests/cbpi-test-config/config.json @@ -3,6 +3,7 @@ "description": "Author", "name": "AUTHOR", "options": null, + "source": "craftbeerpi", "type": "string", "value": "John Doe" }, @@ -19,6 +20,7 @@ "value": "No" } ], + "source": "steps", "type": "select", "value": "Yes" }, @@ -35,6 +37,7 @@ "value": "No" } ], + "source": "steps", "type": "select", "value": "Yes" }, @@ -42,6 +45,7 @@ "description": "Brewery Name", "name": "BREWERY_NAME", "options": null, + "source": "craftbeerpi", "type": "string", "value": "Some New Brewery Name" }, @@ -58,6 +62,7 @@ "value": "No" } ], + "source": "steps", "type": "select", "value": "No" }, @@ -65,9 +70,18 @@ "description": "Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used", "name": "BoilKettle", "options": null, + "source": "steps", "type": "kettle", "value": "" }, + "CONFIG_STATUS": { + "description": "Status of the config file. Internal use for maintenance", + "name": "CONFIG_STATUS", + "options": null, + "source": "hidden", + "type": "string", + "value": "4.1.8.a11" + }, "CSVLOGFILES": { "description": "Write sensor data to csv logfiles", "name": "CSVLOGFILES", @@ -81,6 +95,7 @@ "value": "No" } ], + "source": "craftbeerpi", "type": "select", "value": "Yes" }, @@ -97,6 +112,7 @@ "value": "No" } ], + "source": "craftbeerpi", "type": "select", "value": "No" }, @@ -104,6 +120,7 @@ "description": "IP Address of your influxdb server (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)", "name": "INFLUXDBADDR", "options": null, + "source": "craftbeerpi", "type": "string", "value": "localhost" }, @@ -120,6 +137,7 @@ "value": "No" } ], + "source": "craftbeerpi", "type": "select", "value": "No" }, @@ -127,6 +145,7 @@ "description": "Name of the measurement in your INFLUXDB database (default: measurement)", "name": "INFLUXDBMEASUREMENT", "options": null, + "source": "craftbeerpi", "type": "string", "value": "measurement" }, @@ -134,6 +153,7 @@ "description": "Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)", "name": "INFLUXDBNAME", "options": null, + "source": "craftbeerpi", "type": "string", "value": "cbpi4" }, @@ -141,6 +161,7 @@ "description": "Port of your influxdb server", "name": "INFLUXDBPORT", "options": null, + "source": "craftbeerpi", "type": "string", "value": "8086" }, @@ -148,6 +169,7 @@ "description": "Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)", "name": "INFLUXDBPWD", "options": null, + "source": "craftbeerpi", "type": "string", "value": " " }, @@ -155,6 +177,7 @@ "description": "User name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)", "name": "INFLUXDBUSER", "options": null, + "source": "craftbeerpi", "type": "string", "value": " " }, @@ -162,6 +185,7 @@ "description": "Default Mash Tun", "name": "MASH_TUN", "options": null, + "source": "steps", "type": "kettle", "value": "" }, @@ -190,6 +214,7 @@ "value": 0 } ], + "source": "craftbeerpi", "type": "select", "value": 0 }, @@ -206,6 +231,24 @@ "value": "No" } ], + "source": "craftbeerpi", + "type": "select", + "value": "No" + }, + "PLAY_BUZZER": { + "description": "Play buzzer sound in Web interface on Notifications", + "name": "PLAY_BUZZER", + "options": [ + { + "label": "Yes", + "value": "Yes" + }, + { + "label": "No", + "value": "No" + } + ], + "source": "craftbeerpi", "type": "select", "value": "No" }, @@ -222,6 +265,7 @@ "value": "PSI" } ], + "source": "craftbeerpi", "type": "select", "value": "kPa" }, @@ -229,6 +273,7 @@ "description": "API path to creation plugin. Default: upload . CHANGE ONLY IF USING A RECIPE CREATION PLUGIN", "name": "RECIPE_CREATION_PATH", "options": null, + "source": "craftbeerpi", "type": "string", "value": "upload" }, @@ -236,6 +281,7 @@ "description": "Max. number of backup logs", "name": "SENSOR_LOG_BACKUP_COUNT", "options": null, + "source": "craftbeerpi", "type": "number", "value": 3 }, @@ -243,6 +289,7 @@ "description": "Max. number of bytes in sensor logs", "name": "SENSOR_LOG_MAX_BYTES", "options": null, + "source": "craftbeerpi", "type": "number", "value": 100000 }, @@ -259,6 +306,7 @@ "value": "F" } ], + "source": "craftbeerpi", "type": "select", "value": "C" }, @@ -266,6 +314,7 @@ "description": "Brewfather API Key", "name": "brewfather_api_key", "options": null, + "source": "craftbeerpi", "type": "string", "value": "" }, @@ -273,6 +322,7 @@ "description": "Brewfather User ID", "name": "brewfather_user_id", "options": null, + "source": "craftbeerpi", "type": "string", "value": "" }, @@ -280,6 +330,7 @@ "description": "Number of current Dashboard", "name": "current_dashboard_number", "options": null, + "source": "hidden", "type": "number", "value": 1 }, @@ -328,6 +379,7 @@ "value": 10 } ], + "source": "craftbeerpi", "type": "select", "value": 4 }, @@ -344,6 +396,7 @@ "value": "No" } ], + "source": "craftbeerpi", "type": "select", "value": "Yes" }, @@ -351,6 +404,7 @@ "description": "Boil step type", "name": "steps_boil", "options": null, + "source": "steps", "type": "step", "value": "BoilStep" }, @@ -358,6 +412,7 @@ "description": "Default Boil Temperature for Recipe Creation", "name": "steps_boil_temp", "options": null, + "source": "steps", "type": "number", "value": "99" }, @@ -365,6 +420,7 @@ "description": "Cooldown step type", "name": "steps_cooldown", "options": null, + "source": "steps", "type": "step", "value": "CooldownStep" }, @@ -372,6 +428,7 @@ "description": "Actor to trigger cooldown water on and off (default: None)", "name": "steps_cooldown_actor", "options": null, + "source": "steps", "type": "actor", "value": "" }, @@ -379,6 +436,7 @@ "description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)", "name": "steps_cooldown_sensor", "options": null, + "source": "steps", "type": "sensor", "value": "" }, @@ -386,6 +444,7 @@ "description": "Cooldown temp will send notification when this temeprature is reached", "name": "steps_cooldown_temp", "options": null, + "source": "steps", "type": "number", "value": 35 }, @@ -393,6 +452,7 @@ "description": "Mash step type", "name": "steps_mash", "options": null, + "source": "steps", "type": "step", "value": "MashStep" }, @@ -400,6 +460,7 @@ "description": "MashIn step type", "name": "steps_mashin", "options": null, + "source": "steps", "type": "step", "value": "MashInStep" }, @@ -407,6 +468,7 @@ "description": "MashOut step type", "name": "steps_mashout", "options": null, + "source": "steps", "type": "step", "value": "NotificationStep" } diff --git a/tests/cbpi-test-config/dashboard/cbpi_dashboard_1.json b/tests/cbpi-test-config/dashboard/cbpi_dashboard_1.json index 92079a0..b737cf7 100644 --- a/tests/cbpi-test-config/dashboard/cbpi_dashboard_1.json +++ b/tests/cbpi-test-config/dashboard/cbpi_dashboard_1.json @@ -1,3 +1,7 @@ { - "elements": [] + "config": {}, + "dbid": 1, + "type": "Test", + "x": 0, + "y": 0 } \ No newline at end of file From 639efe72c496bda5836cee3ba133c721bbc6bf24 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 16 Apr 2023 17:48:19 +0200 Subject: [PATCH 19/34] added update parameter to list --- cbpi/__init__.py | 2 +- cbpi/controller/config_controller.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 4d52643..9843a78 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a12" +__version__ = "4.1.8.a13" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index 16c7c02..d63685a 100644 --- a/cbpi/controller/config_controller.py +++ b/cbpi/controller/config_controller.py @@ -87,7 +87,7 @@ class ConfigController: if test == []: update=self.get(str(value.source)+'_update') if update: - logging.warning(update) + result[str(value.source)+'_update']={"value": update} if remove: await self.remove(str(value.source)+'_update') if remove: From 9f2f4c87c7b7063f7dca5f8abd961ee0d6dd591a Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 16 Apr 2023 17:56:00 +0200 Subject: [PATCH 20/34] changed api get all config parameters --- cbpi/__init__.py | 2 +- cbpi/controller/config_controller.py | 1 - cbpi/http_endpoints/http_config.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 9843a78..273ff68 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a13" +__version__ = "4.1.8.a14" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index d63685a..d56fe50 100644 --- a/cbpi/controller/config_controller.py +++ b/cbpi/controller/config_controller.py @@ -31,7 +31,6 @@ class ConfigController: data = json.load(json_file) for key, value in data.items(): self.cache[key] = Config(name=value.get("name"), value=value.get("value"), description=value.get("description"), type=ConfigType(value.get("type", "string")), source=value.get("source", "craftbeerpi"), options=value.get("options", None)) - logging.error(self.cache) def get(self, name, default=None): self.logger.debug("GET CONFIG VALUE %s (default %s)" % (name, default)) diff --git a/cbpi/http_endpoints/http_config.py b/cbpi/http_endpoints/http_config.py index 8efd20f..d762c60 100644 --- a/cbpi/http_endpoints/http_config.py +++ b/cbpi/http_endpoints/http_config.py @@ -56,7 +56,7 @@ class ConfigHttpEndpoints: "200": description: successful operation """ - return web.json_response(self.controller.cache, dumps=json_dumps) + return web.json_response(self.controller.get_state(), dumps=json_dumps) @request_mapping(path="/{name}/", method="POST", auth_required=False) async def http_paramter(self, request) -> web.Response: From f01bdb94bde622d07bcca788ca18699688e6538a Mon Sep 17 00:00:00 2001 From: prash3r Date: Wed, 19 Apr 2023 14:07:26 +0200 Subject: [PATCH 21/34] implements the log_data() hook --- cbpi/controller/log_file_controller.py | 50 +++++++++++++++++++++----- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index c752216..9c61e85 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -13,6 +13,7 @@ from cbpi.api import * from cbpi.api.config import ConfigType from cbpi.api.base import CBPiBase import asyncio +import shortuuid class LogController: @@ -28,28 +29,59 @@ class LogController: self.datalogger = {} self.logsFolderPath = self.cbpi.config_folder.logsFolderPath self.logger.info("Log folder path : " + self.logsFolderPath) + self.sensor_data_listeners = {} + + def add_sensor_data_listener(self, method): + listener_id = shortuuid.uuid() + self.sensor_data_listeners[listener_id] = method + return listener_id + + def remove_sensor_data_listener(self, listener_id): + try: + del self.sensor_data_listener[listener_id] + except: + self.logger.error("Failed to remove listener {}".format(listener_id)) - def log_data(self, name: str, value: str) -> None: + async def _call_sensor_data_listeners(self, id, value, formatted_time, name): + for id, method in self.sensor_data_listeners.items(): + asyncio.create_task(method(self.cbpi, id, value, formatted_time, name)) + + def log_data(self, id: str, value: str) -> None: + # check which default log targets are enabled: self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes") self.influxdb = self.cbpi.config.get("INFLUXDB", "No") + formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) + # ^^ both legacy log targets should probably be implemented as a core plugin each unsing the hook instead + + # CSV target: if self.logfiles == "Yes": - if name not in self.datalogger: + if id not in self.datalogger: max_bytes = int(self.cbpi.config.get("SENSOR_LOG_MAX_BYTES", 100000)) backup_count = int(self.cbpi.config.get("SENSOR_LOG_BACKUP_COUNT", 3)) - data_logger = logging.getLogger('cbpi.sensor.%s' % name) + data_logger = logging.getLogger('cbpi.sensor.%s' % id) data_logger.propagate = False data_logger.setLevel(logging.DEBUG) - handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{name}.log"), maxBytes=max_bytes, backupCount=backup_count) + handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{id}.log"), maxBytes=max_bytes, backupCount=backup_count) data_logger.addHandler(handler) - self.datalogger[name] = data_logger - - formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) - self.datalogger[name].info("%s,%s" % (formatted_time, str(value))) + self.datalogger[id] = data_logger + self.datalogger[id].info("%s,%s" % (formatted_time, str(value))) + + # influx target: if self.influxdb == "Yes": ## Write to influxdb in an asyncio task - self._task = asyncio.create_task(self.log_influx(name,value)) + self._task = asyncio.create_task(self.log_influx(id,value)) + + # all plugin targets: + if self.sensor_data_listeners: # true if there are listners + try: + sensor=self.cbpi.sensor.find_by_id(id) + if sensor is not None: + name = sensor.name.replace(" ", "_") + asyncio.create_task(self._call_sensor_data_listeners(id, value, formatted_time, name)) + except Exception as e: + logging.error("sensor logging listener exception: {}".format(e)) async def log_influx(self, name:str, value:str): self.influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", "No") From 4eebb17291442213870a68eaf86fdf7f51d76288 Mon Sep 17 00:00:00 2001 From: prash3r Date: Wed, 19 Apr 2023 16:47:48 +0200 Subject: [PATCH 22/34] moves influxDB integration into extensions folder --- cbpi/controller/log_file_controller.py | 46 +---------- .../SensorLogTarget_InfluxDB/__init__.py | 78 +++++++++++++++++++ .../SensorLogTarget_InfluxDB/config.yaml | 3 + 3 files changed, 82 insertions(+), 45 deletions(-) create mode 100644 cbpi/extension/SensorLogTarget_InfluxDB/__init__.py create mode 100644 cbpi/extension/SensorLogTarget_InfluxDB/config.yaml diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index 9c61e85..b55c7eb 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -49,7 +49,6 @@ class LogController: def log_data(self, id: str, value: str) -> None: # check which default log targets are enabled: self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes") - self.influxdb = self.cbpi.config.get("INFLUXDB", "No") formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) # ^^ both legacy log targets should probably be implemented as a core plugin each unsing the hook instead @@ -68,11 +67,6 @@ class LogController: self.datalogger[id].info("%s,%s" % (formatted_time, str(value))) - # influx target: - if self.influxdb == "Yes": - ## Write to influxdb in an asyncio task - self._task = asyncio.create_task(self.log_influx(id,value)) - # all plugin targets: if self.sensor_data_listeners: # true if there are listners try: @@ -81,45 +75,7 @@ class LogController: name = sensor.name.replace(" ", "_") asyncio.create_task(self._call_sensor_data_listeners(id, value, formatted_time, name)) except Exception as e: - logging.error("sensor logging listener exception: {}".format(e)) - - async def log_influx(self, name:str, value:str): - self.influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", "No") - self.influxdbaddr = self.cbpi.config.get("INFLUXDBADDR", None) - self.influxdbname = self.cbpi.config.get("INFLUXDBNAME", None) - self.influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None) - self.influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None) - self.influxdbmeasurement = self.cbpi.config.get("INFLUXDBMEASUREMENT", "measurement") - id = name - timeout = Timeout(connect=5.0, read=None) - try: - sensor=self.cbpi.sensor.find_by_id(name) - if sensor is not None: - itemname=sensor.name.replace(" ", "_") - out=str(self.influxdbmeasurement)+",source=" + itemname + ",itemID=" + str(id) + " value="+str(value) - except Exception as e: - logging.error("InfluxDB ID Error: {}".format(e)) - - if self.influxdbcloud == "Yes": - self.influxdburl=self.influxdbaddr + "/api/v2/write?org=" + self.influxdbuser + "&bucket=" + self.influxdbname + "&precision=s" - try: - header = {'User-Agent': name, 'Authorization': "Token {}".format(self.influxdbpwd)} - http = PoolManager(timeout=timeout) - req = http.request('POST',self.influxdburl, body=out.encode(), headers = header) - except Exception as e: - logging.error("InfluxDB cloud write Error: {}".format(e)) - - else: - self.base64string = base64.b64encode(('%s:%s' % (self.influxdbuser,self.influxdbpwd)).encode()) - self.influxdburl= self.influxdbaddr + '/write?db=' + self.influxdbname - try: - header = {'User-Agent': name, 'Content-Type': 'application/x-www-form-urlencoded','Authorization': 'Basic %s' % self.base64string.decode('utf-8')} - http = PoolManager(timeout=timeout) - req = http.request('POST',self.influxdburl, body=out.encode(), headers = header) - except Exception as e: - logging.error("InfluxDB write Error: {}".format(e)) - - + logging.error("sensor logging listener exception: {}".format(e)) async def get_data(self, names, sample_rate='60s'): logging.info("Start Log for {}".format(names)) diff --git a/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py new file mode 100644 index 0000000..2a35d67 --- /dev/null +++ b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py @@ -0,0 +1,78 @@ + +# -*- coding: utf-8 -*- +import os +from urllib3 import Timeout, PoolManager +import logging +from unittest.mock import MagicMock, patch +import asyncio +import random +from cbpi.api import * +from cbpi.api.config import ConfigType +import urllib3 +import base64 + +logger = logging.getLogger(__name__) + +# ToDo: +# - make log_data(id, value) to use id explicitly so there is no abiguity +# - create data legend for listener method call parameters including id, value, timestamp, name, cleanname +# - clean up data preperations for universal use +# - move influxDB logic to the plugin +# - + +class SensorLogTargetInfluxDB(CBPiExtension): + + def __init__(self, cbpi): # called from cbpi on start + self.cbpi = cbpi + self.influxdb = self.cbpi.config.get("INFLUXDB", "No") + if self.influxdb == "No": + return # never run() + self._task = asyncio.create_task(self.run()) # one time run() only + + + async def run(self): # called by __init__ once on start if influx is enabled + self.listener_ID = self.cbpi.log.add_sensor_data_listener(self.log_data_to_InfluxDB) + logger.info("InfluxDB sensor log target listener ID: {}".format(self.listener_ID)) + + async def log_data_to_InfluxDB(self, cbpi, id:str, value:str, timestamp, name): # called by log_data() hook from the log file controller + self.influxdb = self.cbpi.config.get("INFLUXDB", "No") + if self.influxdb == "No": + # We intentionally do not unsubscribe the listener here because then we had no way of resubscribing him without a restart of cbpi + # as long as cbpi was STARTED with INFLUXDB set to Yes this function is still subscribed, so changes can be made on the fly. + return + self.influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", "No") + self.influxdbaddr = self.cbpi.config.get("INFLUXDBADDR", None) + self.influxdbname = self.cbpi.config.get("INFLUXDBNAME", None) + self.influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None) + self.influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None) + self.influxdbmeasurement = self.cbpi.config.get("INFLUXDBMEASUREMENT", "measurement") + timeout = Timeout(connect=5.0, read=None) + try: + sensor=self.cbpi.sensor.find_by_id(id) + if sensor is not None: + itemname=sensor.name.replace(" ", "_") + out=str(self.influxdbmeasurement)+",source=" + itemname + ",itemID=" + str(id) + " value="+str(value) + except Exception as e: + logging.error("InfluxDB ID Error: {}".format(e)) + + if self.influxdbcloud == "Yes": + self.influxdburl=self.influxdbaddr + "/api/v2/write?org=" + self.influxdbuser + "&bucket=" + self.influxdbname + "&precision=s" + try: + header = {'User-Agent': id, 'Authorization': "Token {}".format(self.influxdbpwd)} + http = PoolManager(timeout=timeout) + req = http.request('POST',self.influxdburl, body=out.encode(), headers = header) + except Exception as e: + logging.error("InfluxDB cloud write Error: {}".format(e)) + + else: + self.base64string = base64.b64encode(('%s:%s' % (self.influxdbuser,self.influxdbpwd)).encode()) + self.influxdburl= self.influxdbaddr + '/write?db=' + self.influxdbname + try: + header = {'User-Agent': id, 'Content-Type': 'application/x-www-form-urlencoded','Authorization': 'Basic %s' % self.base64string.decode('utf-8')} + http = PoolManager(timeout=timeout) + req = http.request('POST',self.influxdburl, body=out.encode(), headers = header) + except Exception as e: + logging.error("InfluxDB write Error: {}".format(e)) + +def setup(cbpi): + cbpi.plugin.register("SensorLogTargetInfluxDB", SensorLogTargetInfluxDB) diff --git a/cbpi/extension/SensorLogTarget_InfluxDB/config.yaml b/cbpi/extension/SensorLogTarget_InfluxDB/config.yaml new file mode 100644 index 0000000..13cb8df --- /dev/null +++ b/cbpi/extension/SensorLogTarget_InfluxDB/config.yaml @@ -0,0 +1,3 @@ +name: SensorLogTargetInfluxDB +version: 4 +active: true From 384a8d5422d6a1e6d5ab165206c1127442aeeb6c Mon Sep 17 00:00:00 2001 From: prash3r Date: Wed, 19 Apr 2023 17:33:18 +0200 Subject: [PATCH 23/34] moves CSV Sensor logging into core extension --- cbpi/controller/log_file_controller.py | 27 +--------- cbpi/extension/ConfigUpdate/__init__.py | 4 +- .../extension/SensorLogTarget_CSV/__init__.py | 52 +++++++++++++++++++ .../extension/SensorLogTarget_CSV/config.yaml | 3 ++ .../SensorLogTarget_InfluxDB/__init__.py | 1 + 5 files changed, 60 insertions(+), 27 deletions(-) create mode 100644 cbpi/extension/SensorLogTarget_CSV/__init__.py create mode 100644 cbpi/extension/SensorLogTarget_CSV/config.yaml diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index b55c7eb..634f110 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -46,33 +46,14 @@ class LogController: for id, method in self.sensor_data_listeners.items(): asyncio.create_task(method(self.cbpi, id, value, formatted_time, name)) - def log_data(self, id: str, value: str) -> None: - # check which default log targets are enabled: - self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes") - formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) - # ^^ both legacy log targets should probably be implemented as a core plugin each unsing the hook instead - - # CSV target: - if self.logfiles == "Yes": - if id not in self.datalogger: - max_bytes = int(self.cbpi.config.get("SENSOR_LOG_MAX_BYTES", 100000)) - backup_count = int(self.cbpi.config.get("SENSOR_LOG_BACKUP_COUNT", 3)) - - data_logger = logging.getLogger('cbpi.sensor.%s' % id) - data_logger.propagate = False - data_logger.setLevel(logging.DEBUG) - handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{id}.log"), maxBytes=max_bytes, backupCount=backup_count) - data_logger.addHandler(handler) - self.datalogger[id] = data_logger - - self.datalogger[id].info("%s,%s" % (formatted_time, str(value))) - + def log_data(self, id: str, value: str) -> None: # all plugin targets: if self.sensor_data_listeners: # true if there are listners try: sensor=self.cbpi.sensor.find_by_id(id) if sensor is not None: name = sensor.name.replace(" ", "_") + formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) asyncio.create_task(self._call_sensor_data_listeners(id, value, formatted_time, name)) except Exception as e: logging.error("sensor logging listener exception: {}".format(e)) @@ -171,10 +152,6 @@ class LogController: def clear_log(self, name:str ) -> str: all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*")) - if name in self.datalogger: - self.datalogger[name].removeHandler(self.datalogger[name].handlers[0]) - del self.datalogger[name] - for f in all_filenames: try: os.remove(f) diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index 0aab218..ebc7a85 100644 --- a/cbpi/extension/ConfigUpdate/__init__.py +++ b/cbpi/extension/ConfigUpdate/__init__.py @@ -205,7 +205,7 @@ class ConfigUpdate(CBPiExtension): if logfiles is None: logger.info("INIT CSV logfiles") try: - await self.cbpi.config.add("CSVLOGFILES", "Yes", ConfigType.SELECT, "Write sensor data to csv logfiles", + await self.cbpi.config.add("CSVLOGFILES", "Yes", ConfigType.SELECT, "Write sensor data to csv logfiles (enabling requires restart)", [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: @@ -215,7 +215,7 @@ class ConfigUpdate(CBPiExtension): if influxdb is None: logger.info("INIT Influxdb") try: - await self.cbpi.config.add("INFLUXDB", "No", ConfigType.SELECT, "Write sensor data to influxdb", + await self.cbpi.config.add("INFLUXDB", "No", ConfigType.SELECT, "Write sensor data to influxdb (enabling requires restart)", [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: diff --git a/cbpi/extension/SensorLogTarget_CSV/__init__.py b/cbpi/extension/SensorLogTarget_CSV/__init__.py new file mode 100644 index 0000000..6983021 --- /dev/null +++ b/cbpi/extension/SensorLogTarget_CSV/__init__.py @@ -0,0 +1,52 @@ + +# -*- coding: utf-8 -*- +import os +from logging.handlers import RotatingFileHandler +import logging +from unittest.mock import MagicMock, patch +import asyncio +import random +from cbpi.api import * +from cbpi.api.config import ConfigType +import urllib3 +import base64 + +logger = logging.getLogger(__name__) + +class SensorLogTargetCSV(CBPiExtension): + + def __init__(self, cbpi): # called from cbpi on start + self.cbpi = cbpi + self.datalogger = {} + self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes") + if self.logfiles == "No": + return # never run() + self._task = asyncio.create_task(self.run()) # one time run() only + + + async def run(self): # called by __init__ once on start if CSV is enabled + self.listener_ID = self.cbpi.log.add_sensor_data_listener(self.log_data_to_CSV) + logger.info("CSV sensor log target listener ID: {}".format(self.listener_ID)) + + async def log_data_to_CSV(self, cbpi, id:str, value:str, formatted_time, name): # called by log_data() hook from the log file controller + self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes") + if self.logfiles == "No": + # We intentionally do not unsubscribe the listener here because then we had no way of resubscribing him without a restart of cbpi + # as long as cbpi was STARTED with CSVLOGFILES set to Yes this function is still subscribed, so changes can be made on the fly. + # but after initially enabling this logging target a restart is required. + return + if id not in self.datalogger: + max_bytes = int(self.cbpi.config.get("SENSOR_LOG_MAX_BYTES", 100000)) + backup_count = int(self.cbpi.config.get("SENSOR_LOG_BACKUP_COUNT", 3)) + + data_logger = logging.getLogger('cbpi.sensor.%s' % id) + data_logger.propagate = False + data_logger.setLevel(logging.DEBUG) + handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{id}.log"), maxBytes=max_bytes, backupCount=backup_count) + data_logger.addHandler(handler) + self.datalogger[id] = data_logger + + self.datalogger[id].info("%s,%s" % (formatted_time, str(value))) + +def setup(cbpi): + cbpi.plugin.register("SensorLogTargetCSV", SensorLogTargetCSV) diff --git a/cbpi/extension/SensorLogTarget_CSV/config.yaml b/cbpi/extension/SensorLogTarget_CSV/config.yaml new file mode 100644 index 0000000..d396a0c --- /dev/null +++ b/cbpi/extension/SensorLogTarget_CSV/config.yaml @@ -0,0 +1,3 @@ +name: SensorLogTargetCSV +version: 4 +active: true diff --git a/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py index 2a35d67..44d68f9 100644 --- a/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py +++ b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py @@ -39,6 +39,7 @@ class SensorLogTargetInfluxDB(CBPiExtension): if self.influxdb == "No": # We intentionally do not unsubscribe the listener here because then we had no way of resubscribing him without a restart of cbpi # as long as cbpi was STARTED with INFLUXDB set to Yes this function is still subscribed, so changes can be made on the fly. + # but after initially enabling this logging target a restart is required. return self.influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", "No") self.influxdbaddr = self.cbpi.config.get("INFLUXDBADDR", None) From e483ef6287b134f37d7a379ab5bb5d8468a55fc4 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Fri, 21 Apr 2023 07:29:05 +0200 Subject: [PATCH 24/34] update influx parameter description and remove port parameter automatically --- cbpi/__init__.py | 2 +- cbpi/extension/ConfigUpdate/__init__.py | 37 ++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 273ff68..fbaef73 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.8.a14" +__version__ = "4.1.8.a15" __codename__ = "Groundhog Day" diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index 56eefab..419e523 100644 --- a/cbpi/extension/ConfigUpdate/__init__.py +++ b/cbpi/extension/ConfigUpdate/__init__.py @@ -45,6 +45,7 @@ class ConfigUpdate(CBPiExtension): influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None) influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None) influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", None) + influxdbport = self.cbpi.config.get("INFLUXDBPORT", None) influxdbmeasurement = self.cbpi.config.get("INFLUXDBMEASUREMENT", None) mqttupdate = self.cbpi.config.get("MQTTUpdate", None) PRESSURE_UNIT = self.cbpi.config.get("PRESSURE_UNIT", None) @@ -295,13 +296,29 @@ class ConfigUpdate(CBPiExtension): except: logger.warning('Unable to update config') + + ## Check if influxdbport is in config and remove it as it is obsolete + if influxdbport is not None: + logger.warning("Remove obsolete Influxdbport config parameter") + try: + await self.cbpi.config.remove("INFLUXDBPORT") + except: + logger.warning('Unable to update config') + + ## Check if influxdbaddr is in config if influxdbaddr is None: logger.info("INIT Influxdbaddr") try: - await self.cbpi.config.add("INFLUXDBADDR", "http://localhost:8086", type=ConfigType.STRING, description="URL Address of your influxdb server (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)", source="craftbeerpi") + await self.cbpi.config.add("INFLUXDBADDR", "http://localhost:8086", type=ConfigType.STRING, description="URL Address of your influxdb server incl. http:// and port, e.g. http://localhost:8086 (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)", source="craftbeerpi") except: logger.warning('Unable to update config') + else: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: + try: + await self.cbpi.config.add("INFLUXDBADDR", influxdbaddr, type=ConfigType.STRING, description="URL Address of your influxdb server incl. http:// and port, e.g. http://localhost:8086 (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)", source="craftbeerpi") + except: + logger.warning('Unable to update config') ## Check if influxdbname is in config if influxdbname is None: @@ -310,6 +327,12 @@ class ConfigUpdate(CBPiExtension): await self.cbpi.config.add("INFLUXDBNAME", "cbpi4", type=ConfigType.STRING, description="Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)", source="craftbeerpi") except: logger.warning('Unable to update config') + else: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: + try: + await self.cbpi.config.add("INFLUXDBNAME", influxdbname, type=ConfigType.STRING, description="Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)", source="craftbeerpi") + except: + logger.warning('Unable to update config') ## Check if influxduser is in config if influxdbuser is None: @@ -318,6 +341,12 @@ class ConfigUpdate(CBPiExtension): await self.cbpi.config.add("INFLUXDBUSER", " ", type=ConfigType.STRING, description="User name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)", source="craftbeerpi") except: logger.warning('Unable to update config') + else: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: + try: + await self.cbpi.config.add("INFLUXDBUSER", influxdbuser, type=ConfigType.STRING, description="User Name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)", source="craftbeerpi") + except: + logger.warning('Unable to update config') ## Check if influxdpwd is in config if influxdbpwd is None: @@ -326,6 +355,12 @@ class ConfigUpdate(CBPiExtension): await self.cbpi.config.add("INFLUXDBPWD", " ", type=ConfigType.STRING, description="Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)", source="craftbeerpi") except: logger.warning('Unable to update config') + else: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: + try: + await self.cbpi.config.add("INFLUXDBPWD", influxdbpwd, type=ConfigType.STRING, description="Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)", source="craftbeerpi") + except: + logger.warning('Unable to update config') ## Check if influxdb cloud is on config if influxdbcloud is None: From 7fd6361d3036394f5df790fc8418d34ac6bd670e Mon Sep 17 00:00:00 2001 From: prash3r Date: Sun, 14 May 2023 15:55:41 +0200 Subject: [PATCH 25/34] hookable log data debugging fixes --- cbpi/controller/log_file_controller.py | 6 +++--- cbpi/extension/SensorLogTarget_CSV/__init__.py | 2 +- cbpi/extension/SensorLogTarget_InfluxDB/__init__.py | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index 634f110..14a9c02 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -42,9 +42,9 @@ class LogController: except: self.logger.error("Failed to remove listener {}".format(listener_id)) - async def _call_sensor_data_listeners(self, id, value, formatted_time, name): - for id, method in self.sensor_data_listeners.items(): - asyncio.create_task(method(self.cbpi, id, value, formatted_time, name)) + async def _call_sensor_data_listeners(self, sensor_id, value, formatted_time, name): + for listener_id, method in self.sensor_data_listeners.items(): + asyncio.create_task(method(self.cbpi, sensor_id, value, formatted_time, name)) def log_data(self, id: str, value: str) -> None: # all plugin targets: diff --git a/cbpi/extension/SensorLogTarget_CSV/__init__.py b/cbpi/extension/SensorLogTarget_CSV/__init__.py index 6983021..8fbb468 100644 --- a/cbpi/extension/SensorLogTarget_CSV/__init__.py +++ b/cbpi/extension/SensorLogTarget_CSV/__init__.py @@ -42,7 +42,7 @@ class SensorLogTargetCSV(CBPiExtension): data_logger = logging.getLogger('cbpi.sensor.%s' % id) data_logger.propagate = False data_logger.setLevel(logging.DEBUG) - handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{id}.log"), maxBytes=max_bytes, backupCount=backup_count) + handler = RotatingFileHandler(os.path.join(self.cbpi.log.logsFolderPath, f"sensor_{id}.log"), maxBytes=max_bytes, backupCount=backup_count) data_logger.addHandler(handler) self.datalogger[id] = data_logger diff --git a/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py index 44d68f9..879e057 100644 --- a/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py +++ b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py @@ -62,6 +62,8 @@ class SensorLogTargetInfluxDB(CBPiExtension): header = {'User-Agent': id, 'Authorization': "Token {}".format(self.influxdbpwd)} http = PoolManager(timeout=timeout) req = http.request('POST',self.influxdburl, body=out.encode(), headers = header) + if req.status != 204: + raise Exception(f'InfluxDB Status code {req.status}') except Exception as e: logging.error("InfluxDB cloud write Error: {}".format(e)) @@ -72,6 +74,8 @@ class SensorLogTargetInfluxDB(CBPiExtension): header = {'User-Agent': id, 'Content-Type': 'application/x-www-form-urlencoded','Authorization': 'Basic %s' % self.base64string.decode('utf-8')} http = PoolManager(timeout=timeout) req = http.request('POST',self.influxdburl, body=out.encode(), headers = header) + if req.status != 204: + raise Exception(f'InfluxDB Status code {req.status}') except Exception as e: logging.error("InfluxDB write Error: {}".format(e)) From 125bd071628934029102890424720700a213fac0 Mon Sep 17 00:00:00 2001 From: prash3r Date: Sun, 14 May 2023 17:43:48 +0200 Subject: [PATCH 26/34] modified tests for hookable log data --- tests/cbpi-test-config/config.json | 14 +++----------- tests/cbpi-test-config/sensor.json | 10 +++++++++- tests/test_logger.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/cbpi-test-config/config.json b/tests/cbpi-test-config/config.json index da7fab4..298b8b0 100644 --- a/tests/cbpi-test-config/config.json +++ b/tests/cbpi-test-config/config.json @@ -80,7 +80,7 @@ "options": null, "source": "hidden", "type": "string", - "value": "4.1.8.a11" + "value": "4.1.10.a1" }, "CSVLOGFILES": { "description": "Write sensor data to csv logfiles", @@ -117,7 +117,7 @@ "value": "No" }, "INFLUXDBADDR": { - "description": "IP Address of your influxdb server (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)", + "description": "URL Address of your influxdb server incl. http:// and port, e.g. http://localhost:8086 (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)", "name": "INFLUXDBADDR", "options": null, "source": "craftbeerpi", @@ -157,14 +157,6 @@ "type": "string", "value": "cbpi4" }, - "INFLUXDBPORT": { - "description": "Port of your influxdb server", - "name": "INFLUXDBPORT", - "options": null, - "source": "craftbeerpi", - "type": "string", - "value": "8086" - }, "INFLUXDBPWD": { "description": "Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)", "name": "INFLUXDBPWD", @@ -174,7 +166,7 @@ "value": " " }, "INFLUXDBUSER": { - "description": "User name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)", + "description": "User Name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)", "name": "INFLUXDBUSER", "options": null, "source": "craftbeerpi", diff --git a/tests/cbpi-test-config/sensor.json b/tests/cbpi-test-config/sensor.json index ce96464..4cf8966 100644 --- a/tests/cbpi-test-config/sensor.json +++ b/tests/cbpi-test-config/sensor.json @@ -1,3 +1,11 @@ { - "data": [] + "data": [ + { + "id": "unconfigured_test_sensor_ID", + "name": "unconfigured_mqtt_sensor", + "props": {}, + "state": false, + "type": "MQTTSensor" + } + ] } \ No newline at end of file diff --git a/tests/test_logger.py b/tests/test_logger.py index 50d729b..633ba00 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -10,10 +10,10 @@ class LoggerTestCase(CraftBeerPiTestCase): async def test_log_data(self): os.makedirs(os.path.join(".", "tests", "logs"), exist_ok=True) - log_name = "test" + log_name = "unconfigured_test_sensor_ID" #clear all logs self.cbpi.log.clear_log(log_name) - assert len(glob.glob(os.path.join(".", "tests", "logs", f"sensor_{log_name}.log*"))) == 0 + assert len(glob.glob(os.path.join(self.cbpi.log.logsFolderPath, f"sensor_{log_name}.log*"))) == 0 # write log entries for i in range(5): From 955409d81aa946e84ba024601188d7fe5c6b91e0 Mon Sep 17 00:00:00 2001 From: prash3r Date: Tue, 16 May 2023 12:23:24 +0200 Subject: [PATCH 27/34] repaires log clearence from analytics page --- .gitignore | 3 ++- .vscode/launch.json | 6 +++++- cbpi/controller/log_file_controller.py | 6 ++++++ cbpi/extension/SensorLogTarget_CSV/__init__.py | 7 +++---- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index d23da45..6157d3e 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ logs/ .coverage .devcontainer/cbpi-dev-config/* cbpi4-* -temp* \ No newline at end of file +temp* +*.patch \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index e24e1e9..0b0fdc4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,11 @@ "type": "python", "request": "launch", "module": "run", - "args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "start"], + "args": [ + "--config-folder-path=./.devcontainer/cbpi-dev-config", + "--debug-log-level=20", + "start" + ], "preLaunchTask": "copy default cbpi config files if dev config files dont exist" }, diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index 14a9c02..fecae47 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -151,6 +151,12 @@ class LogController: def clear_log(self, name:str ) -> str: all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*")) + + logging.info(f'Deleting logfiles for sensor {name}.') + + if name in self.datalogger: + self.datalogger[name].removeHandler(self.datalogger[name].handlers[0]) + del self.datalogger[name] for f in all_filenames: try: diff --git a/cbpi/extension/SensorLogTarget_CSV/__init__.py b/cbpi/extension/SensorLogTarget_CSV/__init__.py index 8fbb468..6360cf7 100644 --- a/cbpi/extension/SensorLogTarget_CSV/__init__.py +++ b/cbpi/extension/SensorLogTarget_CSV/__init__.py @@ -17,7 +17,6 @@ class SensorLogTargetCSV(CBPiExtension): def __init__(self, cbpi): # called from cbpi on start self.cbpi = cbpi - self.datalogger = {} self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes") if self.logfiles == "No": return # never run() @@ -35,7 +34,7 @@ class SensorLogTargetCSV(CBPiExtension): # as long as cbpi was STARTED with CSVLOGFILES set to Yes this function is still subscribed, so changes can be made on the fly. # but after initially enabling this logging target a restart is required. return - if id not in self.datalogger: + if id not in self.cbpi.log.datalogger: max_bytes = int(self.cbpi.config.get("SENSOR_LOG_MAX_BYTES", 100000)) backup_count = int(self.cbpi.config.get("SENSOR_LOG_BACKUP_COUNT", 3)) @@ -44,9 +43,9 @@ class SensorLogTargetCSV(CBPiExtension): data_logger.setLevel(logging.DEBUG) handler = RotatingFileHandler(os.path.join(self.cbpi.log.logsFolderPath, f"sensor_{id}.log"), maxBytes=max_bytes, backupCount=backup_count) data_logger.addHandler(handler) - self.datalogger[id] = data_logger + self.cbpi.log.datalogger[id] = data_logger - self.datalogger[id].info("%s,%s" % (formatted_time, str(value))) + self.cbpi.log.datalogger[id].info("%s,%s" % (formatted_time, str(value))) def setup(cbpi): cbpi.plugin.register("SensorLogTargetCSV", SensorLogTargetCSV) From 819c9f33b9831068ff5b69f0635969bf099dbfe0 Mon Sep 17 00:00:00 2001 From: prash3r Date: Tue, 16 May 2023 13:40:59 +0200 Subject: [PATCH 28/34] removes unnecessary comments previously used as ToDo-List --- cbpi/extension/SensorLogTarget_InfluxDB/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py index 879e057..cb9fe75 100644 --- a/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py +++ b/cbpi/extension/SensorLogTarget_InfluxDB/__init__.py @@ -13,13 +13,6 @@ import base64 logger = logging.getLogger(__name__) -# ToDo: -# - make log_data(id, value) to use id explicitly so there is no abiguity -# - create data legend for listener method call parameters including id, value, timestamp, name, cleanname -# - clean up data preperations for universal use -# - move influxDB logic to the plugin -# - - class SensorLogTargetInfluxDB(CBPiExtension): def __init__(self, cbpi): # called from cbpi on start From 9558f3eb6e8b2b8c4bda39b6fe2cc303460e79ca Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Wed, 17 May 2023 21:52:53 +0200 Subject: [PATCH 29/34] update config parameters with version change and bump version --- cbpi/__init__.py | 2 +- cbpi/extension/ConfigUpdate/__init__.py | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 97eaa16..c051562 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.10.a1" +__version__ = "4.1.10.a2" __codename__ = "Groundhog Day" diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index 62fee1b..39f3213 100644 --- a/cbpi/extension/ConfigUpdate/__init__.py +++ b/cbpi/extension/ConfigUpdate/__init__.py @@ -284,6 +284,16 @@ class ConfigUpdate(CBPiExtension): {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') + else: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: + try: + await self.cbpi.config.add("CSVLOGFILES", logfiles, type=ConfigType.SELECT, description="Write sensor data to csv logfiles (enabling requires restart)", + source="craftbeerpi", + options= [{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}]) + except: + logger.warning('Unable to update config') + ## Check if influxdb is on config if influxdb is None: @@ -295,7 +305,15 @@ class ConfigUpdate(CBPiExtension): {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') - + else: + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: + try: + await self.cbpi.config.add("INFLUXDB", influxdb, type=ConfigType.SELECT, description="Write sensor data to influxdb (enabling requires restart)", + source="craftbeerpi", + options= [{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}]) + except: + logger.warning('Unable to update config') ## Check if influxdbport is in config and remove it as it is obsolete if influxdbport is not None: From 0441b735030f9218e78948d2d99a1c811067ffa7 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Wed, 31 May 2023 07:05:58 +0200 Subject: [PATCH 30/34] bump to rc1 --- cbpi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index c051562..4ab5f6c 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.10.a2" +__version__ = "4.1.10.rc1" __codename__ = "Groundhog Day" From 8aa773db261e0a452958bc66315f2eae146ac9c6 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Wed, 31 May 2023 16:52:02 +0200 Subject: [PATCH 31/34] address dependabot alert #3 (https://github.com/PiBrewing/craftbeerpi4/security/dependabot/3) --- cbpi/__init__.py | 2 +- cbpi/cli.py | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 4ab5f6c..045ac09 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.10.rc1" +__version__ = "4.1.10.rc2" __codename__ = "Groundhog Day" diff --git a/cbpi/cli.py b/cbpi/cli.py index d2925c2..416883b 100644 --- a/cbpi/cli.py +++ b/cbpi/cli.py @@ -91,7 +91,7 @@ class CraftBeerPiCli(): print("Cant create Plugin. Folder {} already exists ".format(name)) return - url = 'https://github.com/Manuel83/craftbeerpi4-plugin-template/archive/main.zip' + url = 'https://github.com/PiBrewing/craftbeerpi4-plugin-template/archive/main.zip' r = requests.get(url) with open('temp.zip', 'wb') as f: f.write(r.content) diff --git a/requirements.txt b/requirements.txt index c02ae4a..bdcfc87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ aiohttp-swagger==1.0.16 aiojobs==1.1.0 aiosqlite==0.17.0 cryptography==40.0.1 -requests==2.28.1 +requests==2.31.0 voluptuous==0.13.1 pyfiglet==0.8.post1 pandas==1.5.3 diff --git a/setup.py b/setup.py index d9478a4..7a2159d 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ setup(name='cbpi4', "aiojobs==1.1.0 ", "aiosqlite==0.17.0", "cryptography==40.0.1", - "requests==2.28.1", + "requests==2.31.0", "voluptuous==0.13.1", "pyfiglet==0.8.post1", 'click==8.1.3', From eb646fc5293f93ce2a7b32dea47474477dda3c6b Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Mon, 5 Jun 2023 07:39:33 +0200 Subject: [PATCH 32/34] changed cryptography requirement due to dependabot alarm --- cbpi/__init__.py | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 045ac09..4ac1b43 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.10.rc2" +__version__ = "4.1.10.rc3" __codename__ = "Groundhog Day" diff --git a/requirements.txt b/requirements.txt index bdcfc87..6070db4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ aiohttp-session==2.12.0 aiohttp-swagger==1.0.16 aiojobs==1.1.0 aiosqlite==0.17.0 -cryptography==40.0.1 +cryptography==41.0.0 requests==2.31.0 voluptuous==0.13.1 pyfiglet==0.8.post1 diff --git a/setup.py b/setup.py index 7a2159d..aead252 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ setup(name='cbpi4', "aiohttp-swagger==1.0.16", "aiojobs==1.1.0 ", "aiosqlite==0.17.0", - "cryptography==40.0.1", + "cryptography==41.0.0", "requests==2.31.0", "voluptuous==0.13.1", "pyfiglet==0.8.post1", From af97dc88adc7961d5075a39bbe8357b2d39be1b9 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Mon, 5 Jun 2023 07:46:42 +0200 Subject: [PATCH 33/34] Revert "changed cryptography requirement due to dependabot alarm" This reverts commit eb646fc5293f93ce2a7b32dea47474477dda3c6b. --- cbpi/__init__.py | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 4ac1b43..045ac09 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.10.rc3" +__version__ = "4.1.10.rc2" __codename__ = "Groundhog Day" diff --git a/requirements.txt b/requirements.txt index 6070db4..bdcfc87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ aiohttp-session==2.12.0 aiohttp-swagger==1.0.16 aiojobs==1.1.0 aiosqlite==0.17.0 -cryptography==41.0.0 +cryptography==40.0.1 requests==2.31.0 voluptuous==0.13.1 pyfiglet==0.8.post1 diff --git a/setup.py b/setup.py index aead252..7a2159d 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ setup(name='cbpi4', "aiohttp-swagger==1.0.16", "aiojobs==1.1.0 ", "aiosqlite==0.17.0", - "cryptography==41.0.0", + "cryptography==40.0.1", "requests==2.31.0", "voluptuous==0.13.1", "pyfiglet==0.8.post1", From 1d9b27ed4bab87bbc7397d06dafae13dc1dc9291 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 10 Jun 2023 13:58:33 +0200 Subject: [PATCH 34/34] bump version to release --- cbpi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 045ac09..2a723f2 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.10.rc2" +__version__ = "4.1.10" __codename__ = "Groundhog Day"