mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-23 23:48:16 +01:00
Merge pull request #120 from PiBrewing/development
merge from Development to allow for installation with pipx under bookworm
This commit is contained in:
commit
42d8a6c65d
18 changed files with 190 additions and 84 deletions
|
@ -1,3 +1,3 @@
|
||||||
__version__ = "4.1.11"
|
__version__ = "4.2.0"
|
||||||
__codename__ = "Groundhog Day"
|
__codename__ = "Indian Summer"
|
||||||
|
|
||||||
|
|
108
cbpi/cli.py
108
cbpi/cli.py
|
@ -1,6 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import requests
|
import requests
|
||||||
|
from cbpi import __version__, __codename__
|
||||||
from cbpi.configFolder import ConfigFolder
|
from cbpi.configFolder import ConfigFolder
|
||||||
from cbpi.utils.utils import load_config
|
from cbpi.utils.utils import load_config
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
@ -38,14 +40,27 @@ class CraftBeerPiCli():
|
||||||
|
|
||||||
def setup_one_wire(self):
|
def setup_one_wire(self):
|
||||||
print("Setting up 1Wire")
|
print("Setting up 1Wire")
|
||||||
with open('/boot/config.txt', 'w') as f:
|
with open('/boot/config.txt', 'r') as f:
|
||||||
f.write("dtoverlay=w1-gpio,gpiopin=4,pullup=on")
|
lines=f.readlines()
|
||||||
|
lines.append("dtoverlay=w1-gpio,gpiopin=4,pullup=on")
|
||||||
|
|
||||||
|
configtempfile=os.path.join(self.config.get_file_path(""),"config.txt")
|
||||||
|
|
||||||
|
with open(configtempfile, 'w') as f:
|
||||||
|
for line in lines:
|
||||||
|
f.write(line)
|
||||||
|
destfile="/boot/config.txt"
|
||||||
|
|
||||||
|
#copy and remove afterwards as mv will work, but raise an error message due to different file owners
|
||||||
|
shutil.os.system('sudo cp "{}" "{}"'.format(configtempfile,destfile))
|
||||||
|
shutil.os.system('rm -rf "{}"'.format(configtempfile))
|
||||||
|
|
||||||
print("/boot/config.txt created")
|
print("/boot/config.txt created")
|
||||||
|
|
||||||
def list_one_wire(self):
|
def list_one_wire(self):
|
||||||
print("List 1Wire")
|
print("List 1Wire")
|
||||||
call(["modprobe", "w1-gpio"])
|
call(["sudo","modprobe", "w1-gpio"])
|
||||||
call(["modprobe", "w1-therm"])
|
call(["sudo","modprobe", "w1-therm"])
|
||||||
try:
|
try:
|
||||||
for dirname in os.listdir('/sys/bus/w1/devices'):
|
for dirname in os.listdir('/sys/bus/w1/devices'):
|
||||||
if (dirname.startswith("28") or dirname.startswith("10")):
|
if (dirname.startswith("28") or dirname.startswith("10")):
|
||||||
|
@ -150,16 +165,34 @@ class CraftBeerPiCli():
|
||||||
else:
|
else:
|
||||||
print("CraftBeerPi Autostart is {}OFF{}".format(Fore.RED,Style.RESET_ALL))
|
print("CraftBeerPi Autostart is {}OFF{}".format(Fore.RED,Style.RESET_ALL))
|
||||||
elif(name == "on"):
|
elif(name == "on"):
|
||||||
|
user=os.getlogin()
|
||||||
|
path="/usr/local/bin/cbpi"
|
||||||
|
if os.path.exists("/home/"+user+"/.local/bin/cbpi") is True:
|
||||||
|
path="/home/"+user+"/.local/bin/cbpi"
|
||||||
print("Add craftbeerpi.service to systemd")
|
print("Add craftbeerpi.service to systemd")
|
||||||
try:
|
try:
|
||||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is False:
|
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is False:
|
||||||
|
templatefile=self.config.get_file_path("craftbeerpi.template")
|
||||||
|
shutil.os.system('cp "{}" "{}"'.format(templatefile,self.config.get_file_path("craftbeerpi.service")))
|
||||||
srcfile = self.config.get_file_path("craftbeerpi.service")
|
srcfile = self.config.get_file_path("craftbeerpi.service")
|
||||||
|
import jinja2
|
||||||
|
|
||||||
|
templateLoader = jinja2.FileSystemLoader(searchpath=os.path.join(self.config.get_file_path("")))
|
||||||
|
templateEnv = jinja2.Environment(loader=templateLoader)
|
||||||
|
operatingsystem = str(platform.system()).lower()
|
||||||
|
if operatingsystem.startswith("win"):
|
||||||
|
srcfile=str(srcfile).replace('\\','/')
|
||||||
|
|
||||||
|
template = templateEnv.get_template("craftbeerpi.service")
|
||||||
|
outputText = template.render(user=user, path=path)
|
||||||
|
with open(srcfile, "w") as fh:
|
||||||
|
fh.write(outputText)
|
||||||
destfile = os.path.join("/etc/systemd/system")
|
destfile = os.path.join("/etc/systemd/system")
|
||||||
shutil.copy(srcfile, destfile)
|
shutil.os.system('sudo mv "{}" "{}"'.format(srcfile,destfile))
|
||||||
print("Copied craftbeerpi.service to /etc/systemd/system")
|
print("Copied craftbeerpi.service to /etc/systemd/system")
|
||||||
os.system('systemctl enable craftbeerpi.service')
|
shutil.os.system('sudo systemctl enable craftbeerpi.service')
|
||||||
print('Enabled craftbeerpi service')
|
print('Enabled craftbeerpi service')
|
||||||
os.system('systemctl start craftbeerpi.service')
|
shutil.os.system('sudo systemctl start craftbeerpi.service')
|
||||||
print('Started craftbeerpi.service')
|
print('Started craftbeerpi.service')
|
||||||
else:
|
else:
|
||||||
print("craftbeerpi.service is already located in /etc/systemd/system")
|
print("craftbeerpi.service is already located in /etc/systemd/system")
|
||||||
|
@ -167,20 +200,20 @@ class CraftBeerPiCli():
|
||||||
print(e)
|
print(e)
|
||||||
return
|
return
|
||||||
return
|
return
|
||||||
elif(name == "off"):
|
elif(name == "off"):
|
||||||
print("Remove craftbeerpi.service from systemd")
|
print("Remove craftbeerpi.service from systemd")
|
||||||
try:
|
try:
|
||||||
status = os.popen('systemctl list-units --type=service --state=running | grep craftbeerpi.service').read()
|
status = os.popen('systemctl list-units --type=service --state=running | grep craftbeerpi.service').read()
|
||||||
if status.find("craftbeerpi.service") != -1:
|
if status.find("craftbeerpi.service") != -1:
|
||||||
os.system('systemctl stop craftbeerpi.service')
|
shutil.os.system('sudo systemctl stop craftbeerpi.service')
|
||||||
print('Stopped craftbeerpi service')
|
print('Stopped craftbeerpi service')
|
||||||
os.system('systemctl disable craftbeerpi.service')
|
shutil.os.system('sudo systemctl disable craftbeerpi.service')
|
||||||
print('Removed craftbeerpi.service as service')
|
print('Removed craftbeerpi.service as service')
|
||||||
else:
|
else:
|
||||||
print('craftbeerpi.service service is not running')
|
print('craftbeerpi.service service is not running')
|
||||||
|
|
||||||
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
|
if os.path.exists(os.path.join("/etc/systemd/system","craftbeerpi.service")) is True:
|
||||||
os.remove(os.path.join("/etc/systemd/system","craftbeerpi.service"))
|
shutil.os.system('sudo rm -rf "{}"'.format(os.path.join("/etc/systemd/system","craftbeerpi.service")))
|
||||||
print("Deleted craftbeerpi.service from /etc/systemd/system")
|
print("Deleted craftbeerpi.service from /etc/systemd/system")
|
||||||
else:
|
else:
|
||||||
print("craftbeerpi.service is not located in /etc/systemd/system")
|
print("craftbeerpi.service is not located in /etc/systemd/system")
|
||||||
|
@ -188,8 +221,6 @@ class CraftBeerPiCli():
|
||||||
print(e)
|
print(e)
|
||||||
return
|
return
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def chromium(self, name):
|
def chromium(self, name):
|
||||||
'''Enable or disable autostart'''
|
'''Enable or disable autostart'''
|
||||||
if(name == "status"):
|
if(name == "status"):
|
||||||
|
@ -203,7 +234,7 @@ class CraftBeerPiCli():
|
||||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is False:
|
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is False:
|
||||||
srcfile = self.config.get_file_path("chromium.desktop")
|
srcfile = self.config.get_file_path("chromium.desktop")
|
||||||
destfile = os.path.join("/etc/xdg/autostart/")
|
destfile = os.path.join("/etc/xdg/autostart/")
|
||||||
shutil.copy(srcfile, destfile)
|
shutil.os.system('sudo cp "{}" "{}"'.format(srcfile,destfile))
|
||||||
print("Copied chromium.desktop to /etc/xdg/autostart/")
|
print("Copied chromium.desktop to /etc/xdg/autostart/")
|
||||||
else:
|
else:
|
||||||
print("chromium.desktop is already located in /etc/xdg/autostart/")
|
print("chromium.desktop is already located in /etc/xdg/autostart/")
|
||||||
|
@ -215,7 +246,7 @@ class CraftBeerPiCli():
|
||||||
print("Remove chromium.desktop from /etc/xdg/autostart/")
|
print("Remove chromium.desktop from /etc/xdg/autostart/")
|
||||||
try:
|
try:
|
||||||
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
|
if os.path.exists(os.path.join("/etc/xdg/autostart/","chromium.desktop")) is True:
|
||||||
os.remove(os.path.join("/etc/xdg/autostart/","chromium.desktop"))
|
shutil.os.system('sudo rm -rf "{}"'.format(os.path.join("/etc/xdg/autostart/","chromium.desktop")))
|
||||||
print("Deleted chromium.desktop from /etc/xdg/autostart/")
|
print("Deleted chromium.desktop from /etc/xdg/autostart/")
|
||||||
else:
|
else:
|
||||||
print("chromium.desktop is not located in /etc/xdg/autostart/")
|
print("chromium.desktop is not located in /etc/xdg/autostart/")
|
||||||
|
@ -229,16 +260,27 @@ class CraftBeerPiCli():
|
||||||
@click.pass_context
|
@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'.")
|
@click.option('--config-folder-path', '-c', default="./config", type=click.Path(), help="Specify where the config folder is located. Defaults to './config'.")
|
||||||
@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('--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")
|
@click.option('--debug-log-level', '-d', default="99", 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. Can be also set in config.yaml (debug-log-level: INT)")
|
||||||
def main(context, config_folder_path, logs_folder_path, debug_log_level):
|
def main(context, config_folder_path, logs_folder_path, debug_log_level):
|
||||||
print("---------------------")
|
print("--------------------------")
|
||||||
print("Welcome to CBPi")
|
print("Welcome to CBPi "+__version__)
|
||||||
print("---------------------")
|
print("--------------------------")
|
||||||
if logs_folder_path == "":
|
if logs_folder_path == "":
|
||||||
logs_folder_path = os.path.join(Path(config_folder_path).absolute().parent, 'logs')
|
logs_folder_path = os.path.join(Path(config_folder_path).absolute().parent, 'logs')
|
||||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||||
|
config=ConfigFolder(config_folder_path, logs_folder_path)
|
||||||
|
static_config = load_config(config.get_file_path("config.yaml"))
|
||||||
|
try:
|
||||||
|
if debug_log_level == 99:
|
||||||
|
debug_log_level=static_config['debug-log-level']
|
||||||
|
except:
|
||||||
|
debug_log_level=30
|
||||||
|
|
||||||
logging.basicConfig(format=formatter, stream=logging.StreamHandler())
|
logging.basicConfig(format=formatter, stream=logging.StreamHandler())
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
print("*******************************")
|
||||||
|
print("Debug-log-level is {}".format(debug_log_level))
|
||||||
|
print("*******************************")
|
||||||
logger.setLevel(debug_log_level)
|
logger.setLevel(debug_log_level)
|
||||||
try:
|
try:
|
||||||
if not os.path.isdir(logs_folder_path):
|
if not os.path.isdir(logs_folder_path):
|
||||||
|
@ -249,7 +291,7 @@ def main(context, config_folder_path, logs_folder_path, debug_log_level):
|
||||||
except Exception as e:
|
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'")
|
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)
|
logging.critical(e, exc_info=True)
|
||||||
cbpi_cli = CraftBeerPiCli(ConfigFolder(config_folder_path, logs_folder_path))
|
cbpi_cli = CraftBeerPiCli(config)
|
||||||
context.obj = cbpi_cli
|
context.obj = cbpi_cli
|
||||||
|
|
||||||
@main.command()
|
@main.command()
|
||||||
|
@ -263,11 +305,15 @@ def setup(context):
|
||||||
@click.option('--list', is_flag=True, help="List all 1Wire Devices")
|
@click.option('--list', is_flag=True, help="List all 1Wire Devices")
|
||||||
@click.option('--setup', is_flag=True, help="Setup 1Wire on Raspberry Pi")
|
@click.option('--setup', is_flag=True, help="Setup 1Wire on Raspberry Pi")
|
||||||
def onewire(context, list, setup):
|
def onewire(context, list, setup):
|
||||||
'''Setup 1wire on Raspberry Pi'''
|
'''(--setup | --list) Setup 1wire on Raspberry Pi or list sensors'''
|
||||||
if setup is True:
|
operationsystem= sys.platform
|
||||||
context.obj.setup_one_wire()
|
if not operationsystem.startswith('win'):
|
||||||
if list is True:
|
if setup is True:
|
||||||
context.obj.list_one_wire()
|
context.obj.setup_one_wire()
|
||||||
|
if list is True:
|
||||||
|
context.obj.list_one_wire()
|
||||||
|
else:
|
||||||
|
print("Onewire options NOT available under Windows")
|
||||||
|
|
||||||
@main.command()
|
@main.command()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
|
@ -297,7 +343,11 @@ def create(context, pluginname=[]):
|
||||||
@click.argument('name')
|
@click.argument('name')
|
||||||
def autostart(context, name):
|
def autostart(context, name):
|
||||||
'''(on|off|status) Enable or disable autostart'''
|
'''(on|off|status) Enable or disable autostart'''
|
||||||
context.obj.autostart(name)
|
operationsystem= sys.platform
|
||||||
|
if not operationsystem.startswith('win'):
|
||||||
|
context.obj.autostart(name)
|
||||||
|
else:
|
||||||
|
print("Autostart option NOT available under Windows")
|
||||||
|
|
||||||
|
|
||||||
@main.command()
|
@main.command()
|
||||||
|
@ -305,4 +355,8 @@ def autostart(context, name):
|
||||||
@click.argument('name')
|
@click.argument('name')
|
||||||
def chromium(context, name):
|
def chromium(context, name):
|
||||||
'''(on|off|status) Enable or disable Kiosk mode'''
|
'''(on|off|status) Enable or disable Kiosk mode'''
|
||||||
context.obj.chromium(name)
|
operationsystem= sys.platform
|
||||||
|
if not operationsystem.startswith('win'):
|
||||||
|
context.obj.chromium(name)
|
||||||
|
else:
|
||||||
|
print("Chromium option NOT available under Windows")
|
||||||
|
|
|
@ -5,7 +5,7 @@ version: 4.0.8
|
||||||
index_url: /cbpi_ui/static/index.html
|
index_url: /cbpi_ui/static/index.html
|
||||||
|
|
||||||
port: 8000
|
port: 8000
|
||||||
|
debug-log-level: 30
|
||||||
mqtt: false
|
mqtt: false
|
||||||
mqtt_host: localhost
|
mqtt_host: localhost
|
||||||
mqtt_port: 1883
|
mqtt_port: 1883
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
Description=Craftbeer Pi
|
Description=Craftbeer Pi
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
WorkingDirectory=/home/pi
|
WorkingDirectory=/home/{{ user }}
|
||||||
ExecStart=/usr/local/bin/cbpi start
|
ExecStart={{ path }} start
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
|
@ -94,7 +94,7 @@ class ConfigFolder:
|
||||||
#['fermenter_data.json', 'file'], created by fermentation_controller @ start if not available
|
#['fermenter_data.json', 'file'], created by fermentation_controller @ start if not available
|
||||||
#['step_data.json', 'file'], created by step_controller @ start if not available
|
#['step_data.json', 'file'], created by step_controller @ start if not available
|
||||||
['config.json', 'file'],
|
['config.json', 'file'],
|
||||||
['craftbeerpi.service', 'file'],
|
['craftbeerpi.template', 'file'],
|
||||||
['chromium.desktop', 'file'],
|
['chromium.desktop', 'file'],
|
||||||
['dashboard', 'folder'],
|
['dashboard', 'folder'],
|
||||||
['dashboard/widgets', 'folder'],
|
['dashboard/widgets', 'folder'],
|
||||||
|
@ -106,7 +106,7 @@ class ConfigFolder:
|
||||||
]
|
]
|
||||||
for checking in required_config_content:
|
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])):
|
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:
|
# since there is no complete config we now check if the config folder may be completely empty to show hints:
|
||||||
if len(os.listdir(os.path.join(self.configFolderPath))) == 0 :
|
if len(os.listdir(os.path.join(self.configFolderPath))) == 0 :
|
||||||
print("***************************************************")
|
print("***************************************************")
|
||||||
print(f"the config folder '{self.configFolderPath}' seems to be completely empty")
|
print(f"the config folder '{self.configFolderPath}' seems to be completely empty")
|
||||||
|
@ -118,7 +118,7 @@ class ConfigFolder:
|
||||||
print("***************************************************")
|
print("***************************************************")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# if cbpi_dashboard_1.json doesnt exist at the new location (configFolderPath/dashboard)
|
# if cbpi_dashboard_1.json does'nt exist at the new location (configFolderPath/dashboard)
|
||||||
# we move every cbpi_dashboard_n.json file from the old location (configFolderPath) there.
|
# 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.
|
# this could be a config zip file restore from version 4.0.7.a4 or prior.
|
||||||
dashboard_1_path = os.path.join(self.configFolderPath, 'dashboard', 'cbpi_dashboard_1.json')
|
dashboard_1_path = os.path.join(self.configFolderPath, 'dashboard', 'cbpi_dashboard_1.json')
|
||||||
|
@ -132,7 +132,7 @@ class ConfigFolder:
|
||||||
try:
|
try:
|
||||||
with open(dashboard_1_path, 'r') as f:
|
with open(dashboard_1_path, 'r') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
if (len(data['elements']) == 0): # there may exist some pathes but pathes without elements in dashboard is not very likely
|
if (len(data['elements']) == 0): # there may exist some paths but paths without elements in dashboard is not very likely
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -142,6 +142,11 @@ class ConfigFolder:
|
||||||
def inform_missing_content(self, whatsmissing : str):
|
def inform_missing_content(self, whatsmissing : str):
|
||||||
if whatsmissing == "":
|
if whatsmissing == "":
|
||||||
return False
|
return False
|
||||||
|
# Starting with cbpi 4.2.0, the craftbeerpi.service file will be created dynamically from the template file based on the user id.
|
||||||
|
# Therefore, the service file is replaced with a template file in the config folder
|
||||||
|
if whatsmissing.find("craftbeerpi.template"):
|
||||||
|
self.copyDefaultFileIfNotExists("craftbeerpi.template")
|
||||||
|
return False
|
||||||
print("***************************************************")
|
print("***************************************************")
|
||||||
print(f"CraftBeerPi config content not found: {whatsmissing}")
|
print(f"CraftBeerPi config content not found: {whatsmissing}")
|
||||||
print("Please run 'cbpi setup' before starting the server ")
|
print("Please run 'cbpi setup' before starting the server ")
|
||||||
|
@ -181,7 +186,7 @@ class ConfigFolder:
|
||||||
self.copyDefaultFileIfNotExists("fermenter_data.json")
|
self.copyDefaultFileIfNotExists("fermenter_data.json")
|
||||||
self.copyDefaultFileIfNotExists("step_data.json")
|
self.copyDefaultFileIfNotExists("step_data.json")
|
||||||
self.copyDefaultFileIfNotExists("config.json")
|
self.copyDefaultFileIfNotExists("config.json")
|
||||||
self.copyDefaultFileIfNotExists("craftbeerpi.service")
|
self.copyDefaultFileIfNotExists("craftbeerpi.template")
|
||||||
self.copyDefaultFileIfNotExists("chromium.desktop")
|
self.copyDefaultFileIfNotExists("chromium.desktop")
|
||||||
|
|
||||||
print("Config Folder created")
|
print("Config Folder created")
|
||||||
|
@ -206,5 +211,5 @@ class ConfigFolder:
|
||||||
shutil.chown(os.path.join(dirpath, filename), owner, group)
|
shutil.chown(os.path.join(dirpath, filename), owner, group)
|
||||||
except:
|
except:
|
||||||
print("problems assigning file or folder permissions")
|
print("problems assigning file or folder permissions")
|
||||||
print("if this happend on windows its fine")
|
print("if this happened 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")
|
print("if this happened in the dev container running inside windows its also fine but you might have to rebuild the container if you run into further problems")
|
||||||
|
|
|
@ -60,11 +60,19 @@ class DashboardController:
|
||||||
async def get_current_dashboard(self):
|
async def get_current_dashboard(self):
|
||||||
current_dashboard_number = self.cbpi.config.get("current_dashboard_number", 1)
|
current_dashboard_number = self.cbpi.config.get("current_dashboard_number", 1)
|
||||||
return current_dashboard_number
|
return current_dashboard_number
|
||||||
|
|
||||||
async def set_current_dashboard(self, dashboard_id=1):
|
async def set_current_dashboard(self, dashboard_id=1):
|
||||||
await self.cbpi.config.set("current_dashboard_number", dashboard_id)
|
await self.cbpi.config.set("current_dashboard_number", dashboard_id)
|
||||||
return {"status": "OK"}
|
return {"status": "OK"}
|
||||||
|
|
||||||
|
async def get_current_grid(self):
|
||||||
|
current_grid = self.cbpi.config.get("current_grid", 5)
|
||||||
|
return current_grid
|
||||||
|
|
||||||
|
async def set_current_grid(self, grid_width=5):
|
||||||
|
await self.cbpi.config.set("current_grid", grid_width)
|
||||||
|
return {"status": "OK"}
|
||||||
|
|
||||||
async def get_slow_pipe_animation(self):
|
async def get_slow_pipe_animation(self):
|
||||||
slow_pipe_animation = self.cbpi.config.get("slow_pipe_animation", "Yes")
|
slow_pipe_animation = self.cbpi.config.get("slow_pipe_animation", "Yes")
|
||||||
return slow_pipe_animation
|
return slow_pipe_animation
|
|
@ -219,12 +219,12 @@ class SystemController:
|
||||||
for nic, addrs in ethernet.items():
|
for nic, addrs in ethernet.items():
|
||||||
if nic == "eth0":
|
if nic == "eth0":
|
||||||
for addr in addrs:
|
for addr in addrs:
|
||||||
if str(addr.family) == "AddressFamily.AF_INET":
|
if str(addr.family) == "AddressFamily.AF_INET" or str(addr.family) == "2":
|
||||||
if addr.address:
|
if addr.address:
|
||||||
eth0IP = addr.address
|
eth0IP = addr.address
|
||||||
if nic == "wlan0":
|
if nic == "wlan0":
|
||||||
for addr in addrs:
|
for addr in addrs:
|
||||||
if str(addr.family) == "AddressFamily.AF_INET":
|
if str(addr.family) == "AddressFamily.AF_INET" or str(addr.family) == "2":
|
||||||
if addr.address:
|
if addr.address:
|
||||||
wlan0IP = addr.address
|
wlan0IP = addr.address
|
||||||
info = psutil.net_if_stats()
|
info = psutil.net_if_stats()
|
||||||
|
|
|
@ -170,7 +170,7 @@ class UploadController:
|
||||||
# load beerxml file located in upload folder
|
# load beerxml file located in upload folder
|
||||||
self.path = self.cbpi.config_folder.get_upload_file("kbh.db")
|
self.path = self.cbpi.config_folder.get_upload_file("kbh.db")
|
||||||
if os.path.exists(self.path) is False:
|
if os.path.exists(self.path) is False:
|
||||||
self.cbpi.notify("File Not Found", "Please upload a kbh V2 databsel file", NotificationType.ERROR)
|
self.cbpi.notify("File Not Found", "Please upload a kbh V2 database file", NotificationType.ERROR)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get Recipe Nmae
|
# Get Recipe Nmae
|
||||||
|
@ -179,19 +179,19 @@ class UploadController:
|
||||||
c.execute('SELECT Sudname FROM Sud WHERE ID = ?', (Recipe_ID,))
|
c.execute('SELECT Sudname FROM Sud WHERE ID = ?', (Recipe_ID,))
|
||||||
row = c.fetchone()
|
row = c.fetchone()
|
||||||
name = row[0]
|
name = row[0]
|
||||||
|
|
||||||
# get MashIn Temp
|
# get MashIn Temp
|
||||||
mashin_temp = None
|
mashin_temp = None
|
||||||
c.execute('SELECT Temp FROM Rasten WHERE Typ = 0 AND SudID = ?', (Recipe_ID,))
|
c.execute('SELECT TempWasser FROM Maischplan WHERE Typ = 0 AND SudID = ?', (Recipe_ID,))
|
||||||
row = c.fetchone()
|
row = c.fetchone()
|
||||||
try:
|
try:
|
||||||
if self.cbpi.config.get("TEMP_UNIT", "C") == "C":
|
if self.cbpi.config.get("TEMP_UNIT", "C") == "C":
|
||||||
mashin_temp = str(int(row[0]))
|
mashin_temp = str(float(row[0]))
|
||||||
else:
|
else:
|
||||||
mashin_temp = str(round(9.0 / 5.0 * int(row[0]) + 32))
|
mashin_temp = str(round(9.0 / 5.0 * float(row[0]) + 32))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
logging.error(mashin_temp)
|
||||||
# get the hop addition times
|
# get the hop addition times
|
||||||
c.execute('SELECT Zeit, Name FROM Hopfengaben WHERE Vorderwuerze <> 1 AND SudID = ?', (Recipe_ID,))
|
c.execute('SELECT Zeit, Name FROM Hopfengaben WHERE Vorderwuerze <> 1 AND SudID = ?', (Recipe_ID,))
|
||||||
hops = c.fetchall()
|
hops = c.fetchall()
|
||||||
|
@ -201,11 +201,9 @@ class UploadController:
|
||||||
whirlpool.append(hop)
|
whirlpool.append(hop)
|
||||||
for whirl in whirlpool:
|
for whirl in whirlpool:
|
||||||
hops.remove(whirl)
|
hops.remove(whirl)
|
||||||
|
|
||||||
# get the misc addition times
|
# get the misc addition times
|
||||||
c.execute('SELECT Zugabedauer, Name FROM WeitereZutatenGaben WHERE Zeitpunkt = 1 AND SudID = ?', (Recipe_ID,))
|
c.execute('SELECT Zugabedauer, Name FROM WeitereZutatenGaben WHERE Zeitpunkt = 1 AND SudID = ?', (Recipe_ID,))
|
||||||
miscs = c.fetchall()
|
miscs = c.fetchall()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
c.execute('SELECT Zeit, Name FROM Hopfengaben WHERE Vorderwuerze = 1 AND SudID = ?', (Recipe_ID,))
|
c.execute('SELECT Zeit, Name FROM Hopfengaben WHERE Vorderwuerze = 1 AND SudID = ?', (Recipe_ID,))
|
||||||
FW_Hops = c.fetchall()
|
FW_Hops = c.fetchall()
|
||||||
|
@ -216,9 +214,7 @@ class UploadController:
|
||||||
c.execute('SELECT Kochdauer FROM Sud WHERE ID = ?', (Recipe_ID,))
|
c.execute('SELECT Kochdauer FROM Sud WHERE ID = ?', (Recipe_ID,))
|
||||||
row = c.fetchone()
|
row = c.fetchone()
|
||||||
BoilTime = str(int(row[0]))
|
BoilTime = str(int(row[0]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
await self.create_recipe(name)
|
await self.create_recipe(name)
|
||||||
|
|
||||||
if mashin_temp is not None:
|
if mashin_temp is not None:
|
||||||
|
@ -238,7 +234,7 @@ class UploadController:
|
||||||
}
|
}
|
||||||
await self.create_step(step_string)
|
await self.create_step(step_string)
|
||||||
|
|
||||||
for row in c.execute('SELECT Name, Temp, Dauer FROM Rasten WHERE Typ <> 0 AND SudID = ?', (Recipe_ID,)):
|
for row in c.execute('SELECT Name, TempRast, DauerRast FROM Maischplan WHERE Typ <> 0 AND SudID = ?', (Recipe_ID,)):
|
||||||
if mashin_temp is None and self.addmashin == "Yes":
|
if mashin_temp is None and self.addmashin == "Yes":
|
||||||
step_type = self.mashin if self.mashin != "" else "MashInStep"
|
step_type = self.mashin if self.mashin != "" else "MashInStep"
|
||||||
step_string = { "name": "MashIn",
|
step_string = { "name": "MashIn",
|
||||||
|
@ -246,7 +242,7 @@ class UploadController:
|
||||||
"AutoMode": self.AutoMode,
|
"AutoMode": self.AutoMode,
|
||||||
"Kettle": self.id,
|
"Kettle": self.id,
|
||||||
"Sensor": self.kettle.sensor,
|
"Sensor": self.kettle.sensor,
|
||||||
"Temp": str(int(row[1])) if self.TEMP_UNIT == "C" else str(round(9.0 / 5.0 * int(row[1]) + 32)),
|
"Temp": str(float(row[1])) if self.TEMP_UNIT == "C" else str(round(9.0 / 5.0 * float(row[1]) + 32)),
|
||||||
"Timer": "0",
|
"Timer": "0",
|
||||||
"Notification": "Target temperature reached. Please add malt."
|
"Notification": "Target temperature reached. Please add malt."
|
||||||
},
|
},
|
||||||
|
@ -263,7 +259,7 @@ class UploadController:
|
||||||
"AutoMode": self.AutoMode,
|
"AutoMode": self.AutoMode,
|
||||||
"Kettle": self.id,
|
"Kettle": self.id,
|
||||||
"Sensor": self.kettle.sensor,
|
"Sensor": self.kettle.sensor,
|
||||||
"Temp": str(int(row[1])) if self.TEMP_UNIT == "C" else str(round(9.0 / 5.0 * int(row[1]) + 32)),
|
"Temp": str(float(row[1])) if self.TEMP_UNIT == "C" else str(round(9.0 / 5.0 * float(row[1]) + 32)),
|
||||||
"Timer": str(int(row[2]))
|
"Timer": str(int(row[2]))
|
||||||
},
|
},
|
||||||
"status_text": "",
|
"status_text": "",
|
||||||
|
|
|
@ -260,9 +260,9 @@ class CraftBeerPi:
|
||||||
def _print_logo(self):
|
def _print_logo(self):
|
||||||
from pyfiglet import Figlet
|
from pyfiglet import Figlet
|
||||||
f = Figlet(font='big')
|
f = Figlet(font='big')
|
||||||
logger.info("\n%s" % f.renderText("CraftBeerPi %s " % self.version))
|
logger.warning("\n%s" % f.renderText("CraftBeerPi %s " % self.version))
|
||||||
logger.info("www.CraftBeerPi.com")
|
logger.warning("www.CraftBeerPi.com")
|
||||||
logger.info("(c) 2021/2022 Manuel Fritsch / Alexander Vollkopf")
|
logger.warning("(c) 2021/2022/2023 Manuel Fritsch / Alexander Vollkopf")
|
||||||
|
|
||||||
def _setup_http_index(self):
|
def _setup_http_index(self):
|
||||||
async def http_index(request):
|
async def http_index(request):
|
||||||
|
|
|
@ -64,6 +64,7 @@ class ConfigUpdate(CBPiExtension):
|
||||||
BoilKettle = self.cbpi.config.get("BoilKettle", None)
|
BoilKettle = self.cbpi.config.get("BoilKettle", None)
|
||||||
CONFIG_STATUS = self.cbpi.config.get("CONFIG_STATUS", None)
|
CONFIG_STATUS = self.cbpi.config.get("CONFIG_STATUS", None)
|
||||||
self.version=__version__
|
self.version=__version__
|
||||||
|
current_grid = self.cbpi.config.get("current_grid", None)
|
||||||
|
|
||||||
|
|
||||||
if boil_temp is None:
|
if boil_temp is None:
|
||||||
|
@ -491,6 +492,16 @@ class ConfigUpdate(CBPiExtension):
|
||||||
source="steps",
|
source="steps",
|
||||||
options=[{"label": "Yes", "value": "Yes"},
|
options=[{"label": "Yes", "value": "Yes"},
|
||||||
{"label": "No", "value": "No"}])
|
{"label": "No", "value": "No"}])
|
||||||
|
|
||||||
|
if current_grid is None:
|
||||||
|
logger.info("INIT Current Dashboard Number")
|
||||||
|
try:
|
||||||
|
await self.cbpi.config.add("current_grid", 5, type=ConfigType.NUMBER, description="Dashboard Grid Width",source="hidden")
|
||||||
|
except:
|
||||||
|
logger.warning('Unable to update database')
|
||||||
|
else:
|
||||||
|
if CONFIG_STATUS is None or CONFIG_STATUS != self.version:
|
||||||
|
await self.cbpi.config.add("current_grid", current_grid, type=ConfigType.NUMBER, description="Dashboard Grid Width",source="hidden")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -503,6 +514,8 @@ class ConfigUpdate(CBPiExtension):
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def setup(cbpi):
|
def setup(cbpi):
|
||||||
cbpi.plugin.register("ConfigUpdate", ConfigUpdate)
|
cbpi.plugin.register("ConfigUpdate", ConfigUpdate)
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -170,7 +170,7 @@ def setup(cbpi):
|
||||||
cbpi.plugin.register("OneWire", OneWire)
|
cbpi.plugin.register("OneWire", OneWire)
|
||||||
try:
|
try:
|
||||||
# Global Init
|
# Global Init
|
||||||
call(["modprobe", "w1-gpio"])
|
call(["sudo","modprobe", "w1-gpio"])
|
||||||
call(["modprobe", "w1-therm"])
|
call(["sudo","modprobe", "w1-therm"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -156,6 +156,40 @@ class DashBoardHttpEndpoints:
|
||||||
"""
|
"""
|
||||||
dashboard_id = int(request.match_info['id'])
|
dashboard_id = int(request.match_info['id'])
|
||||||
return web.json_response(await self.cbpi.dashboard.set_current_dashboard(dashboard_id))
|
return web.json_response(await self.cbpi.dashboard.set_current_dashboard(dashboard_id))
|
||||||
|
|
||||||
|
@request_mapping(path="/currentgrid", method="GET", auth_required=False)
|
||||||
|
async def get_current_grid(self, request):
|
||||||
|
"""
|
||||||
|
---
|
||||||
|
description: Get Dashboard Numbers
|
||||||
|
tags:
|
||||||
|
- Dashboard
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: successful operation
|
||||||
|
"""
|
||||||
|
return web.json_response(await self.cbpi.dashboard.get_current_grid(), dumps=json_dumps)
|
||||||
|
|
||||||
|
@request_mapping(path="/{width}/currentgrid", method="POST", auth_required=False)
|
||||||
|
async def set_current_grid(self, request):
|
||||||
|
"""
|
||||||
|
---
|
||||||
|
description: Set Current Grid Width
|
||||||
|
tags:
|
||||||
|
- Dashboard
|
||||||
|
parameters:
|
||||||
|
- name: "width"
|
||||||
|
in: "path"
|
||||||
|
description: "Grid Width"
|
||||||
|
required: true
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: successful operation
|
||||||
|
"""
|
||||||
|
grid_width = int(request.match_info['width'])
|
||||||
|
return web.json_response(await self.cbpi.dashboard.set_current_grid(grid_width))
|
||||||
|
|
||||||
@request_mapping(path="/slowPipeAnimation", method="GET", auth_required=False)
|
@request_mapping(path="/slowPipeAnimation", method="GET", auth_required=False)
|
||||||
async def get_slow_pipe_animation(self, request):
|
async def get_slow_pipe_animation(self, request):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
typing-extensions>=4
|
typing-extensions>=4
|
||||||
aiohttp==3.8.5
|
aiohttp==3.8.6
|
||||||
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
|
||||||
|
@ -7,22 +7,22 @@ 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==41.0.2
|
cryptography==41.0.5
|
||||||
pyopenssl==23.2.0
|
pyopenssl==23.3.0
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
voluptuous==0.13.1
|
voluptuous==0.13.1
|
||||||
pyfiglet==0.8.post1
|
pyfiglet==1.0.2
|
||||||
pandas==1.5.3
|
pandas==1.5.3
|
||||||
shortuuid==1.0.11
|
shortuuid==1.0.11
|
||||||
tabulate==0.9.0
|
tabulate==0.9.0
|
||||||
numpy==1.24.1
|
numpy==1.24.1
|
||||||
cbpi4gui
|
cbpi4gui
|
||||||
click==8.1.3
|
click==8.1.7
|
||||||
importlib_metadata==4.11.1
|
importlib_metadata==4.11.1
|
||||||
aiomqtt==1.0.0
|
aiomqtt==1.2.1
|
||||||
psutil==5.9.4
|
psutil==5.9.6
|
||||||
zipp>=0.5
|
zipp>=0.5
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
pytest-aiohttp
|
pytest-aiohttp
|
||||||
coverage==6.3.1
|
coverage==6.3.1
|
||||||
inquirer==3.1.1
|
inquirer==3.1.3
|
||||||
|
|
16
setup.py
16
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.5",
|
"aiohttp==3.8.6",
|
||||||
"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,18 +47,18 @@ 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==41.0.2",
|
"cryptography==41.0.5",
|
||||||
"pyopenssl==23.2.0",
|
"pyopenssl==23.3.0",
|
||||||
"requests==2.31.0",
|
"requests==2.31.0",
|
||||||
"voluptuous==0.13.1",
|
"voluptuous==0.13.1",
|
||||||
"pyfiglet==0.8.post1",
|
"pyfiglet==1.0.2",
|
||||||
'click==8.1.3',
|
'click==8.1.7',
|
||||||
'shortuuid==1.0.11',
|
'shortuuid==1.0.11',
|
||||||
'tabulate==0.9.0',
|
'tabulate==0.9.0',
|
||||||
'aiomqtt==1.0.0',
|
'aiomqtt==1.2.1',
|
||||||
'inquirer==3.1.1',
|
'inquirer==3.1.3',
|
||||||
'colorama==0.4.6',
|
'colorama==0.4.6',
|
||||||
'psutil==5.9.4',
|
'psutil==5.9.6',
|
||||||
'cbpi4gui',
|
'cbpi4gui',
|
||||||
'importlib_metadata',
|
'importlib_metadata',
|
||||||
'numpy==1.24.1',
|
'numpy==1.24.1',
|
||||||
|
|
|
@ -80,10 +80,10 @@
|
||||||
"options": null,
|
"options": null,
|
||||||
"source": "hidden",
|
"source": "hidden",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": "4.1.10.a1"
|
"value": "4.2.0.a6"
|
||||||
},
|
},
|
||||||
"CSVLOGFILES": {
|
"CSVLOGFILES": {
|
||||||
"description": "Write sensor data to csv logfiles",
|
"description": "Write sensor data to csv logfiles (enabling requires restart)",
|
||||||
"name": "CSVLOGFILES",
|
"name": "CSVLOGFILES",
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
"value": "Yes"
|
"value": "Yes"
|
||||||
},
|
},
|
||||||
"INFLUXDB": {
|
"INFLUXDB": {
|
||||||
"description": "Write sensor data to influxdb",
|
"description": "Write sensor data to influxdb (enabling requires restart)",
|
||||||
"name": "INFLUXDB",
|
"name": "INFLUXDB",
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,6 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
class SensorTestCase(CraftBeerPiTestCase):
|
class SensorTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_crud(self):
|
async def test_crud(self):
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
|
|
@ -4,7 +4,6 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
class StepTestCase(CraftBeerPiTestCase):
|
class StepTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_get(self):
|
async def test_get(self):
|
||||||
|
|
||||||
resp = await self.client.request("GET", "/step2")
|
resp = await self.client.request("GET", "/step2")
|
||||||
|
@ -12,7 +11,6 @@ class StepTestCase(CraftBeerPiTestCase):
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_crud(self):
|
async def test_crud(self):
|
||||||
data = {
|
data = {
|
||||||
"name": "Test",
|
"name": "Test",
|
||||||
|
|
|
@ -4,7 +4,6 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
class IndexTestCase(CraftBeerPiTestCase):
|
class IndexTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_endpoints(self):
|
async def test_endpoints(self):
|
||||||
# Test Index Page
|
# Test Index Page
|
||||||
resp = await self.client.post(path="/system/restart")
|
resp = await self.client.post(path="/system/restart")
|
||||||
|
|
Loading…
Reference in a new issue