diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 9109e84..97eaa16 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.9" +__version__ = "4.1.10.a1" __codename__ = "Groundhog Day" diff --git a/cbpi/api/base.py b/cbpi/api/base.py index 837b4a2..7e6365c 100644 --- a/cbpi/api/base.py +++ b/cbpi/api/base.py @@ -16,9 +16,12 @@ 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, 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 5007a64..eb545db 100644 --- a/cbpi/api/dataclasses.py +++ b/cbpi/api/dataclasses.py @@ -198,12 +198,13 @@ class Config: value: Any = None description: str = None type: ConfigType = ConfigType.STRING + source: str = None options: Any = 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, source=self.source, options=self.options) @dataclass class NotificationAction: 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/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)) diff --git a/cbpi/controller/config_controller.py b/cbpi/controller/config_controller.py index 6d62d79..d56fe50 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")), source=value.get("source", "craftbeerpi"), options=value.get("options", None)) def get(self, name, default=None): self.logger.debug("GET CONFIG VALUE %s (default %s)" % (name, default)) @@ -53,10 +50,46 @@ 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="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={} + 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") ) + success=True + except Exception as e: + print(e) + 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 + + async def obsolete(self, remove=False): + 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: + result[str(value.source)+'_update']={"value": 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/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index e958371..9bd32b4 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -215,3 +215,28 @@ class PluginController(): logger.error(e) return [] return result + + async def load_plugin_names(self, filter="cbpi"): + result = [] + 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) + 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) + 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 read metadata for plugin {} ".format(key)) + logger.error(e) + except Exception as e: + logger.error(e) + return result + return result diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index ebc7a85..62fee1b 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__) @@ -40,11 +41,11 @@ 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) 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) @@ -54,76 +55,113 @@ 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) + self.version=__version__ + + 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 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: 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 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: 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 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: 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 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: 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 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: 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 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: 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 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: 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 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: 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 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: 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", + source="craftbeerpi", + options= [{"label": "1", "value": 1}, {"label": "2", "value": 2}, {"label": "3", "value": 3}, {"label": "4", "value": 4}, @@ -133,80 +171,116 @@ class ConfigUpdate(CBPiExtension): {"label": "8", "value": 8}, {"label": "9", "value": 9}, {"label": "10", "value": 10}]) + 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') + else: + 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 - 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"}, + 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"}]) + 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", + source="steps", + options=[{"label": "Yes", "value": "Yes"}, + {"label": "No", "value": "No"}]) + ## 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"}, + 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"}]) + 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"}]) + ## 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", source="craftbeerpi") 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", source="craftbeerpi") 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", source="craftbeerpi") 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 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: + 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 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 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 (enabling requires restart)", - [{"label": "Yes", "value": "Yes"}, + await self.cbpi.config.add("CSVLOGFILES", "Yes", 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') @@ -215,50 +289,86 @@ 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 (enabling requires restart)", - [{"label": "Yes", "value": "Yes"}, + await self.cbpi.config.add("INFLUXDB", "No", 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: + 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", 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 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: 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)", 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: 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)", 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: 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)", 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: 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)", + source="craftbeerpi", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -267,15 +377,16 @@ 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)", source="craftbeerpi") 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", + source="craftbeerpi", + options= [{"label": "30", "value": 30}, {"label": "60", "value": 60}, {"label": "120", "value": 120}, {"label": "300", "value": 300}, @@ -287,8 +398,9 @@ 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", + source="craftbeerpi", + options= [{"label": "kPa", "value": "kPa"}, {"label": "PSI", "value": "PSI"}]) except: logger.warning('Unable to update config') @@ -297,7 +409,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", source="craftbeerpi") except: logger.warning('Unable to update database') @@ -305,7 +417,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", source="craftbeerpi") except: logger.warning('Unable to update database') @@ -313,8 +425,9 @@ 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", + source="craftbeerpi", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -323,8 +436,9 @@ 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", + source="craftbeerpi", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -333,8 +447,9 @@ 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", + source="craftbeerpi", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) except: logger.warning('Unable to update config') @@ -342,13 +457,33 @@ 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"}, + 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', + source="steps", + options= [{"label": "Yes", "value": "Yes"}, {"label": "No", "value": "No"}]) + BoilAutoTimer = self.cbpi.config.get("BoilAutoTimer", "No") except: logging.warning('Unable to update database') + else: + 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"}]) + + + + ## Check if influxdbname is in config + if CONFIG_STATUS is None or CONFIG_STATUS != self.version: + logger.warning("Setting Config Status") + try: + 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') + def setup(cbpi): cbpi.plugin.register("ConfigUpdate", ConfigUpdate) diff --git a/cbpi/http_endpoints/http_config.py b/cbpi/http_endpoints/http_config.py index 5e233d2..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: @@ -78,5 +78,55 @@ 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) + + @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: + """ + --- + description: Remove obsolete config parameters + tags: + - Config + responses: + "200": + description: successful operation + """ + await self.controller.obsolete(True) + return web.Response(status=200) \ No newline at end of file diff --git a/cbpi/http_endpoints/http_plugin.py b/cbpi/http_endpoints/http_plugin.py index fc3ae2c..27b5149 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 names(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) 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", 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