mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-12-22 13:34:55 +01:00
Merge branch 'development' into hop_text
This commit is contained in:
commit
cb60b6d5c5
34 changed files with 253 additions and 103 deletions
|
@ -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 \
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
cbpi4-SimulatedSensor==0.0.2
|
0
.devcontainer/cbpi-default-dev-config/recipes/.gitkeep
Normal file
0
.devcontainer/cbpi-default-dev-config/recipes/.gitkeep
Normal file
0
.devcontainer/cbpi-default-dev-config/upload/.gitkeep
Normal file
0
.devcontainer/cbpi-default-dev-config/upload/.gitkeep
Normal file
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"elements": []
|
||||
}
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -17,4 +17,7 @@ node_modules
|
|||
.DS_Store
|
||||
config/*
|
||||
logs/
|
||||
.coverage
|
||||
.coverage
|
||||
.devcontainer/cbpi-dev-config/*
|
||||
cbpi4-*
|
||||
temp*
|
29
.vscode/launch.json
vendored
29
.vscode/launch.json
vendored
|
@ -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
20
.vscode/tasks.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
32
cbpi/cli.py
32
cbpi/cli.py
|
@ -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
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"elements": []
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 == '':
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__':
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue