Merge branch 'development' into hop_text

This commit is contained in:
Alexander Vollkopf 2022-10-03 11:37:19 +02:00 committed by GitHub
commit cb60b6d5c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 253 additions and 103 deletions

View file

@ -9,9 +9,10 @@ RUN apt-get install --no-install-recommends -y \
&& rm -rf /var/lib/apt/lists/*
RUN python3 -m pip install --no-cache-dir --upgrade pip setuptools wheel
# Install craftbeerpi requirements for better caching
COPY ./requirements.txt /workspace/requirements.txt
RUN pip3 install --no-cache-dir -r /workspace/requirements.txt
# Install craftbeerpi requirements and additional-dev-requirements for better caching
COPY ./requirements.txt ./.devcontainer/cbpi-default-dev-config/additional-dev-requirements.txt /workspace/
RUN cat /workspace/additional-dev-requirements.txt 2>/dev/null 1>> /workspace/requirements.txt \
&& pip3 install --no-cache-dir -r /workspace/requirements.txt
# Install current version of cbpi-ui
RUN mkdir /opt/downloads \

View file

@ -0,0 +1 @@
cbpi4-SimulatedSensor==0.0.2

View file

@ -1,3 +0,0 @@
{
"elements": []
}

5
.gitignore vendored
View file

@ -17,4 +17,7 @@ node_modules
.DS_Store
config/*
logs/
.coverage
.coverage
.devcontainer/cbpi-dev-config/*
cbpi4-*
temp*

29
.vscode/launch.json vendored
View file

@ -6,11 +6,36 @@
"configurations": [
{
"name": "Run CraftBeerPi4",
"name": "run CraftBeerPi4",
"type": "python",
"request": "launch",
"module": "run",
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "start"]
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "start"],
"preLaunchTask": "copy default cbpi config files if dev config files dont exist"
},
{
"name": "create CraftBeerPi4 plugin",
"type": "python",
"request": "launch",
"module": "run",
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "create"]
},
{
"name": "setup CraftBeerPi4: create config folder structure",
"type": "python",
"request": "launch",
"module": "run",
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "setup"]
},
{
"name": "run tests",
"type": "python",
"request": "launch",
"module": "pytest",
"args": ["tests"]
}
]
}

20
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "copy default cbpi config files if dev config files dont exist",
"type": "shell",
"command": "cp -ru ${workspaceFolder}/.devcontainer/cbpi-default-dev-config/. ${workspaceFolder}/.devcontainer/cbpi-dev-config",
"windows": {
"command": "echo 'this pre debug task should only be run inside the docker dev container - doing nothing instead'"
},
"group": "build",
"presentation": {
"reveal": "silent",
"panel": "shared"
}
}
]
}

View file

@ -1,4 +1,5 @@
import logging
from pathlib import Path
import requests
from cbpi.configFolder import ConfigFolder
from cbpi.utils.utils import load_config
@ -8,6 +9,7 @@ import os
import pkgutil
import shutil
import click
import pathlib
from subprocess import call
from colorama import Fore, Back, Style
import importlib
@ -15,6 +17,7 @@ from importlib_metadata import metadata
from tabulate import tabulate
from PyInquirer import prompt, print_json
import platform
import time
class CraftBeerPiCli():
def __init__(self, config) -> None:
@ -85,7 +88,7 @@ class CraftBeerPiCli():
answers = prompt(questions)
name = "cbpi4_" + answers["name"]
name = "cbpi4-" + str(answers["name"]).replace('_', '-').replace(' ', '-')
if os.path.exists(os.path.join(".", name)) is True:
print("Cant create Plugin. Folder {} already exists ".format(name))
return
@ -97,8 +100,10 @@ class CraftBeerPiCli():
with ZipFile('temp.zip', 'r') as repo_zip:
repo_zip.extractall()
time.sleep(1) # windows dev container permissions problem otherwise
os.rename("./craftbeerpi4-plugin-template-main", os.path.join(".", name))
os.rename(os.path.join(".","craftbeerpi4-plugin-template-main"), os.path.join(".", name))
os.rename(os.path.join(".", name, "src"), os.path.join(".", name, name))
import jinja2
@ -225,15 +230,28 @@ class CraftBeerPiCli():
@click.group()
@click.pass_context
@click.option('--config-folder-path', '-c', default="./config", type=click.Path(), help="Specify where the config folder is located. Defaults to './config'.")
def main(context, config_folder_path):
@click.option('--logs-folder-path', '-l', default="", type=click.Path(), help="Specify where the log folder is located. Defaults to '../logs' relative from the config folder.")
@click.option('--debug-log-level', '-d', default="30", type=int, help="Specify the log level you want to write to all logs. 0=ALL, 10=DEBUG, 20=INFO 30(default)=WARNING, 40=ERROR, 50=CRITICAL")
def main(context, config_folder_path, logs_folder_path, debug_log_level):
print("---------------------")
print("Welcome to CBPi")
print("---------------------")
level = logging.INFO
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
cbpi_cli = CraftBeerPiCli(ConfigFolder(config_folder_path))
if logs_folder_path == "":
logs_folder_path = os.path.join(Path(config_folder_path).absolute().parent, 'logs')
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
logging.basicConfig(format=formatter,level=debug_log_level, stream=logging.StreamHandler())
logger = logging.getLogger()
try:
if not os.path.isdir(logs_folder_path):
logger.info(f"logs folder '{logs_folder_path}' doesnt exist and we are trying to create it")
pathlib.Path(logs_folder_path).mkdir(parents=True, exist_ok=True)
logger.info(f"logs folder '{logs_folder_path}' successfully created")
logger.addHandler(logging.handlers.RotatingFileHandler(os.path.join(logs_folder_path, f"cbpi.log"), maxBytes=1000000, backupCount=3))
except Exception as e:
logger.warning("log folder or log file could not be created or accessed. check folder and file permissions or create the logs folder somewhere you have access with a start option like '--log-folder-path=./logs'")
logging.critical(e, exc_info=True)
cbpi_cli = CraftBeerPiCli(ConfigFolder(config_folder_path, logs_folder_path))
context.obj = cbpi_cli
pass
@main.command()
@click.pass_context

View file

@ -1,3 +0,0 @@
{
"elements": []
}

View file

@ -1,3 +1,4 @@
from ast import If, Try
import os
from os import listdir
from os.path import isfile, join
@ -5,53 +6,43 @@ import pathlib
import platform
import shutil
import zipfile
from pathlib import Path
import glob
class ConfigFolder:
def __init__(self, configFolderPath):
self._rawPath = configFolderPath
def __init__(self, configFolderPath, logsFolderPath):
self.configFolderPath = configFolderPath
self.logsFolderPath = logsFolderPath
print("config folder path : " + configFolderPath)
print("logs folder path : " + logsFolderPath)
def config_file_exists(self, path):
return os.path.exists(self.get_file_path(path))
def get_file_path(self, file):
return os.path.join(self._rawPath, file)
return os.path.join(self.configFolderPath, file)
def get_dashboard_path(self, file):
return os.path.join(self.configFolderPath, "dashboard", file)
def get_upload_file(self, file):
return os.path.join(self._rawPath, 'upload', file)
return os.path.join(self.configFolderPath, 'upload', file)
def get_recipe_file_by_id(self, recipe_id):
return os.path.join(self._rawPath, 'recipes', "{}.yaml".format(recipe_id))
return os.path.join(self.configFolderPath, 'recipes', "{}.yaml".format(recipe_id))
def get_fermenter_recipe_by_id(self, recipe_id):
return os.path.join(self._rawPath, 'fermenterrecipes', "{}.yaml".format(recipe_id))
return os.path.join(self.configFolderPath, 'fermenterrecipes', "{}.yaml".format(recipe_id))
def get_all_fermenter_recipes(self):
fermenter_recipes_folder = os.path.join(self._rawPath, 'fermenterrecipes')
fermenter_recipes_folder = os.path.join(self.configFolderPath, 'fermenterrecipes')
fermenter_recipe_ids = [os.path.splitext(f)[0] for f in listdir(fermenter_recipes_folder) if isfile(join(fermenter_recipes_folder, f)) and f.endswith(".yaml")]
return fermenter_recipe_ids
def check_for_setup(self):
if self.config_file_exists("config.yaml") is False:
print("***************************************************")
print("CraftBeerPi Config File not found: %s" % self.get_file_path("config.yaml"))
print("Please run 'cbpi setup' before starting the server ")
print("***************************************************")
return False
if self.config_file_exists("upload") is False:
print("***************************************************")
print("CraftBeerPi upload folder not found: %s" % self.get_file_path("upload"))
print("Please run 'cbpi setup' before starting the server ")
print("***************************************************")
return False
# if os.path.exists(os.path.join(".", "config", "fermenterrecipes")) is False:
# print("***************************************************")
# print("CraftBeerPi fermenterrecipes folder not found: %s" % os.path.join(".", "config/fermenterrecipes"))
# print("Please run 'cbpi setup' before starting the server ")
# print("***************************************************")
# return False
backupfile = os.path.join(".", "restored_config.zip")
# is there a restored_config.zip file? if yes restore it first then delte the zip.
backupfile = os.path.join(self.configFolderPath, "restored_config.zip")
if os.path.exists(os.path.join(backupfile)) is True:
print("***************************************************")
print("Found backup of config. Starting restore")
@ -68,14 +59,14 @@ class ConfigFolder:
if zip_content == True:
print("Found correct content. Starting Restore process")
output_path = pathlib.Path(self._rawPath)
output_path = pathlib.Path(self.configFolderPath)
system = platform.system()
print(system)
if system != "Windows":
owner = output_path.owner()
group = output_path.group()
print("Removing old config folder")
shutil.rmtree(output_path, ignore_errors=True)
shutil.rmtree(output_path, ignore_errors=True)
print("Extracting zip file to config folder")
zip.extractall(output_path)
zip.close()
@ -83,17 +74,89 @@ class ConfigFolder:
print(f"Changing owner and group of config folder recursively to {owner}:{group}")
self.recursive_chown(output_path, owner, group)
print("Removing backup file")
os.remove(backupfile)
print("contents of restored_config.zip file have been restored.")
print("in case of a partial backup you will still be prompted to run 'cbpi setup'.")
else:
print("Wrong Content in zip file. No restore possible")
print("Removing zip file")
os.remove(backupfile)
print("renaming zip file so it will be ignored on the next start")
try:
os.rename(backupfile, os.path.join(self.configFolderPath, "UNRESTORABLE_restored_config.zip"))
except:
print("renamed file does exist - deleting instead")
os.remove(backupfile)
print("***************************************************")
# possible restored_config.zip has been handeled now lets check if files and folders exist
required_config_content = [
['config.yaml', 'file'],
['actor.json', 'file'],
['sensor.json', 'file'],
['kettle.json', 'file'],
['fermenter_data.json', 'file'],
['step_data.json', 'file'],
['config.json', 'file'],
['craftbeerpi.service', 'file'],
['chromium.desktop', 'file'],
['dashboard', 'folder'],
['dashboard/widgets', 'folder'],
['fermenterrecipes', 'folder'],
[self.logsFolderPath, 'folder'],
['recipes', 'folder'],
['upload', 'folder']
#['dashboard/cbpi_dashboard_1.json', 'file'] no need to check - can be created with online editor
]
for checking in required_config_content:
if self.inform_missing_content(self.check_for_file_or_folder(os.path.join(self.configFolderPath, checking[0]), checking[1])):
# since there is no complete config we now check if the config folde rmay be completely empty to show hints:
if len(os.listdir(os.path.join(self.configFolderPath))) == 0 :
print("***************************************************")
print(f"the config folder '{self.configFolderPath}' seems to be completely empty")
print("you might want to run 'cbpi setup'.print")
print("but you could also place your zipped config backup named")
print("'restored_config.zip' inside the mentioned config folder for")
print("cbpi4 to automatically unpack it")
print("of course you can also place your config files manually")
print("***************************************************")
return False
# if cbpi_dashboard_1.json doesnt exist at the new location (configFolderPath/dashboard)
# we move every cbpi_dashboard_n.json file from the old location (configFolderPath) there.
# this could be a config zip file restore from version 4.0.7.a4 or prior.
if not (os.path.isfile(os.path.join(self.configFolderPath, 'dashboard', 'cbpi_dashboard_1.json'))):
for file in glob.glob(os.path.join(self.configFolderPath, 'cbpi_dashboard_*.json')):
shutil.move(file, os.path.join(self.configFolderPath, 'dashboard', os.path.basename(file)))
def inform_missing_content(self, whatsmissing : str):
if whatsmissing == "":
return False
print("***************************************************")
print(f"CraftBeerPi config content not found: {whatsmissing}")
print("Please run 'cbpi setup' before starting the server ")
print("***************************************************")
return True
def check_for_file_or_folder(self, path : str, file_or_folder : str = ""): # file_or_folder should be "file" or "folder" or "" if both is ok
if (file_or_folder == ""): # file and folder is ok
if os.path.exists(path):
return ""
else:
return "file or folder missing: " + path
if (file_or_folder == "file"): # only file is ok
if (os.path.isfile(path)):
return ""
else:
return "file missing: " + path
if (file_or_folder == "folder"): # oly folder is ok
if (os.path.isdir(path)):
return ""
else:
return "folder missing: " + path
return "usage of check_file_or_folder() function wrong. second Argument must either be 'file' or 'folder' or an empty string"
def copyDefaultFileIfNotExists(self, file):
if self.config_file_exists(file) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", file)
destfile = os.path.join(self._rawPath, file)
destfile = os.path.join(self.configFolderPath, file)
shutil.copy(srcfile, destfile)
def create_config_file(self):
@ -107,27 +170,27 @@ class ConfigFolder:
self.copyDefaultFileIfNotExists("craftbeerpi.service")
self.copyDefaultFileIfNotExists("chromium.desktop")
if os.path.exists(os.path.join(self._rawPath, "dashboard", "cbpi_dashboard_1.json")) is False:
srcfile = os.path.join(os.path.dirname(__file__), "config", "dashboard", "cbpi_dashboard_1.json")
destfile = os.path.join(self._rawPath, "dashboard")
shutil.copy(srcfile, destfile)
print("Config Folder created")
def create_home_folder_structure(configFolder):
pathlib.Path(os.path.join(".", 'logs/sensors')).mkdir(parents=True, exist_ok=True)
configFolder.create_folders()
print("Folder created")
def create_folders(self):
pathlib.Path(self._rawPath).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self._rawPath, 'dashboard', 'widgets')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self._rawPath, 'recipes')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self._rawPath, 'upload')).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.configFolderPath).mkdir(parents=True, exist_ok=True)
pathlib.Path(self.logsFolderPath).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self.configFolderPath, 'dashboard', 'widgets')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self.configFolderPath, 'recipes')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self.configFolderPath, 'fermenterrecipes')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(self.configFolderPath, 'upload')).mkdir(parents=True, exist_ok=True)
def recursive_chown(self, path, owner, group):
for dirpath, dirnames, filenames in os.walk(path):
shutil.chown(dirpath, owner, group)
for filename in filenames:
shutil.chown(os.path.join(dirpath, filename), owner, group)
try:
for dirpath, dirnames, filenames in os.walk(path):
shutil.chown(dirpath, owner, group)
for filename in filenames:
shutil.chown(os.path.join(dirpath, filename), owner, group)
except:
print("problems assigning file or folder permissions")
print("if this happend on windows its fine")
print("if this happend in the dev container running inside windows its also fine but you might have to rebuild the container if you run into further problems")

View file

@ -1,6 +1,7 @@
from cbpi.api.dataclasses import Config
import logging
import os
from pathlib import Path
from cbpi.api.config import ConfigType
from cbpi.utils import load_config
@ -16,6 +17,7 @@ class ConfigController:
self.cbpi.register(self)
self.path = cbpi.config_folder.get_file_path("config.json")
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):

View file

@ -18,14 +18,14 @@ class DashboardController:
self.logger = logging.getLogger(__name__)
self.cbpi.register(self)
self.path = cbpi.config_folder.get_file_path("cbpi_dashboard_1.json")
self.path = cbpi.config_folder.get_dashboard_path("cbpi_dashboard_1.json")
async def init(self):
pass
async def get_content(self, dashboard_id):
try:
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_"+ str(dashboard_id) +".json")
self.path = self.cbpi.config_folder.get_dashboard_path("cbpi_dashboard_"+ str(dashboard_id) +".json")
logging.info(self.path)
with open(self.path) as json_file:
data = json.load(json_file)
@ -35,21 +35,21 @@ class DashboardController:
async def add_content(self, dashboard_id, data):
print(data)
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_" + str(dashboard_id)+ ".json")
self.path = self.cbpi.config_folder.get_dashboard_path("cbpi_dashboard_" + str(dashboard_id)+ ".json")
with open(self.path, 'w') as outfile:
json.dump(data, outfile, indent=4, sort_keys=True)
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Saved Successfully", type=NotificationType.SUCCESS)
return {"status": "OK"}
async def delete_content(self, dashboard_id):
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_"+ str(dashboard_id)+ ".json")
self.path = self.cbpi.config_folder.get_dashboard_path("cbpi_dashboard_"+ str(dashboard_id)+ ".json")
if os.path.exists(self.path):
os.remove(self.path)
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Deleted Successfully", type=NotificationType.SUCCESS)
async def get_custom_widgets(self):
path = os.path.join(self.cbpi.config_folder.get_file_path("dashboard"), "widgets")
path = self.cbpi.config_folder.get_dashboard_path("widgets")
onlyfiles = [os.path.splitext(f)[0] for f in sorted(listdir(path)) if isfile(join(path, f)) and f.endswith(".svg")]
return onlyfiles

View file

@ -8,6 +8,7 @@ import pandas as pd
import zipfile
import base64
import urllib3
from pathlib import Path
from cbpi.api import *
from cbpi.api.config import ConfigType
from cbpi.api.base import CBPiBase
@ -25,6 +26,8 @@ class LogController:
self.logger = logging.getLogger(__name__)
self.configuration = False
self.datalogger = {}
self.logsFolderPath = self.cbpi.config_folder.logsFolderPath
self.logger.info("Log folder path : " + self.logsFolderPath)
def log_data(self, name: str, value: str) -> None:
self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes")
@ -37,7 +40,7 @@ class LogController:
data_logger = logging.getLogger('cbpi.sensor.%s' % name)
data_logger.propagate = False
data_logger.setLevel(logging.DEBUG)
handler = RotatingFileHandler('./logs/sensor_%s.log' % name, maxBytes=max_bytes, backupCount=backup_count)
handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{name}.log"), maxBytes=max_bytes, backupCount=backup_count)
data_logger.addHandler(handler)
self.datalogger[name] = data_logger
@ -115,7 +118,7 @@ class LogController:
for name in names:
# get all log names
all_filenames = glob.glob('./logs/sensor_%s.log*' % name)
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))
# concat all logs
df = pd.concat([pd.read_csv(f, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime', name], header=None) for f in all_filenames])
logging.info("Read all files for {}".format(names))
@ -157,7 +160,7 @@ class LogController:
for id in ids:
# df = pd.read_csv("./logs/sensor_%s.log" % id, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime',"Values"], header=None)
# concat all logs
all_filenames = glob.glob('./logs/sensor_%s.log*' % id)
all_filenames = glob.glob(os.path.join(self.logsFolderPath,f"sensor_{id}.log*"))
df = pd.concat([pd.read_csv(f, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime', 'Values'], header=None) for f in all_filenames])
df = df.resample('60s').max()
df = df.dropna()
@ -173,11 +176,10 @@ class LogController:
:return: list of log file names
'''
return [os.path.basename(x) for x in glob.glob('./logs/sensor_%s.log*' % name)]
return [os.path.basename(x) for x in glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))]
def clear_log(self, name:str ) -> str:
all_filenames = glob.glob('./logs/sensor_%s.log*' % name)
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))
for f in all_filenames:
os.remove(f)
@ -193,7 +195,7 @@ class LogController:
:return:
'''
return [os.path.basename(x) for x in glob.glob('./logs/*-sensor-%s.zip' % name)]
return [os.path.basename(x) for x in glob.glob(os.path.join(self.logsFolderPath, f"*-sensor-{name}.zip"))]
def clear_zip(self, name:str ) -> None:
"""
@ -202,7 +204,7 @@ class LogController:
:return: None
"""
all_filenames = glob.glob('./logs/*-sensor-%s.zip' % name)
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"*-sensor-{name}.zip"))
for f in all_filenames:
os.remove(f)
@ -213,9 +215,9 @@ class LogController:
"""
formatted_time = strftime("%Y-%m-%d-%H_%M_%S", localtime())
file_name = './logs/%s-sensor-%s.zip' % (formatted_time, name)
file_name = os.path.join(self.logsFolderPath, f"{formatted_time}-sensor-{name}.zip")
zip = zipfile.ZipFile(file_name, 'w', zipfile.ZIP_DEFLATED)
all_filenames = glob.glob('./logs/sensor_%s.log*' % name)
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))
for f in all_filenames:
zip.write(os.path.join(f))
zip.close()

View file

@ -1,4 +1,5 @@
import asyncio
from email import message
from cbpi.api.dataclasses import NotificationType
import logging
import shortuuid
@ -10,9 +11,23 @@ class NotificationController:
'''
self.cbpi = cbpi
self.logger = logging.getLogger(__name__)
logging.root.addFilter(self.notify_log_event)
self.callback_cache = {}
self.listener = {}
def notify_log_event(self, record):
try:
if record.levelno > 20:
# on log events higher then INFO we want to notify all clients
type = NotificationType.WARNING
if record.levelno > 30:
type = NotificationType.ERROR
self.cbpi.notify(title=f"{record.levelname}", message=record.msg, type = type)
except Exception as e:
pass
finally:
return True
def add_listener(self, method):
listener_id = shortuuid.uuid()
self.listener[listener_id] = method

View file

@ -56,9 +56,9 @@ class SystemController:
output_filename="cbpi4_log.zip"
if logtime == "b":
os.system('journalctl -b -u craftbeerpi.service > {}'.format(fullname))
os.system('journalctl -b -u craftbeerpi.service --output cat > {}'.format(fullname))
else:
os.system('journalctl --since \"{} hours ago\" -u craftbeerpi.service > {}'.format(logtime, fullname))
os.system('journalctl --since \"{} hours ago\" -u craftbeerpi.service --output cat > {}'.format(logtime, fullname))
os.system('cbpi plugins > {}'.format(fullpluginname))
@ -116,8 +116,8 @@ class SystemController:
try:
content = backup_file.read()
if backup_file and self.allowed_file(filename, 'zip'):
self.path = os.path.join(".", "restored_config.zip")
self.path = os.path.join(self.cbpi.config_folder.configFolderPath, "restored_config.zip")
f=open(self.path, "wb")
f.write(content)
f.close()

View file

@ -193,8 +193,15 @@ class UploadController:
pass
# get the hop addition times
c.execute('SELECT Zeit, Name FROM Hopfengaben WHERE Vorderwuerze <> 1 AND Vorderwuerze <> 5 AND SudID = ?', (Recipe_ID,))
c.execute('SELECT Zeit FROM Hopfengaben WHERE Vorderwuerze <> 1 AND SudID = ?', (Recipe_ID,))
hops = c.fetchall()
whirlpool = []
for hop in hops:
if hop[0] < 0:
whirlpool.append(hop)
for whirl in whirlpool:
hops.remove(whirl)
# get the misc addition times
c.execute('SELECT Zugabedauer, Name FROM WeitereZutatenGaben WHERE Zeitpunkt = 1 AND SudID = ?', (Recipe_ID,))
@ -314,8 +321,11 @@ class UploadController:
await self.create_step(step_string)
await self.create_Whirlpool_Cooldown()
if not whirlpool:
await self.create_Whirlpool_Cooldown()
else :
await self.create_Whirlpool_Cooldown(str(abs(whirlpool[0][0]))) # from kbh this value comes as negative but must be positive
self.cbpi.notify('KBH Recipe created', name, NotificationType.INFO)
except:
@ -981,13 +991,13 @@ class UploadController:
return [alert, " and ".join(names)]
async def create_Whirlpool_Cooldown(self):
async def create_Whirlpool_Cooldown(self, time : str = "15"):
# Add Waitstep as Whirlpool
if self.cooldown != "WaiStep" and self.cooldown !="":
step_string = { "name": "Whirlpool",
"props": {
"Kettle": self.boilid,
"Timer": "15"
"Timer": time
},
"status_text": "",
"status": "I",
@ -1000,7 +1010,7 @@ class UploadController:
step_name = "CoolDown"
cooldown_sensor = ""
step_temp = ""
step_timer = "15"
step_timer = time
if step_type == "CooldownStep":
cooldown_sensor = self.cbpi.config.get("steps_cooldown_sensor", None)
if cooldown_sensor is None or cooldown_sensor == '':

View file

@ -301,12 +301,6 @@ class CraftBeerPi:
self._swagger_setup()
level = logging.INFO
logger = logging.getLogger()
logger.setLevel(level)
for handler in logger.handlers:
handler.setLevel(level)
return self.app
def start(self):

View file

@ -2,6 +2,7 @@ from cbpi.utils.encoder import ComplexEncoder
from aiohttp import web
from cbpi.utils.utils import json_dumps
from cbpi.api import request_mapping
import os
import json
class LogHttpEndpoints:
@ -83,7 +84,7 @@ class LogHttpEndpoints:
)
await response.prepare(request)
log_name = request.match_info['name']
with open('./logs/%s.zip' % log_name, 'rb') as file:
with open(os.path.join(self.cbpi.logsFolderPath, '%s.zip' % log_name), 'rb') as file:
for line in file.readlines():
await response.write(line)

View file

@ -44,7 +44,7 @@ class SystemHttpEndpoints:
async def http_get_log(self, request):
result = []
file_pattern = re.compile("^(\w+.).log(.?\d*)")
for filename in sorted(os.listdir("./logs"), reverse=True): #
for filename in sorted(os.listdir(self.cbpi.logsFolderPath), reverse=True):
if file_pattern.match(filename):
result.append(filename)
return web.json_response(result)

View file

@ -19,5 +19,6 @@ class CraftBeerPiTestCase(AioHTTPTestCase):
def configuration(self):
test_directory = os.path.dirname(__file__)
test_config_directory = os.path.join(test_directory, 'cbpi-test-config')
configFolder = ConfigFolder(test_config_directory)
test_logs_directory = os.path.join(test_directory, 'logs')
configFolder = ConfigFolder(test_config_directory, test_logs_directory)
return configFolder

View file

@ -9,7 +9,7 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
class CLITest(unittest.TestCase):
def test_list(self):
cli = CraftBeerPiCli(ConfigFolder("./cbpi-test-config"))
cli = CraftBeerPiCli(ConfigFolder("./cbpi-test-config", './logs')) # inside tests folder
cli.plugins_list()
if __name__ == '__main__':

View file

@ -10,11 +10,11 @@ class LoggerTestCase(CraftBeerPiTestCase):
@unittest_run_loop
async def test_log_data(self):
os.makedirs("./logs", exist_ok=True)
os.makedirs(os.path.join(".", "tests", "logs"), exist_ok=True)
log_name = "test"
#clear all logs
self.cbpi.log.clear_log(log_name)
assert len(glob.glob('./logs/sensor_%s.log*' % log_name)) == 0
assert len(glob.glob(os.path.join(".", "tests", "logs", f"sensor_{log_name}.log*"))) == 0
# write log entries
for i in range(5):