mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-21 22:48:16 +01:00
Merge pull request #111 from PiBrewing/development
Merge Development into master
This commit is contained in:
commit
e9c38f1010
24 changed files with 639 additions and 272 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -19,4 +19,5 @@ logs/
|
||||||
.coverage
|
.coverage
|
||||||
.devcontainer/cbpi-dev-config/*
|
.devcontainer/cbpi-dev-config/*
|
||||||
cbpi4-*
|
cbpi4-*
|
||||||
temp*
|
temp*
|
||||||
|
*.patch
|
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -10,7 +10,11 @@
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "run",
|
"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"
|
"preLaunchTask": "copy default cbpi config files if dev config files dont exist"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
__version__ = "4.1.7"
|
__version__ = "4.1.10"
|
||||||
__codename__ = "Groundhog Day"
|
__codename__ = "Groundhog Day"
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,12 @@ class CBPiBase(metaclass=ABCMeta):
|
||||||
|
|
||||||
async def set_config_value(self,name,value):
|
async def set_config_value(self,name,value):
|
||||||
return await self.cbpi.config.set(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):
|
async def add_config_value(self, name, value, type: ConfigType, description, source, options=None):
|
||||||
await self.cbpi.config.add(name, value, type, description, options=None)
|
await self.cbpi.config.add(name, value, type, description, source, options=None)
|
||||||
|
|
||||||
def get_kettle(self,id):
|
def get_kettle(self,id):
|
||||||
return self.cbpi.kettle.find_by_id(id)
|
return self.cbpi.kettle.find_by_id(id)
|
||||||
|
|
|
@ -198,12 +198,13 @@ class Config:
|
||||||
value: Any = None
|
value: Any = None
|
||||||
description: str = None
|
description: str = None
|
||||||
type: ConfigType = ConfigType.STRING
|
type: ConfigType = ConfigType.STRING
|
||||||
|
source: str = None
|
||||||
options: Any = None
|
options: Any = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "....name={} value={}".format(self.name, self.value)
|
return "....name={} value={}".format(self.name, self.value)
|
||||||
def to_dict(self):
|
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
|
@dataclass
|
||||||
class NotificationAction:
|
class NotificationAction:
|
||||||
|
|
|
@ -91,7 +91,7 @@ class CraftBeerPiCli():
|
||||||
print("Cant create Plugin. Folder {} already exists ".format(name))
|
print("Cant create Plugin. Folder {} already exists ".format(name))
|
||||||
return
|
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)
|
r = requests.get(url)
|
||||||
with open('temp.zip', 'wb') as f:
|
with open('temp.zip', 'wb') as f:
|
||||||
f.write(r.content)
|
f.write(r.content)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"name": "AUTHOR",
|
"name": "AUTHOR",
|
||||||
"options": null,
|
"options": null,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"source": "craftbeerpi",
|
||||||
"value": "John Doe"
|
"value": "John Doe"
|
||||||
},
|
},
|
||||||
"BREWERY_NAME": {
|
"BREWERY_NAME": {
|
||||||
|
@ -11,52 +12,9 @@
|
||||||
"name": "BREWERY_NAME",
|
"name": "BREWERY_NAME",
|
||||||
"options": null,
|
"options": null,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"source": "craftbeerpi",
|
||||||
"value": "CraftBeerPi Brewery"
|
"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": {
|
"TEMP_UNIT": {
|
||||||
"description": "Temperature Unit",
|
"description": "Temperature Unit",
|
||||||
"name": "TEMP_UNIT",
|
"name": "TEMP_UNIT",
|
||||||
|
@ -71,78 +29,9 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
"source": "craftbeerpi",
|
||||||
"value": "C"
|
"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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,19 +34,39 @@ class BasicController:
|
||||||
return self.resource(data.get("id"), data.get("name"), type=data.get("type"), props=Props(data.get("props", {})) )
|
return self.resource(data.get("id"), data.get("name"), type=data.get("type"), props=Props(data.get("props", {})) )
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
logging.info("{} Load ".format(self.name))
|
try:
|
||||||
with open(self.path) as json_file:
|
logging.info("{} Load ".format(self.name))
|
||||||
data = json.load(json_file)
|
with open(self.path) as json_file:
|
||||||
data['data'].sort(key=lambda x: x.get('name').upper())
|
data = json.load(json_file)
|
||||||
|
data['data'].sort(key=lambda x: x.get('name').upper())
|
||||||
|
|
||||||
for i in data["data"]:
|
for i in data["data"]:
|
||||||
self.data.append(self.create(i))
|
self.data.append(self.create(i))
|
||||||
|
|
||||||
if self.autostart is True:
|
if self.autostart is True:
|
||||||
for item in self.data:
|
for item in self.data:
|
||||||
logging.info("{} Starting ".format(self.name))
|
logging.info("{} Starting ".format(self.name))
|
||||||
await self.start(item.id)
|
await self.start(item.id)
|
||||||
await self.push_udpate()
|
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):
|
async def save(self):
|
||||||
logging.info("{} Save ".format(self.name))
|
logging.info("{} Save ".format(self.name))
|
||||||
|
|
|
@ -19,21 +19,18 @@ class ConfigController:
|
||||||
self.path_static = cbpi.config_folder.get_file_path("config.yaml")
|
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()))
|
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 = {}
|
result = {}
|
||||||
for key, value in self.cache.items():
|
for key, value in self.cache.items():
|
||||||
result[key] = value.to_dict()
|
result[key] = value.to_dict()
|
||||||
|
return result
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
async def init(self):
|
async def init(self):
|
||||||
self.static = load_config(self.path_static)
|
self.static = load_config(self.path_static)
|
||||||
with open(self.path) as json_file:
|
with open(self.path) as json_file:
|
||||||
data = json.load(json_file)
|
data = json.load(json_file)
|
||||||
for key, value in data.items():
|
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):
|
def get(self, name, default=None):
|
||||||
self.logger.debug("GET CONFIG VALUE %s (default %s)" % (name, default))
|
self.logger.debug("GET CONFIG VALUE %s (default %s)" % (name, default))
|
||||||
|
@ -53,10 +50,46 @@ class ConfigController:
|
||||||
with open(self.path, "w") as file:
|
with open(self.path, "w") as file:
|
||||||
json.dump(data, file, indent=4, sort_keys=True)
|
json.dump(data, file, indent=4, sort_keys=True)
|
||||||
|
|
||||||
async def add(self, name, value, type: ConfigType, description, options=None):
|
async def add(self, name, value, type: ConfigType, description, source="craftbeerpi", options=None):
|
||||||
self.cache[name] = Config(name,value,description,type,options)
|
self.cache[name] = Config(name,value,description,type,source,options)
|
||||||
data = {}
|
data = {}
|
||||||
for key, value in self.cache.items():
|
for key, value in self.cache.items():
|
||||||
data[key] = value.to_dict()
|
data[key] = value.to_dict()
|
||||||
with open(self.path, "w") as file:
|
with open(self.path, "w") as file:
|
||||||
json.dump(data, file, indent=4, sort_keys=True)
|
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
|
|
@ -13,6 +13,7 @@ from cbpi.api import *
|
||||||
from cbpi.api.config import ConfigType
|
from cbpi.api.config import ConfigType
|
||||||
from cbpi.api.base import CBPiBase
|
from cbpi.api.base import CBPiBase
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import shortuuid
|
||||||
|
|
||||||
|
|
||||||
class LogController:
|
class LogController:
|
||||||
|
@ -28,66 +29,34 @@ class LogController:
|
||||||
self.datalogger = {}
|
self.datalogger = {}
|
||||||
self.logsFolderPath = self.cbpi.config_folder.logsFolderPath
|
self.logsFolderPath = self.cbpi.config_folder.logsFolderPath
|
||||||
self.logger.info("Log folder path : " + self.logsFolderPath)
|
self.logger.info("Log folder path : " + self.logsFolderPath)
|
||||||
|
self.sensor_data_listeners = {}
|
||||||
def log_data(self, name: str, value: str) -> None:
|
|
||||||
self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes")
|
|
||||||
self.influxdb = self.cbpi.config.get("INFLUXDB", "No")
|
|
||||||
if self.logfiles == "Yes":
|
|
||||||
if name 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)
|
def add_sensor_data_listener(self, method):
|
||||||
data_logger.propagate = False
|
listener_id = shortuuid.uuid()
|
||||||
data_logger.setLevel(logging.DEBUG)
|
self.sensor_data_listeners[listener_id] = method
|
||||||
handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{name}.log"), maxBytes=max_bytes, backupCount=backup_count)
|
return listener_id
|
||||||
data_logger.addHandler(handler)
|
|
||||||
self.datalogger[name] = data_logger
|
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))
|
||||||
|
|
||||||
formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
|
async def _call_sensor_data_listeners(self, sensor_id, value, formatted_time, name):
|
||||||
self.datalogger[name].info("%s,%s" % (formatted_time, str(value)))
|
for listener_id, method in self.sensor_data_listeners.items():
|
||||||
|
asyncio.create_task(method(self.cbpi, sensor_id, value, formatted_time, name))
|
||||||
|
|
||||||
if self.influxdb == "Yes":
|
def log_data(self, id: str, value: str) -> None:
|
||||||
## Write to influxdb in an asyncio task
|
# all plugin targets:
|
||||||
self._task = asyncio.create_task(self.log_influx(name,value))
|
if self.sensor_data_listeners: # true if there are listners
|
||||||
|
|
||||||
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:
|
try:
|
||||||
sensor=self.cbpi.sensor.find_by_id(name)
|
sensor=self.cbpi.sensor.find_by_id(id)
|
||||||
if sensor is not None:
|
if sensor is not None:
|
||||||
itemname=sensor.name.replace(" ", "_")
|
name = sensor.name.replace(" ", "_")
|
||||||
out=str(self.influxdbmeasurement)+",source=" + itemname + ",itemID=" + str(id) + " value="+str(value)
|
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:
|
except Exception as e:
|
||||||
logging.error("InfluxDB ID Error: {}".format(e))
|
logging.error("sensor logging listener exception: {}".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))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def get_data(self, names, sample_rate='60s'):
|
async def get_data(self, names, sample_rate='60s'):
|
||||||
logging.info("Start Log for {}".format(names))
|
logging.info("Start Log for {}".format(names))
|
||||||
|
@ -182,7 +151,9 @@ class LogController:
|
||||||
|
|
||||||
def clear_log(self, name:str ) -> str:
|
def clear_log(self, name:str ) -> str:
|
||||||
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))
|
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:
|
if name in self.datalogger:
|
||||||
self.datalogger[name].removeHandler(self.datalogger[name].handlers[0])
|
self.datalogger[name].removeHandler(self.datalogger[name].handlers[0])
|
||||||
del self.datalogger[name]
|
del self.datalogger[name]
|
||||||
|
|
|
@ -215,3 +215,28 @@ class PluginController():
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
return []
|
return []
|
||||||
return result
|
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
|
||||||
|
|
|
@ -8,6 +8,7 @@ import json
|
||||||
from cbpi.api import *
|
from cbpi.api import *
|
||||||
from cbpi.api.config import ConfigType
|
from cbpi.api.config import ConfigType
|
||||||
from cbpi.api.base import CBPiBase
|
from cbpi.api.base import CBPiBase
|
||||||
|
from cbpi import __version__
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -40,11 +41,11 @@ class ConfigUpdate(CBPiExtension):
|
||||||
logfiles = self.cbpi.config.get("CSVLOGFILES", None)
|
logfiles = self.cbpi.config.get("CSVLOGFILES", None)
|
||||||
influxdb = self.cbpi.config.get("INFLUXDB", None)
|
influxdb = self.cbpi.config.get("INFLUXDB", None)
|
||||||
influxdbaddr = self.cbpi.config.get("INFLUXDBADDR", None)
|
influxdbaddr = self.cbpi.config.get("INFLUXDBADDR", None)
|
||||||
#influxdbport = self.cbpi.config.get("INFLUXDBPORT", None)
|
|
||||||
influxdbname = self.cbpi.config.get("INFLUXDBNAME", None)
|
influxdbname = self.cbpi.config.get("INFLUXDBNAME", None)
|
||||||
influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None)
|
influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None)
|
||||||
influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None)
|
influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None)
|
||||||
influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", None)
|
influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", None)
|
||||||
|
influxdbport = self.cbpi.config.get("INFLUXDBPORT", None)
|
||||||
influxdbmeasurement = self.cbpi.config.get("INFLUXDBMEASUREMENT", None)
|
influxdbmeasurement = self.cbpi.config.get("INFLUXDBMEASUREMENT", None)
|
||||||
mqttupdate = self.cbpi.config.get("MQTTUpdate", None)
|
mqttupdate = self.cbpi.config.get("MQTTUpdate", None)
|
||||||
PRESSURE_UNIT = self.cbpi.config.get("PRESSURE_UNIT", 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)
|
NOTIFY_ON_ERROR = self.cbpi.config.get("NOTIFY_ON_ERROR", None)
|
||||||
PLAY_BUZZER = self.cbpi.config.get("PLAY_BUZZER", None)
|
PLAY_BUZZER = self.cbpi.config.get("PLAY_BUZZER", None)
|
||||||
BoilAutoTimer = self.cbpi.config.get("BoilAutoTimer", 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:
|
if boil_temp is None:
|
||||||
logger.info("INIT Boil Temp Setting")
|
logger.info("INIT Boil Temp Setting")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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:
|
if cooldown_sensor is None:
|
||||||
logger.info("INIT Cooldown Sensor Setting")
|
logger.info("INIT Cooldown Sensor Setting")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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:
|
if cooldown_actor is None:
|
||||||
logger.info("INIT Cooldown Actor Setting")
|
logger.info("INIT Cooldown Actor Setting")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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:
|
if cooldown_temp is None:
|
||||||
logger.info("INIT Cooldown Temp Setting")
|
logger.info("INIT Cooldown Temp Setting")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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:
|
if cooldown_step is None:
|
||||||
logger.info("INIT Cooldown Step Type")
|
logger.info("INIT Cooldown Step Type")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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:
|
if mashin_step is None:
|
||||||
logger.info("INIT MashIn Step Type")
|
logger.info("INIT MashIn Step Type")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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:
|
if mash_step is None:
|
||||||
logger.info("INIT Mash Step Type")
|
logger.info("INIT Mash Step Type")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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:
|
if mashout_step is None:
|
||||||
logger.info("INIT MashOut Step Type")
|
logger.info("INIT MashOut Step Type")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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:
|
if boil_step is None:
|
||||||
logger.info("INIT Boil Step Type")
|
logger.info("INIT Boil Step Type")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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:
|
if max_dashboard_number is None:
|
||||||
logger.info("INIT Max Dashboard Numbers for multiple dashboards")
|
logger.info("INIT Max Dashboard Numbers for multiple dashboards")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add("max_dashboard_number", 4, ConfigType.SELECT, "Max Number of Dashboards",
|
await self.cbpi.config.add("max_dashboard_number", 4, type=ConfigType.SELECT, description="Max Number of Dashboards",
|
||||||
[{"label": "1", "value": 1},
|
source="craftbeerpi",
|
||||||
|
options= [{"label": "1", "value": 1},
|
||||||
{"label": "2", "value": 2},
|
{"label": "2", "value": 2},
|
||||||
{"label": "3", "value": 3},
|
{"label": "3", "value": 3},
|
||||||
{"label": "4", "value": 4},
|
{"label": "4", "value": 4},
|
||||||
|
@ -133,132 +171,222 @@ class ConfigUpdate(CBPiExtension):
|
||||||
{"label": "8", "value": 8},
|
{"label": "8", "value": 8},
|
||||||
{"label": "9", "value": 9},
|
{"label": "9", "value": 9},
|
||||||
{"label": "10", "value": 10}])
|
{"label": "10", "value": 10}])
|
||||||
|
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update database')
|
logger.warning('Unable to update database')
|
||||||
|
|
||||||
if current_dashboard_number is None:
|
if current_dashboard_number is None:
|
||||||
logger.info("INIT Current Dashboard Number")
|
logger.info("INIT Current Dashboard Number")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
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
|
## Check if AtuoMode for Steps is in config
|
||||||
AutoMode = self.cbpi.config.get("AutoMode", None)
|
|
||||||
if AutoMode is None:
|
if AutoMode is None:
|
||||||
logger.info("INIT AutoMode")
|
logger.info("INIT AutoMode")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add("AutoMode", "Yes", ConfigType.SELECT, "Use AutoMode in steps",
|
await self.cbpi.config.add("AutoMode", "Yes", type=ConfigType.SELECT, description="Use AutoMode in steps",
|
||||||
[{"label": "Yes", "value": "Yes"},
|
source="steps",
|
||||||
|
options=[{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
|
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
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
|
## Check if AddMashInStep for Steps is in config
|
||||||
AddMashIn = self.cbpi.config.get("AddMashInStep", None)
|
|
||||||
if AddMashIn is None:
|
if AddMashIn is None:
|
||||||
logger.info("INIT AddMashInStep")
|
logger.info("INIT AddMashInStep")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add("AddMashInStep", "Yes", ConfigType.SELECT, "Add MashIn Step automatically if not defined in recipe",
|
await self.cbpi.config.add("AddMashInStep", "Yes", type=ConfigType.SELECT, description= "Add MashIn Step automatically if not defined in recipe",
|
||||||
[{"label": "Yes", "value": "Yes"},
|
source="steps",
|
||||||
|
options = [{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
|
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
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
|
## Check if Brewfather UserID is in config
|
||||||
bfuserid = self.cbpi.config.get("brewfather_user_id", None)
|
|
||||||
if bfuserid is None:
|
if bfuserid is None:
|
||||||
logger.info("INIT Brewfather User ID")
|
logger.info("INIT Brewfather User ID")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
|
||||||
## Check if Brewfather API Key is in config
|
## Check if Brewfather API Key is in config
|
||||||
bfapikey = self.cbpi.config.get("brewfather_api_key", None)
|
|
||||||
if bfapikey is None:
|
if bfapikey is None:
|
||||||
logger.info("INIT Brewfather API Key")
|
logger.info("INIT Brewfather API Key")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
|
||||||
## Check if Brewfather API Key is in config
|
## Check if Brewfather API Key is in config
|
||||||
RecipeCreationPath = self.cbpi.config.get("RECIPE_CREATION_PATH", None)
|
|
||||||
if RecipeCreationPath is None:
|
if RecipeCreationPath is None:
|
||||||
logger.info("INIT Recipe Creation Path")
|
logger.info("INIT Recipe Creation Path")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
|
||||||
## Check if Kettle for Boil, Whirlpool and Cooldown is in config
|
## Check if Kettle for Boil, Whirlpool and Cooldown is in config
|
||||||
BoilKettle = self.cbpi.config.get("BoilKettle", None)
|
|
||||||
if BoilKettle is None:
|
if BoilKettle is None:
|
||||||
logger.info("INIT BoilKettle")
|
logger.info("INIT BoilKettle")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update config')
|
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
|
## Check if CSV logfiles is on config
|
||||||
if logfiles is None:
|
if logfiles is None:
|
||||||
logger.info("INIT CSV logfiles")
|
logger.info("INIT CSV logfiles")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add("CSVLOGFILES", "Yes", ConfigType.SELECT, "Write sensor data to csv logfiles",
|
await self.cbpi.config.add("CSVLOGFILES", "Yes", type=ConfigType.SELECT, description="Write sensor data to csv logfiles (enabling requires restart)",
|
||||||
[{"label": "Yes", "value": "Yes"},
|
source="craftbeerpi",
|
||||||
|
options= [{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
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
|
## Check if influxdb is on config
|
||||||
if influxdb is None:
|
if influxdb is None:
|
||||||
logger.info("INIT Influxdb")
|
logger.info("INIT Influxdb")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add("INFLUXDB", "No", ConfigType.SELECT, "Write sensor data to influxdb",
|
await self.cbpi.config.add("INFLUXDB", "No", type=ConfigType.SELECT, description="Write sensor data to influxdb (enabling requires restart)",
|
||||||
[{"label": "Yes", "value": "Yes"},
|
source="craftbeerpi",
|
||||||
|
options= [{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
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:
|
||||||
|
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
|
## Check if influxdbaddr is in config
|
||||||
if influxdbaddr is None:
|
if influxdbaddr is None:
|
||||||
logger.info("INIT Influxdbaddr")
|
logger.info("INIT Influxdbaddr")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update config')
|
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
|
## Check if influxdbname is in config
|
||||||
if influxdbname is None:
|
if influxdbname is None:
|
||||||
logger.info("INIT Influxdbname")
|
logger.info("INIT Influxdbname")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update config')
|
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
|
## Check if influxduser is in config
|
||||||
if influxdbuser is None:
|
if influxdbuser is None:
|
||||||
logger.info("INIT Influxdbuser")
|
logger.info("INIT Influxdbuser")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update config')
|
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
|
## Check if influxdpwd is in config
|
||||||
if influxdbpwd is None:
|
if influxdbpwd is None:
|
||||||
logger.info("INIT Influxdbpwd")
|
logger.info("INIT Influxdbpwd")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update config')
|
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
|
## Check if influxdb cloud is on config
|
||||||
if influxdbcloud is None:
|
if influxdbcloud is None:
|
||||||
logger.info("INIT influxdbcloud")
|
logger.info("INIT influxdbcloud")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add("INFLUXDBCLOUD", "No", ConfigType.SELECT, "Write sensor data to influxdb cloud (INFLUXDB must set to Yes)",
|
await self.cbpi.config.add("INFLUXDBCLOUD", "No", type=ConfigType.SELECT, description="Write sensor data to influxdb cloud (INFLUXDB must set to Yes)",
|
||||||
[{"label": "Yes", "value": "Yes"},
|
source="craftbeerpi",
|
||||||
|
options= [{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
@ -267,15 +395,16 @@ class ConfigUpdate(CBPiExtension):
|
||||||
if influxdbmeasurement is None:
|
if influxdbmeasurement is None:
|
||||||
logger.info("INIT Influxdb measurementname")
|
logger.info("INIT Influxdb measurementname")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
|
||||||
if mqttupdate is None:
|
if mqttupdate is None:
|
||||||
logger.info("INIT MQTT update frequency for Kettles and Fermenters")
|
logger.info("INIT MQTT update frequency for Kettles and Fermenters")
|
||||||
try:
|
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",
|
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",
|
||||||
[{"label": "30", "value": 30},
|
source="craftbeerpi",
|
||||||
|
options= [{"label": "30", "value": 30},
|
||||||
{"label": "60", "value": 60},
|
{"label": "60", "value": 60},
|
||||||
{"label": "120", "value": 120},
|
{"label": "120", "value": 120},
|
||||||
{"label": "300", "value": 300},
|
{"label": "300", "value": 300},
|
||||||
|
@ -287,8 +416,9 @@ class ConfigUpdate(CBPiExtension):
|
||||||
if PRESSURE_UNIT is None:
|
if PRESSURE_UNIT is None:
|
||||||
logger.info("INIT PRESSURE_UNIT")
|
logger.info("INIT PRESSURE_UNIT")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add("PRESSURE_UNIT", "kPa", ConfigType.SELECT, "Set unit for pressure",
|
await self.cbpi.config.add("PRESSURE_UNIT", "kPa", type=ConfigType.SELECT, description="Set unit for pressure",
|
||||||
[{"label": "kPa", "value": "kPa"},
|
source="craftbeerpi",
|
||||||
|
options= [{"label": "kPa", "value": "kPa"},
|
||||||
{"label": "PSI", "value": "PSI"}])
|
{"label": "PSI", "value": "PSI"}])
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
@ -297,7 +427,7 @@ class ConfigUpdate(CBPiExtension):
|
||||||
if SENSOR_LOG_BACKUP_COUNT is None:
|
if SENSOR_LOG_BACKUP_COUNT is None:
|
||||||
logger.info("INIT SENSOR_LOG_BACKUP_COUNT")
|
logger.info("INIT SENSOR_LOG_BACKUP_COUNT")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
logger.warning('Unable to update database')
|
||||||
|
|
||||||
|
@ -305,7 +435,7 @@ class ConfigUpdate(CBPiExtension):
|
||||||
if SENSOR_LOG_MAX_BYTES is None:
|
if SENSOR_LOG_MAX_BYTES is None:
|
||||||
logger.info("Init maximum size of sensor logfiles")
|
logger.info("Init maximum size of sensor logfiles")
|
||||||
try:
|
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:
|
except:
|
||||||
logger.warning('Unable to update database')
|
logger.warning('Unable to update database')
|
||||||
|
|
||||||
|
@ -313,8 +443,9 @@ class ConfigUpdate(CBPiExtension):
|
||||||
if slow_pipe_animation is None:
|
if slow_pipe_animation is None:
|
||||||
logger.info("INIT slow_pipe_animation")
|
logger.info("INIT slow_pipe_animation")
|
||||||
try:
|
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",
|
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",
|
||||||
[{"label": "Yes", "value": "Yes"},
|
source="craftbeerpi",
|
||||||
|
options= [{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
@ -323,8 +454,9 @@ class ConfigUpdate(CBPiExtension):
|
||||||
if NOTIFY_ON_ERROR is None:
|
if NOTIFY_ON_ERROR is None:
|
||||||
logger.info("INIT NOTIFY_ON_ERROR")
|
logger.info("INIT NOTIFY_ON_ERROR")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add("NOTIFY_ON_ERROR", "No", ConfigType.SELECT, "Send Notification on Logging Error",
|
await self.cbpi.config.add("NOTIFY_ON_ERROR", "No", type=ConfigType.SELECT, description="Send Notification on Logging Error",
|
||||||
[{"label": "Yes", "value": "Yes"},
|
source="craftbeerpi",
|
||||||
|
options= [{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
@ -333,8 +465,9 @@ class ConfigUpdate(CBPiExtension):
|
||||||
if PLAY_BUZZER is None:
|
if PLAY_BUZZER is None:
|
||||||
logger.info("INIT PLAY_BUZZER")
|
logger.info("INIT PLAY_BUZZER")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add("PLAY_BUZZER", "No", ConfigType.SELECT, "Play buzzer sound in Web interface on Notifications",
|
await self.cbpi.config.add("PLAY_BUZZER", "No", type=ConfigType.SELECT, description="Play buzzer sound in Web interface on Notifications",
|
||||||
[{"label": "Yes", "value": "Yes"},
|
source="craftbeerpi",
|
||||||
|
options= [{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
@ -342,13 +475,33 @@ class ConfigUpdate(CBPiExtension):
|
||||||
if BoilAutoTimer is None:
|
if BoilAutoTimer is None:
|
||||||
logging.info("INIT BoilAutoTimer")
|
logging.info("INIT BoilAutoTimer")
|
||||||
try:
|
try:
|
||||||
await self.cbpi.config.add('BoilAutoTimer', 'No', ConfigType.SELECT,
|
await self.cbpi.config.add('BoilAutoTimer', 'No', type=ConfigType.SELECT,
|
||||||
'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',
|
||||||
[{"label": "Yes", "value": "Yes"},
|
source="steps",
|
||||||
|
options= [{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
|
|
||||||
BoilAutoTimer = self.cbpi.config.get("BoilAutoTimer", "No")
|
BoilAutoTimer = self.cbpi.config.get("BoilAutoTimer", "No")
|
||||||
except:
|
except:
|
||||||
logging.warning('Unable to update database')
|
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):
|
def setup(cbpi):
|
||||||
cbpi.plugin.register("ConfigUpdate", ConfigUpdate)
|
cbpi.plugin.register("ConfigUpdate", ConfigUpdate)
|
||||||
|
|
51
cbpi/extension/SensorLogTarget_CSV/__init__.py
Normal file
51
cbpi/extension/SensorLogTarget_CSV/__init__.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
# -*- 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.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.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))
|
||||||
|
|
||||||
|
data_logger = logging.getLogger('cbpi.sensor.%s' % id)
|
||||||
|
data_logger.propagate = False
|
||||||
|
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.cbpi.log.datalogger[id] = data_logger
|
||||||
|
|
||||||
|
self.cbpi.log.datalogger[id].info("%s,%s" % (formatted_time, str(value)))
|
||||||
|
|
||||||
|
def setup(cbpi):
|
||||||
|
cbpi.plugin.register("SensorLogTargetCSV", SensorLogTargetCSV)
|
3
cbpi/extension/SensorLogTarget_CSV/config.yaml
Normal file
3
cbpi/extension/SensorLogTarget_CSV/config.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name: SensorLogTargetCSV
|
||||||
|
version: 4
|
||||||
|
active: true
|
76
cbpi/extension/SensorLogTarget_InfluxDB/__init__.py
Normal file
76
cbpi/extension/SensorLogTarget_InfluxDB/__init__.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
|
||||||
|
# -*- 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__)
|
||||||
|
|
||||||
|
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.
|
||||||
|
# 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)
|
||||||
|
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)
|
||||||
|
if req.status != 204:
|
||||||
|
raise Exception(f'InfluxDB Status code {req.status}')
|
||||||
|
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)
|
||||||
|
if req.status != 204:
|
||||||
|
raise Exception(f'InfluxDB Status code {req.status}')
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("InfluxDB write Error: {}".format(e))
|
||||||
|
|
||||||
|
def setup(cbpi):
|
||||||
|
cbpi.plugin.register("SensorLogTargetInfluxDB", SensorLogTargetInfluxDB)
|
3
cbpi/extension/SensorLogTarget_InfluxDB/config.yaml
Normal file
3
cbpi/extension/SensorLogTarget_InfluxDB/config.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
name: SensorLogTargetInfluxDB
|
||||||
|
version: 4
|
||||||
|
active: true
|
|
@ -56,7 +56,7 @@ class ConfigHttpEndpoints:
|
||||||
"200":
|
"200":
|
||||||
description: successful operation
|
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)
|
@request_mapping(path="/{name}/", method="POST", auth_required=False)
|
||||||
async def http_paramter(self, request) -> web.Response:
|
async def http_paramter(self, request) -> web.Response:
|
||||||
|
@ -78,5 +78,55 @@ class ConfigHttpEndpoints:
|
||||||
name = request.match_info['name']
|
name = request.match_info['name']
|
||||||
# if name not in self.cache:
|
# if name not in self.cache:
|
||||||
# raise CBPiException("Parameter %s not found" % name)
|
# 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)
|
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)
|
|
@ -86,3 +86,21 @@ class PluginHttpEndpoints:
|
||||||
"""
|
"""
|
||||||
plugin_list = await self.cbpi.plugin.load_plugin_list()
|
plugin_list = await self.cbpi.plugin.load_plugin_list()
|
||||||
return web.json_response(plugin_list, dumps=json_dumps)
|
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)
|
||||||
|
|
|
@ -7,8 +7,8 @@ aiohttp-session==2.12.0
|
||||||
aiohttp-swagger==1.0.16
|
aiohttp-swagger==1.0.16
|
||||||
aiojobs==1.1.0
|
aiojobs==1.1.0
|
||||||
aiosqlite==0.17.0
|
aiosqlite==0.17.0
|
||||||
cryptography==40.0.0
|
cryptography==40.0.1
|
||||||
requests==2.28.1
|
requests==2.31.0
|
||||||
voluptuous==0.13.1
|
voluptuous==0.13.1
|
||||||
pyfiglet==0.8.post1
|
pyfiglet==0.8.post1
|
||||||
pandas==1.5.3
|
pandas==1.5.3
|
||||||
|
|
6
setup.py
6
setup.py
|
@ -39,7 +39,7 @@ setup(name='cbpi4',
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"typing-extensions>=4",
|
"typing-extensions>=4",
|
||||||
"aiohttp==3.8.3",
|
"aiohttp==3.8.4",
|
||||||
"aiohttp-auth==0.1.1",
|
"aiohttp-auth==0.1.1",
|
||||||
"aiohttp-route-decorator==0.1.4",
|
"aiohttp-route-decorator==0.1.4",
|
||||||
"aiohttp-security==0.4.0",
|
"aiohttp-security==0.4.0",
|
||||||
|
@ -47,8 +47,8 @@ setup(name='cbpi4',
|
||||||
"aiohttp-swagger==1.0.16",
|
"aiohttp-swagger==1.0.16",
|
||||||
"aiojobs==1.1.0 ",
|
"aiojobs==1.1.0 ",
|
||||||
"aiosqlite==0.17.0",
|
"aiosqlite==0.17.0",
|
||||||
"cryptography==40.0.0",
|
"cryptography==40.0.1",
|
||||||
"requests==2.28.1",
|
"requests==2.31.0",
|
||||||
"voluptuous==0.13.1",
|
"voluptuous==0.13.1",
|
||||||
"pyfiglet==0.8.post1",
|
"pyfiglet==0.8.post1",
|
||||||
'click==8.1.3',
|
'click==8.1.3',
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"description": "Author",
|
"description": "Author",
|
||||||
"name": "AUTHOR",
|
"name": "AUTHOR",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": "John Doe"
|
"value": "John Doe"
|
||||||
},
|
},
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
"value": "No"
|
"value": "No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "steps",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "Yes"
|
"value": "Yes"
|
||||||
},
|
},
|
||||||
|
@ -35,6 +37,7 @@
|
||||||
"value": "No"
|
"value": "No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "steps",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "Yes"
|
"value": "Yes"
|
||||||
},
|
},
|
||||||
|
@ -42,6 +45,7 @@
|
||||||
"description": "Brewery Name",
|
"description": "Brewery Name",
|
||||||
"name": "BREWERY_NAME",
|
"name": "BREWERY_NAME",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": "Some New Brewery Name"
|
"value": "Some New Brewery Name"
|
||||||
},
|
},
|
||||||
|
@ -58,6 +62,7 @@
|
||||||
"value": "No"
|
"value": "No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "steps",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "No"
|
"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",
|
"description": "Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used",
|
||||||
"name": "BoilKettle",
|
"name": "BoilKettle",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "kettle",
|
"type": "kettle",
|
||||||
"value": ""
|
"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.10.a1"
|
||||||
|
},
|
||||||
"CSVLOGFILES": {
|
"CSVLOGFILES": {
|
||||||
"description": "Write sensor data to csv logfiles",
|
"description": "Write sensor data to csv logfiles",
|
||||||
"name": "CSVLOGFILES",
|
"name": "CSVLOGFILES",
|
||||||
|
@ -81,6 +95,7 @@
|
||||||
"value": "No"
|
"value": "No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "Yes"
|
"value": "Yes"
|
||||||
},
|
},
|
||||||
|
@ -97,13 +112,15 @@
|
||||||
"value": "No"
|
"value": "No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "No"
|
"value": "No"
|
||||||
},
|
},
|
||||||
"INFLUXDBADDR": {
|
"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",
|
"name": "INFLUXDBADDR",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": "localhost"
|
"value": "localhost"
|
||||||
},
|
},
|
||||||
|
@ -120,6 +137,7 @@
|
||||||
"value": "No"
|
"value": "No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "No"
|
"value": "No"
|
||||||
},
|
},
|
||||||
|
@ -127,6 +145,7 @@
|
||||||
"description": "Name of the measurement in your INFLUXDB database (default: measurement)",
|
"description": "Name of the measurement in your INFLUXDB database (default: measurement)",
|
||||||
"name": "INFLUXDBMEASUREMENT",
|
"name": "INFLUXDBMEASUREMENT",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": "measurement"
|
"value": "measurement"
|
||||||
},
|
},
|
||||||
|
@ -134,27 +153,23 @@
|
||||||
"description": "Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)",
|
"description": "Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)",
|
||||||
"name": "INFLUXDBNAME",
|
"name": "INFLUXDBNAME",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": "cbpi4"
|
"value": "cbpi4"
|
||||||
},
|
},
|
||||||
"INFLUXDBPORT": {
|
|
||||||
"description": "Port of your influxdb server",
|
|
||||||
"name": "INFLUXDBPORT",
|
|
||||||
"options": null,
|
|
||||||
"type": "string",
|
|
||||||
"value": "8086"
|
|
||||||
},
|
|
||||||
"INFLUXDBPWD": {
|
"INFLUXDBPWD": {
|
||||||
"description": "Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)",
|
"description": "Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)",
|
||||||
"name": "INFLUXDBPWD",
|
"name": "INFLUXDBPWD",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": " "
|
"value": " "
|
||||||
},
|
},
|
||||||
"INFLUXDBUSER": {
|
"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",
|
"name": "INFLUXDBUSER",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": " "
|
"value": " "
|
||||||
},
|
},
|
||||||
|
@ -162,6 +177,7 @@
|
||||||
"description": "Default Mash Tun",
|
"description": "Default Mash Tun",
|
||||||
"name": "MASH_TUN",
|
"name": "MASH_TUN",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "kettle",
|
"type": "kettle",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
|
@ -190,6 +206,7 @@
|
||||||
"value": 0
|
"value": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": 0
|
"value": 0
|
||||||
},
|
},
|
||||||
|
@ -206,6 +223,24 @@
|
||||||
"value": "No"
|
"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",
|
"type": "select",
|
||||||
"value": "No"
|
"value": "No"
|
||||||
},
|
},
|
||||||
|
@ -222,6 +257,7 @@
|
||||||
"value": "PSI"
|
"value": "PSI"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "kPa"
|
"value": "kPa"
|
||||||
},
|
},
|
||||||
|
@ -229,6 +265,7 @@
|
||||||
"description": "API path to creation plugin. Default: upload . CHANGE ONLY IF USING A RECIPE CREATION PLUGIN",
|
"description": "API path to creation plugin. Default: upload . CHANGE ONLY IF USING A RECIPE CREATION PLUGIN",
|
||||||
"name": "RECIPE_CREATION_PATH",
|
"name": "RECIPE_CREATION_PATH",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": "upload"
|
"value": "upload"
|
||||||
},
|
},
|
||||||
|
@ -236,6 +273,7 @@
|
||||||
"description": "Max. number of backup logs",
|
"description": "Max. number of backup logs",
|
||||||
"name": "SENSOR_LOG_BACKUP_COUNT",
|
"name": "SENSOR_LOG_BACKUP_COUNT",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 3
|
"value": 3
|
||||||
},
|
},
|
||||||
|
@ -243,6 +281,7 @@
|
||||||
"description": "Max. number of bytes in sensor logs",
|
"description": "Max. number of bytes in sensor logs",
|
||||||
"name": "SENSOR_LOG_MAX_BYTES",
|
"name": "SENSOR_LOG_MAX_BYTES",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 100000
|
"value": 100000
|
||||||
},
|
},
|
||||||
|
@ -259,6 +298,7 @@
|
||||||
"value": "F"
|
"value": "F"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "C"
|
"value": "C"
|
||||||
},
|
},
|
||||||
|
@ -266,6 +306,7 @@
|
||||||
"description": "Brewfather API Key",
|
"description": "Brewfather API Key",
|
||||||
"name": "brewfather_api_key",
|
"name": "brewfather_api_key",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
|
@ -273,6 +314,7 @@
|
||||||
"description": "Brewfather User ID",
|
"description": "Brewfather User ID",
|
||||||
"name": "brewfather_user_id",
|
"name": "brewfather_user_id",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
|
@ -280,6 +322,7 @@
|
||||||
"description": "Number of current Dashboard",
|
"description": "Number of current Dashboard",
|
||||||
"name": "current_dashboard_number",
|
"name": "current_dashboard_number",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "hidden",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 1
|
"value": 1
|
||||||
},
|
},
|
||||||
|
@ -328,6 +371,7 @@
|
||||||
"value": 10
|
"value": 10
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": 4
|
"value": 4
|
||||||
},
|
},
|
||||||
|
@ -344,6 +388,7 @@
|
||||||
"value": "No"
|
"value": "No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"source": "craftbeerpi",
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "Yes"
|
"value": "Yes"
|
||||||
},
|
},
|
||||||
|
@ -351,6 +396,7 @@
|
||||||
"description": "Boil step type",
|
"description": "Boil step type",
|
||||||
"name": "steps_boil",
|
"name": "steps_boil",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "step",
|
"type": "step",
|
||||||
"value": "BoilStep"
|
"value": "BoilStep"
|
||||||
},
|
},
|
||||||
|
@ -358,6 +404,7 @@
|
||||||
"description": "Default Boil Temperature for Recipe Creation",
|
"description": "Default Boil Temperature for Recipe Creation",
|
||||||
"name": "steps_boil_temp",
|
"name": "steps_boil_temp",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": "99"
|
"value": "99"
|
||||||
},
|
},
|
||||||
|
@ -365,6 +412,7 @@
|
||||||
"description": "Cooldown step type",
|
"description": "Cooldown step type",
|
||||||
"name": "steps_cooldown",
|
"name": "steps_cooldown",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "step",
|
"type": "step",
|
||||||
"value": "CooldownStep"
|
"value": "CooldownStep"
|
||||||
},
|
},
|
||||||
|
@ -372,6 +420,7 @@
|
||||||
"description": "Actor to trigger cooldown water on and off (default: None)",
|
"description": "Actor to trigger cooldown water on and off (default: None)",
|
||||||
"name": "steps_cooldown_actor",
|
"name": "steps_cooldown_actor",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "actor",
|
"type": "actor",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
|
@ -379,6 +428,7 @@
|
||||||
"description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)",
|
"description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)",
|
||||||
"name": "steps_cooldown_sensor",
|
"name": "steps_cooldown_sensor",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "sensor",
|
"type": "sensor",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
|
@ -386,6 +436,7 @@
|
||||||
"description": "Cooldown temp will send notification when this temeprature is reached",
|
"description": "Cooldown temp will send notification when this temeprature is reached",
|
||||||
"name": "steps_cooldown_temp",
|
"name": "steps_cooldown_temp",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": 35
|
"value": 35
|
||||||
},
|
},
|
||||||
|
@ -393,6 +444,7 @@
|
||||||
"description": "Mash step type",
|
"description": "Mash step type",
|
||||||
"name": "steps_mash",
|
"name": "steps_mash",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "step",
|
"type": "step",
|
||||||
"value": "MashStep"
|
"value": "MashStep"
|
||||||
},
|
},
|
||||||
|
@ -400,6 +452,7 @@
|
||||||
"description": "MashIn step type",
|
"description": "MashIn step type",
|
||||||
"name": "steps_mashin",
|
"name": "steps_mashin",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "step",
|
"type": "step",
|
||||||
"value": "MashInStep"
|
"value": "MashInStep"
|
||||||
},
|
},
|
||||||
|
@ -407,6 +460,7 @@
|
||||||
"description": "MashOut step type",
|
"description": "MashOut step type",
|
||||||
"name": "steps_mashout",
|
"name": "steps_mashout",
|
||||||
"options": null,
|
"options": null,
|
||||||
|
"source": "steps",
|
||||||
"type": "step",
|
"type": "step",
|
||||||
"value": "NotificationStep"
|
"value": "NotificationStep"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
{
|
{
|
||||||
"elements": []
|
"config": {},
|
||||||
|
"dbid": 1,
|
||||||
|
"type": "Test",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
}
|
}
|
|
@ -1,3 +1,11 @@
|
||||||
{
|
{
|
||||||
"data": []
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "unconfigured_test_sensor_ID",
|
||||||
|
"name": "unconfigured_mqtt_sensor",
|
||||||
|
"props": {},
|
||||||
|
"state": false,
|
||||||
|
"type": "MQTTSensor"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
|
@ -10,10 +10,10 @@ class LoggerTestCase(CraftBeerPiTestCase):
|
||||||
async def test_log_data(self):
|
async def test_log_data(self):
|
||||||
|
|
||||||
os.makedirs(os.path.join(".", "tests", "logs"), exist_ok=True)
|
os.makedirs(os.path.join(".", "tests", "logs"), exist_ok=True)
|
||||||
log_name = "test"
|
log_name = "unconfigured_test_sensor_ID"
|
||||||
#clear all logs
|
#clear all logs
|
||||||
self.cbpi.log.clear_log(log_name)
|
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
|
# write log entries
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
|
|
Loading…
Reference in a new issue