mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-23 15:38:14 +01:00
commit
045e10bfd7
24 changed files with 301 additions and 77 deletions
|
@ -1,9 +1,10 @@
|
||||||
FROM mcr.microsoft.com/vscode/devcontainers/python:3.11-bullseye
|
FROM mcr.microsoft.com/vscode/devcontainers/python:3.11-bookworm
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get upgrade -y
|
&& apt-get upgrade -y
|
||||||
RUN apt-get install --no-install-recommends -y \
|
RUN apt-get install --no-install-recommends -y \
|
||||||
libatlas-base-dev \
|
libatlas-base-dev \
|
||||||
|
libsystemd-dev \
|
||||||
libffi-dev \
|
libffi-dev \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
|
@ -25,7 +25,10 @@ jobs:
|
||||||
- name: Setup python environment
|
- name: Setup python environment
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.9'
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: sudo apt install -y libsystemd-dev
|
||||||
|
|
||||||
- name: Clean
|
- name: Clean
|
||||||
run: python setup.py clean --all
|
run: python setup.py clean --all
|
||||||
|
|
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "run CraftBeerPi4",
|
"name": "run CraftBeerPi4",
|
||||||
"type": "python",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "run",
|
"module": "run",
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "create CraftBeerPi4 plugin",
|
"name": "create CraftBeerPi4 plugin",
|
||||||
"type": "python",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "run",
|
"module": "run",
|
||||||
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "create"]
|
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "create"]
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "setup CraftBeerPi4: create config folder structure",
|
"name": "setup CraftBeerPi4: create config folder structure",
|
||||||
"type": "python",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "run",
|
"module": "run",
|
||||||
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "setup"]
|
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "setup"]
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "run tests",
|
"name": "run tests",
|
||||||
"type": "python",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "pytest",
|
"module": "pytest",
|
||||||
"args": ["tests"]
|
"args": ["tests"]
|
||||||
|
|
|
@ -10,6 +10,7 @@ RUN apt-get update \
|
||||||
&& apt-get upgrade -y
|
&& apt-get upgrade -y
|
||||||
RUN apt-get install --no-install-recommends -y \
|
RUN apt-get install --no-install-recommends -y \
|
||||||
libatlas-base-dev \
|
libatlas-base-dev \
|
||||||
|
libsystemd-dev \
|
||||||
libffi-dev \
|
libffi-dev \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
__version__ = "4.4.0"
|
__version__ = "4.4.1.rc0"
|
||||||
__codename__ = "Yeast Starter"
|
__codename__ = "Yeast Starter"
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ class CBPiActor(metaclass=ABCMeta):
|
||||||
self.state = False
|
self.state = False
|
||||||
self.running = False
|
self.running = False
|
||||||
self.power = 100
|
self.power = 100
|
||||||
|
self.timer = 0
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -56,13 +56,14 @@ class Actor:
|
||||||
props: Props = Props()
|
props: Props = Props()
|
||||||
state: bool = False
|
state: bool = False
|
||||||
power: int = 100
|
power: int = 100
|
||||||
|
timer: int = 0
|
||||||
type: str = None
|
type: str = None
|
||||||
instance: str = None
|
instance: str = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "name={} props={}, state={}, type={}, power={}".format(self.name, self.props, self.state, self.type, self.power)
|
return "name={} props={}, state={}, type={}, power={}, timer={}".format(self.name, self.props, self.state, self.type, self.power, self.timer)
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return dict(id=self.id, name=self.name, type=self.type, props=self.props.to_dict(), state=self.instance.get_state(), power=self.power)
|
return dict(id=self.id, name=self.name, type=self.type, props=self.props.to_dict(), state=self.instance.get_state(), power=self.power, timer=self.timer)
|
||||||
|
|
||||||
class DataType(Enum):
|
class DataType(Enum):
|
||||||
VALUE="value"
|
VALUE="value"
|
||||||
|
@ -78,6 +79,8 @@ class Sensor:
|
||||||
type: str = None
|
type: str = None
|
||||||
instance: str = None
|
instance: str = None
|
||||||
datatype: DataType = DataType.VALUE
|
datatype: DataType = DataType.VALUE
|
||||||
|
inrange: bool = True
|
||||||
|
temp_range: float = 0
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "name={} props={}, state={}".format(self.name, self.props, self.state)
|
return "name={} props={}, state={}".format(self.name, self.props, self.state)
|
||||||
|
|
|
@ -18,6 +18,10 @@ class CBPiSensor(CBPiBase, metaclass=ABCMeta):
|
||||||
self.state = False
|
self.state = False
|
||||||
self.running = False
|
self.running = False
|
||||||
self.datatype=DataType.VALUE
|
self.datatype=DataType.VALUE
|
||||||
|
self.inrange = True
|
||||||
|
self.temprange = 0
|
||||||
|
self.kettle = None
|
||||||
|
self.fermenter = None
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
pass
|
pass
|
||||||
|
@ -34,12 +38,34 @@ class CBPiSensor(CBPiBase, metaclass=ABCMeta):
|
||||||
def get_unit(self):
|
def get_unit(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def push_update(self, value, mqtt = True):
|
def checkrange(self, value):
|
||||||
|
# if Kettle and fermenter are selected, range check is deactivated
|
||||||
|
if self.kettle is not None and self.fermenter is not None:
|
||||||
|
return True
|
||||||
try:
|
try:
|
||||||
self.cbpi.ws.send(dict(topic="sensorstate", id=self.id, value=value, datatype=self.datatype.value))
|
if self.kettle is not None:
|
||||||
|
target_temp=float(self.kettle.target_temp)
|
||||||
|
if self.fermenter is not None:
|
||||||
|
target_temp=float(self.fermenter.target_temp)
|
||||||
|
|
||||||
|
diff=abs(target_temp-value)
|
||||||
|
if diff>self.temprange:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def push_update(self, value, mqtt = True):
|
||||||
|
if self.temprange !=0:
|
||||||
|
self.inrange = self.checkrange(value)
|
||||||
|
else:
|
||||||
|
self.inrange = True
|
||||||
|
try:
|
||||||
|
self.cbpi.ws.send(dict(topic="sensorstate", id=self.id, value=value, datatype=self.datatype.value, inrange=self.inrange))
|
||||||
if mqtt:
|
if mqtt:
|
||||||
self.cbpi.push_update("cbpi/sensordata/{}".format(self.id), dict(id=self.id, value=value, datatype=self.datatype.value), retain=True)
|
self.cbpi.push_update("cbpi/sensordata/{}".format(self.id), dict(id=self.id, value=value, datatype=self.datatype.value, inrange=self.inrange), retain=True)
|
||||||
# self.cbpi.push_update("cbpi/sensor/{}/udpate".format(self.id), dict(id=self.id, value=value), retain=True)
|
# self.cbpi.push_update("cbpi/sensor/{}/udpate".format(self.id), dict(id=self.id, value=value), retain=True)
|
||||||
except:
|
except:
|
||||||
logging.error("Failed to push sensor update for sensor {}".format(self.id))
|
logging.error("Failed to push sensor update for sensor {}".format(self.id))
|
||||||
|
|
|
@ -287,7 +287,9 @@ def main(context, config_folder_path, logs_folder_path, debug_log_level):
|
||||||
logger.info(f"logs folder '{logs_folder_path}' doesnt exist and we are trying to create it")
|
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)
|
pathlib.Path(logs_folder_path).mkdir(parents=True, exist_ok=True)
|
||||||
logger.info(f"logs folder '{logs_folder_path}' successfully created")
|
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))
|
handler=logging.handlers.RotatingFileHandler(os.path.join(logs_folder_path, f"cbpi.log"), maxBytes=1000000, backupCount=3)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
handler.setFormatter(formatter)
|
||||||
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)
|
||||||
|
|
|
@ -46,16 +46,28 @@ class ConfigFolder:
|
||||||
if os.path.exists(os.path.join(backupfile)) is True:
|
if os.path.exists(os.path.join(backupfile)) is True:
|
||||||
print("***************************************************")
|
print("***************************************************")
|
||||||
print("Found backup of config. Starting restore")
|
print("Found backup of config. Starting restore")
|
||||||
required_content=['dashboard/', 'recipes/', 'upload/', 'config.json', 'config.yaml']
|
required_content=['dashboard/',
|
||||||
|
'recipes/',
|
||||||
|
'upload/',
|
||||||
|
'sensor.json',
|
||||||
|
'actor.json',
|
||||||
|
'kettle.json',
|
||||||
|
'config.json',
|
||||||
|
'craftbeerpi.template',
|
||||||
|
'chromium.desktop',
|
||||||
|
'config.yaml']
|
||||||
zip=zipfile.ZipFile(backupfile)
|
zip=zipfile.ZipFile(backupfile)
|
||||||
zip_content_list = zip.namelist()
|
zip_content_list = zip.namelist()
|
||||||
zip_content = True
|
zip_content = True
|
||||||
|
missing_content=[]
|
||||||
print("Checking content of zip file")
|
print("Checking content of zip file")
|
||||||
|
|
||||||
for content in required_content:
|
for content in required_content:
|
||||||
try:
|
try:
|
||||||
check = zip_content_list.index(content)
|
check = zip_content_list.index(content)
|
||||||
except:
|
except:
|
||||||
zip_content = False
|
zip_content = False
|
||||||
|
missing_content.append(content)
|
||||||
|
|
||||||
if zip_content == True:
|
if zip_content == True:
|
||||||
print("Found correct content. Starting Restore process")
|
print("Found correct content. Starting Restore process")
|
||||||
|
@ -74,10 +86,23 @@ class ConfigFolder:
|
||||||
print(f"Changing owner and group of config folder recursively to {owner}:{group}")
|
print(f"Changing owner and group of config folder recursively to {owner}:{group}")
|
||||||
self.recursive_chown(output_path, owner, group)
|
self.recursive_chown(output_path, owner, group)
|
||||||
print("Removing backup file")
|
print("Removing backup file")
|
||||||
print("contents of restored_config.zip file have been restored.")
|
os.remove(backupfile)
|
||||||
print("in case of a partial backup you will still be prompted to run 'cbpi setup'.")
|
Line1="Contents of restored_config.zip file have been restored."
|
||||||
|
Line2="In case of a partial backup you will still be prompted to run 'cbpi setup'."
|
||||||
|
print(Line1)
|
||||||
|
print(Line2)
|
||||||
else:
|
else:
|
||||||
print("Wrong Content in zip file. No restore possible")
|
zip.close()
|
||||||
|
Line1="Wrong Content in zip file. No restore possible"
|
||||||
|
Line2=f'These files are missing {missing_content}'
|
||||||
|
print(Line1)
|
||||||
|
print(Line2)
|
||||||
|
restorelogfile = os.path.join(self.configFolderPath, "restore_error.log")
|
||||||
|
f = open(restorelogfile, "w")
|
||||||
|
f.write(Line1+"\n")
|
||||||
|
f.write(Line2+"\n")
|
||||||
|
f.close()
|
||||||
|
|
||||||
print("renaming zip file so it will be ignored on the next start")
|
print("renaming zip file so it will be ignored on the next start")
|
||||||
try:
|
try:
|
||||||
os.rename(backupfile, os.path.join(self.configFolderPath, "UNRESTORABLE_restored_config.zip"))
|
os.rename(backupfile, os.path.join(self.configFolderPath, "UNRESTORABLE_restored_config.zip"))
|
||||||
|
|
|
@ -67,6 +67,16 @@ class ActorController(BasicController):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Failed to update Actor {} {}".format(id, e))
|
logging.error("Failed to update Actor {} {}".format(id, e))
|
||||||
|
|
||||||
|
async def timeractor_update(self, id, timer):
|
||||||
|
try:
|
||||||
|
item = self.find_by_id(id)
|
||||||
|
item.timer = round(timer)
|
||||||
|
#await self.push_udpate()
|
||||||
|
self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))))
|
||||||
|
self.cbpi.push_update("cbpi/actorupdate/{}".format(id), item.to_dict())
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("Failed to update Actor {} {}".format(id, e))
|
||||||
|
|
||||||
async def ws_actor_update(self):
|
async def ws_actor_update(self):
|
||||||
try:
|
try:
|
||||||
#await self.push_udpate()
|
#await self.push_udpate()
|
||||||
|
|
|
@ -4,6 +4,8 @@ from cbpi.api.dataclasses import NotificationType
|
||||||
from cbpi.api import *
|
from cbpi.api import *
|
||||||
import logging
|
import logging
|
||||||
import shortuuid
|
import shortuuid
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
class NotificationController:
|
class NotificationController:
|
||||||
|
|
||||||
def __init__(self, cbpi):
|
def __init__(self, cbpi):
|
||||||
|
@ -15,6 +17,20 @@ class NotificationController:
|
||||||
logging.root.addFilter(self.notify_log_event)
|
logging.root.addFilter(self.notify_log_event)
|
||||||
self.callback_cache = {}
|
self.callback_cache = {}
|
||||||
self.listener = {}
|
self.listener = {}
|
||||||
|
self.notifications = []
|
||||||
|
self.update_key="notificationupdate"
|
||||||
|
self.sorting=False
|
||||||
|
self.check_startup_message()
|
||||||
|
|
||||||
|
def check_startup_message(self):
|
||||||
|
self.restore_error = self.cbpi.config_folder.get_file_path("restore_error.log")
|
||||||
|
try:
|
||||||
|
with open(self.restore_error) as f:
|
||||||
|
for line in f:
|
||||||
|
self.notifications.insert(0,[f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: Restore Error | {line}'])
|
||||||
|
os.remove(self.restore_error)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
def notify_log_event(self, record):
|
def notify_log_event(self, record):
|
||||||
NOTIFY_ON_ERROR = self.cbpi.config.get("NOTIFY_ON_ERROR", "No")
|
NOTIFY_ON_ERROR = self.cbpi.config.get("NOTIFY_ON_ERROR", "No")
|
||||||
|
@ -32,6 +48,10 @@ class NotificationController:
|
||||||
return True
|
return True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
result = self.notifications
|
||||||
|
return result
|
||||||
|
|
||||||
def add_listener(self, method):
|
def add_listener(self, method):
|
||||||
listener_id = shortuuid.uuid()
|
listener_id = shortuuid.uuid()
|
||||||
self.listener[listener_id] = method
|
self.listener[listener_id] = method
|
||||||
|
@ -69,8 +89,16 @@ class NotificationController:
|
||||||
self.cbpi.ws.send(dict(id=notifcation_id, topic="notifiaction", type=type.value, title=title, message=message, action=actions, timeout=timeout))
|
self.cbpi.ws.send(dict(id=notifcation_id, topic="notifiaction", type=type.value, title=title, message=message, action=actions, timeout=timeout))
|
||||||
data = dict(type=type.value, title=title, message=message, action=actions, timeout=timeout)
|
data = dict(type=type.value, title=title, message=message, action=actions, timeout=timeout)
|
||||||
self.cbpi.push_update(topic="cbpi/notification", data=data)
|
self.cbpi.push_update(topic="cbpi/notification", data=data)
|
||||||
|
self.notifications.insert(0,[f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}: {title} | {message}'])
|
||||||
|
if len(self.notifications) > 100:
|
||||||
|
self.notifications = self.notifications[:100]
|
||||||
|
self.cbpi.ws.send(dict(topic=self.update_key, data=self.notifications),self.sorting)
|
||||||
asyncio.create_task(self._call_listener(title, message, type, action))
|
asyncio.create_task(self._call_listener(title, message, type, action))
|
||||||
|
|
||||||
|
def delete_all_notifications(self):
|
||||||
|
self.notifications = []
|
||||||
|
self.cbpi.ws.send(dict(topic=self.update_key, data=self.notifications),self.sorting)
|
||||||
|
|
||||||
|
|
||||||
def notify_callback(self, notification_id, action_id) -> None:
|
def notify_callback(self, notification_id, action_id) -> None:
|
||||||
try:
|
try:
|
||||||
|
@ -79,5 +107,5 @@ class NotificationController:
|
||||||
asyncio.create_task(action.method())
|
asyncio.create_task(action.method())
|
||||||
del self.callback_cache[notification_id]
|
del self.callback_cache[notification_id]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("Failed to call notificatoin callback")
|
self.logger.error("Failed to call notification callback")
|
||||||
|
|
|
@ -15,6 +15,15 @@ import zipfile
|
||||||
import socket
|
import socket
|
||||||
import importlib
|
import importlib
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
from datetime import datetime, timedelta, date
|
||||||
|
import glob
|
||||||
|
|
||||||
|
try:
|
||||||
|
from systemd import journal
|
||||||
|
systemd_available=True
|
||||||
|
except Exception:
|
||||||
|
logging.warning("Failed to load systemd library. logfile download not available")
|
||||||
|
systemd_available=False
|
||||||
|
|
||||||
class SystemController:
|
class SystemController:
|
||||||
|
|
||||||
|
@ -22,6 +31,7 @@ class SystemController:
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.service = cbpi.actor
|
self.service = cbpi.actor
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.logsFolderPath = self.cbpi.config_folder.logsFolderPath
|
||||||
|
|
||||||
self.cbpi.app.on_startup.append(self.check_for_update)
|
self.cbpi.app.on_startup.append(self.check_for_update)
|
||||||
|
|
||||||
|
@ -40,9 +50,22 @@ class SystemController:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def backupConfig(self):
|
async def backupConfig(self):
|
||||||
output_filename = "cbpi4_config"
|
files=glob.glob('*cbpi4_config*.zip')
|
||||||
|
for f in files:
|
||||||
|
try:
|
||||||
|
os.remove(f)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("Cannot remove old config backup: {}".format(e))
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_date = date.today()
|
||||||
|
current_date=str(current_date).replace("-","_")
|
||||||
|
output_filename = current_date+"_cbpi4_config"
|
||||||
|
except:
|
||||||
|
output_filename = "cbpi4_config"
|
||||||
dir_name = pathlib.Path(self.cbpi.config_folder.get_file_path(''))
|
dir_name = pathlib.Path(self.cbpi.config_folder.get_file_path(''))
|
||||||
shutil.make_archive(output_filename, 'zip', dir_name)
|
shutil.make_archive(output_filename, 'zip', dir_name)
|
||||||
|
return output_filename+".zip"
|
||||||
|
|
||||||
async def plugins_list(self):
|
async def plugins_list(self):
|
||||||
result = []
|
result = []
|
||||||
|
@ -77,19 +100,38 @@ class SystemController:
|
||||||
fullkettlename = pathlib.Path(os.path.join(".",kettlename))
|
fullkettlename = pathlib.Path(os.path.join(".",kettlename))
|
||||||
|
|
||||||
output_filename="cbpi4_log.zip"
|
output_filename="cbpi4_log.zip"
|
||||||
|
result=[]
|
||||||
|
if systemd_available:
|
||||||
|
j = journal.Reader()
|
||||||
|
if logtime == "b":
|
||||||
|
j.this_boot()
|
||||||
|
else:
|
||||||
|
since = datetime.now() - timedelta(hours=int(logtime))
|
||||||
|
j.seek_realtime(since)
|
||||||
|
j.add_match(_SYSTEMD_UNIT="craftbeerpi.service")
|
||||||
|
|
||||||
if logtime == "b":
|
for entry in j:
|
||||||
os.system('journalctl -b -u craftbeerpi.service --output cat > {}'.format(fullname))
|
result.append(entry['MESSAGE'])
|
||||||
else:
|
else:
|
||||||
os.system('journalctl --since \"{} hours ago\" -u craftbeerpi.service --output cat > {}'.format(logtime, fullname))
|
try:
|
||||||
|
logfilename=pathlib.Path(self.logsFolderPath+"/"+"cbpi.log")
|
||||||
|
with open(logfilename) as f:
|
||||||
|
for line in f:
|
||||||
|
result.append(line.rstrip('\n'))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(fullname, 'w') as f:
|
||||||
|
for line in result:
|
||||||
|
f.write(f"{line}\n")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
plugins = await self.plugins_list()
|
plugins = await self.plugins_list()
|
||||||
|
|
||||||
with open(fullpluginname, 'w') as f:
|
with open(fullpluginname, 'w') as f:
|
||||||
f.write(plugins)
|
f.write(plugins)
|
||||||
|
|
||||||
#os.system('echo "{}" >> {}'.format(plugins,fullpluginname))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
actors = self.cbpi.actor.get_state()
|
actors = self.cbpi.actor.get_state()
|
||||||
json.dump(actors['data'],open(fullactorname,'w'),indent=4, sort_keys=True)
|
json.dump(actors['data'],open(fullactorname,'w'),indent=4, sort_keys=True)
|
||||||
|
|
|
@ -185,13 +185,12 @@ class UploadController:
|
||||||
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(float(row[0]))
|
mashin_temp = str(int(row[0]))
|
||||||
else:
|
else:
|
||||||
mashin_temp = str(round(9.0 / 5.0 * float(row[0]) + 32))
|
mashin_temp = str(round(9.0 / 5.0 * int(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()
|
||||||
|
@ -242,7 +241,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(float(row[1])) if self.TEMP_UNIT == "C" else str(round(9.0 / 5.0 * float(row[1]) + 32)),
|
"Temp": str(int(row[1])) if self.TEMP_UNIT == "C" else str(round(9.0 / 5.0 * int(row[1]) + 32)),
|
||||||
"Timer": "0",
|
"Timer": "0",
|
||||||
"Notification": "Target temperature reached. Please add malt."
|
"Notification": "Target temperature reached. Please add malt."
|
||||||
},
|
},
|
||||||
|
@ -259,7 +258,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(float(row[1])) if self.TEMP_UNIT == "C" else str(round(9.0 / 5.0 * float(row[1]) + 32)),
|
"Temp": str(int(row[1])) if self.TEMP_UNIT == "C" else str(round(9.0 / 5.0 * int(row[1]) + 32)),
|
||||||
"Timer": str(int(row[2]))
|
"Timer": str(int(row[2]))
|
||||||
},
|
},
|
||||||
"status_text": "",
|
"status_text": "",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import json
|
||||||
from cbpi.api import *
|
from cbpi.api import *
|
||||||
from cbpi.api.config import ConfigType
|
from cbpi.api.config import ConfigType
|
||||||
from cbpi.api.base import CBPiBase
|
from cbpi.api.base import CBPiBase
|
||||||
|
import glob
|
||||||
from cbpi import __version__
|
from cbpi import __version__
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -503,6 +504,43 @@ class ConfigUpdate(CBPiExtension):
|
||||||
if CONFIG_STATUS is None or CONFIG_STATUS != self.version:
|
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")
|
await self.cbpi.config.add("current_grid", current_grid, type=ConfigType.NUMBER, description="Dashboard Grid Width",source="hidden")
|
||||||
|
|
||||||
|
# Check if CustomSVG props are correct for latest functionality (Widget dependent on acxtor state -> UI >= 0.3.14.a8)
|
||||||
|
plugin_list = await self.cbpi.plugin.load_plugin_list("cbpi4gui")
|
||||||
|
try:
|
||||||
|
version= plugin_list[0].get("Version", "not detected")
|
||||||
|
except:
|
||||||
|
version="not detected"
|
||||||
|
|
||||||
|
logging.info(f'GUI Version: {version}')
|
||||||
|
try:
|
||||||
|
dashboard_files=glob.glob(self.cbpi.config_folder.get_dashboard_path('cbpi_dashboard*.json'))
|
||||||
|
write=False
|
||||||
|
|
||||||
|
for dashboard_file in dashboard_files:
|
||||||
|
with open(dashboard_file, 'r') as file:
|
||||||
|
data = json.load(file)
|
||||||
|
|
||||||
|
for id in data['elements']:
|
||||||
|
if id['type'] == 'CustomSVG':
|
||||||
|
try:
|
||||||
|
testoff = (id['props']['WidgetOff'])
|
||||||
|
except:
|
||||||
|
id['props']['WidgetOff'] = id['props']['name']
|
||||||
|
write=True
|
||||||
|
try:
|
||||||
|
teston = (id['props']['WidgetOn'])
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
id['props']['WidgetOn'] = id['props']['nameon']
|
||||||
|
write=True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if write:
|
||||||
|
with open(dashboard_file, 'w') as outfile:
|
||||||
|
json.dump(data, outfile, indent=4, sort_keys=True)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
|
||||||
## Check if influxdbname is in config
|
## Check if influxdbname is in config
|
||||||
|
|
|
@ -55,9 +55,9 @@ class FermenterHysteresis(CBPiFermenterLogic):
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
try:
|
try:
|
||||||
self.heater_offset_min = float(self.props.get("HeaterOffsetOn", 0))
|
self.heater_offset_min = float(self.props.get("HeaterOffsetOn", 0.2))
|
||||||
self.heater_offset_max = float(self.props.get("HeaterOffsetOff", 0))
|
self.heater_offset_max = float(self.props.get("HeaterOffsetOff", 0))
|
||||||
self.cooler_offset_min = float(self.props.get("CoolerOffsetOn", 0))
|
self.cooler_offset_min = float(self.props.get("CoolerOffsetOn", 0.2))
|
||||||
self.cooler_offset_max = float(self.props.get("CoolerOffsetOff", 0))
|
self.cooler_offset_max = float(self.props.get("CoolerOffsetOff", 0))
|
||||||
self.heater_max_power = int(self.props.get("HeaterMaxPower", 100))
|
self.heater_max_power = int(self.props.get("HeaterMaxPower", 100))
|
||||||
self.cooler_max_power = int(self.props.get("CoolerMaxPower", 100))
|
self.cooler_max_power = int(self.props.get("CoolerMaxPower", 100))
|
||||||
|
@ -153,9 +153,9 @@ class FermenterSpundingHysteresis(CBPiFermenterLogic):
|
||||||
logging.info("No valve or pressure sensor defined")
|
logging.info("No valve or pressure sensor defined")
|
||||||
|
|
||||||
async def temperature_control(self):
|
async def temperature_control(self):
|
||||||
self.heater_offset_min = float(self.props.get("HeaterOffsetOn", 0))
|
self.heater_offset_min = float(self.props.get("HeaterOffsetOn", 0.2))
|
||||||
self.heater_offset_max = float(self.props.get("HeaterOffsetOff", 0))
|
self.heater_offset_max = float(self.props.get("HeaterOffsetOff", 0))
|
||||||
self.cooler_offset_min = float(self.props.get("CoolerOffsetOn", 0))
|
self.cooler_offset_min = float(self.props.get("CoolerOffsetOn", 0.2))
|
||||||
self.cooler_offset_max = float(self.props.get("CoolerOffsetOff", 0))
|
self.cooler_offset_max = float(self.props.get("CoolerOffsetOff", 0))
|
||||||
|
|
||||||
heater = self.cbpi.actor.find_by_id(self.heater)
|
heater = self.cbpi.actor.find_by_id(self.heater)
|
||||||
|
|
|
@ -12,10 +12,12 @@ cache = {}
|
||||||
|
|
||||||
@parameters([Property.Text(label="Key", configurable=True, description="Http Key"),
|
@parameters([Property.Text(label="Key", configurable=True, description="Http Key"),
|
||||||
Property.Number(label="Timeout", configurable="True",unit="sec",description="Timeout in seconds to send notification (default:60 | deactivated: 0)"),
|
Property.Number(label="Timeout", configurable="True",unit="sec",description="Timeout in seconds to send notification (default:60 | deactivated: 0)"),
|
||||||
Property.Kettle(label="Kettle", description="Reduced logging if Kettle is inactive (only Kettle or Fermenter to be selected)"),
|
Property.Kettle(label="Kettle", description="Reduced logging if Kettle is inactive / range warning in dashboard (only Kettle or Fermenter to be selected)"),
|
||||||
Property.Fermenter(label="Fermenter", description="Reduced logging in seconds if Fermenter is inactive (only Kettle or Fermenter to be selected)"),
|
Property.Fermenter(label="Fermenter", description="Reduced logging in seconds if Fermenter is inactive / range warning in dashboard (only Kettle or Fermenter to be selected)"),
|
||||||
Property.Number(label="ReducedLogging", configurable=True, description="Reduced logging frequency in seconds if selected Kettle or Fermenter is inactive (default: 60 sec | disabled: 0)")])
|
Property.Number(label="ReducedLogging", configurable=True, description="Reduced logging frequency in seconds if selected Kettle or Fermenter is inactive (default: 60 sec | disabled: 0)"),
|
||||||
|
Property.Number(label="TempRange", configurable=True, unit="degree",
|
||||||
|
description="Temp range in degree between reading and target temp of fermenter/kettle. Larger difference shows different color in dashboard (default:0 | deactivated: 0)")
|
||||||
|
])
|
||||||
class HTTPSensor(CBPiSensor):
|
class HTTPSensor(CBPiSensor):
|
||||||
def __init__(self, cbpi, id, props):
|
def __init__(self, cbpi, id, props):
|
||||||
super(HTTPSensor, self).__init__(cbpi, id, props)
|
super(HTTPSensor, self).__init__(cbpi, id, props)
|
||||||
|
@ -35,11 +37,12 @@ class HTTPSensor(CBPiSensor):
|
||||||
|
|
||||||
self.kettleid=self.props.get("Kettle", None)
|
self.kettleid=self.props.get("Kettle", None)
|
||||||
self.fermenterid=self.props.get("Fermenter", None)
|
self.fermenterid=self.props.get("Fermenter", None)
|
||||||
|
self.temprange=float(self.props.get("TempRange", 0))
|
||||||
self.reducedlogging = True if self.kettleid or self.fermenterid else False
|
self.reducedlogging = True if self.kettleid or self.fermenterid else False
|
||||||
|
|
||||||
if self.kettleid is not None and self.fermenterid is not None:
|
if self.kettleid is not None and self.fermenterid is not None:
|
||||||
self.reducedlogging=False
|
self.reducedlogging=False
|
||||||
self.cbpi.notify("HTTPSensor", "Sensor '" + str(self.sensor.name) + "' cant't have Fermenter and Kettle defined for reduced logging.", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
self.cbpi.notify("HTTPSensor", "Sensor '" + str(self.sensor.name) + "' cant't have Fermenter and Kettle defined for reduced logging / range warning", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
||||||
|
|
||||||
async def Confirm(self, **kwargs):
|
async def Confirm(self, **kwargs):
|
||||||
self.nextchecktime = time.time() + self.timeout
|
self.nextchecktime = time.time() + self.timeout
|
||||||
|
|
|
@ -11,11 +11,13 @@ from datetime import datetime
|
||||||
@parameters([Property.Text(label="Topic", configurable=True, description="MQTT Topic"),
|
@parameters([Property.Text(label="Topic", configurable=True, description="MQTT Topic"),
|
||||||
Property.Text(label="PayloadDictionary", configurable=True, default_value="",
|
Property.Text(label="PayloadDictionary", configurable=True, default_value="",
|
||||||
description="Where to find msg in payload, leave blank for raw payload"),
|
description="Where to find msg in payload, leave blank for raw payload"),
|
||||||
Property.Kettle(label="Kettle", description="Reduced logging if Kettle is inactive (only Kettle or Fermenter to be selected)"),
|
Property.Kettle(label="Kettle", description="Reduced logging if Kettle is inactive / range warning in dashboard (only Kettle or Fermenter to be selected)"),
|
||||||
Property.Fermenter(label="Fermenter", description="Reduced logging in seconds if Fermenter is inactive (only Kettle or Fermenter to be selected)"),
|
Property.Fermenter(label="Fermenter", description="Reduced logging if Fermenter is inactive / range warning in dashboard (only Kettle or Fermenter to be selected)"),
|
||||||
Property.Number(label="ReducedLogging", configurable=True, description="Reduced logging frequency in seconds if selected Kettle or Fermenter is inactive (default:60 sec | 0 disabled)"),
|
Property.Number(label="ReducedLogging", configurable=True, description="Reduced logging frequency in seconds if selected Kettle or Fermenter is inactive (default:60 sec | 0 disabled)"),
|
||||||
Property.Number(label="Timeout", configurable=True, unit="sec",
|
Property.Number(label="Timeout", configurable=True, unit="sec",
|
||||||
description="Timeout in seconds to send notification (default:60 | deactivated: 0)")])
|
description="Timeout in seconds to send notification (default:60 | deactivated: 0)"),
|
||||||
|
Property.Number(label="TempRange", configurable=True, unit="degree",
|
||||||
|
description="Temp range in degree between reading and target temp of fermenter/kettle. Larger difference shows different color in dashboard (default:0 | deactivated: 0)")])
|
||||||
class MQTTSensor(CBPiSensor):
|
class MQTTSensor(CBPiSensor):
|
||||||
|
|
||||||
def __init__(self, cbpi, id, props):
|
def __init__(self, cbpi, id, props):
|
||||||
|
@ -27,6 +29,7 @@ class MQTTSensor(CBPiSensor):
|
||||||
self.subscribed = self.cbpi.satellite.subscribe(self.Topic, self.on_message)
|
self.subscribed = self.cbpi.satellite.subscribe(self.Topic, self.on_message)
|
||||||
self.value: float = 999
|
self.value: float = 999
|
||||||
self.timeout=int(self.props.get("Timeout", 60))
|
self.timeout=int(self.props.get("Timeout", 60))
|
||||||
|
self.temprange=float(self.props.get("TempRange", 0))
|
||||||
self.starttime = time.time()
|
self.starttime = time.time()
|
||||||
self.notificationsend = False
|
self.notificationsend = False
|
||||||
self.nextchecktime=self.starttime+self.timeout
|
self.nextchecktime=self.starttime+self.timeout
|
||||||
|
@ -42,7 +45,7 @@ class MQTTSensor(CBPiSensor):
|
||||||
|
|
||||||
if self.kettleid is not None and self.fermenterid is not None:
|
if self.kettleid is not None and self.fermenterid is not None:
|
||||||
self.reducedlogging=False
|
self.reducedlogging=False
|
||||||
self.cbpi.notify("MQTTSensor", "Sensor '" + str(self.sensor.name) + "' cant't have Fermenter and Kettle defined for reduced logging.", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
self.cbpi.notify("MQTTSensor", "Sensor '" + str(self.sensor.name) + "' cant't have Fermenter and Kettle defined for reduced logging / range warning.", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
||||||
|
|
||||||
async def Confirm(self, **kwargs):
|
async def Confirm(self, **kwargs):
|
||||||
self.nextchecktime = time.time() + self.timeout
|
self.nextchecktime = time.time() + self.timeout
|
||||||
|
|
|
@ -52,10 +52,13 @@ class ReadThread (threading.Thread):
|
||||||
@parameters([Property.Select(label="Sensor", options=getSensors()),
|
@parameters([Property.Select(label="Sensor", options=getSensors()),
|
||||||
Property.Number(label="offset",configurable = True, default_value = 0, description="Sensor Offset (Default is 0)"),
|
Property.Number(label="offset",configurable = True, default_value = 0, description="Sensor Offset (Default is 0)"),
|
||||||
Property.Select(label="Interval", options=[1,5,10,30,60], description="Interval in Seconds"),
|
Property.Select(label="Interval", options=[1,5,10,30,60], description="Interval in Seconds"),
|
||||||
Property.Kettle(label="Kettle", description="Reduced logging if Kettle is inactive (only Kettle or Fermenter to be selected)"),
|
Property.Kettle(label="Kettle", description="Reduced logging if Kettle is inactive / range warning in dashboard(only Kettle or Fermenter to be selected)"),
|
||||||
Property.Fermenter(label="Fermenter", description="Reduced logging in seconds if Fermenter is inactive (only Kettle or Fermenter to be selected)"),
|
Property.Fermenter(label="Fermenter", description="Reduced logging in seconds if Fermenter is inactive / range warning in dashboard (only Kettle or Fermenter to be selected)"),
|
||||||
Property.Number(label="ReducedLogging", configurable=True, description="Reduced logging frequency in seconds if selected Kettle or Fermenter is inactive (default: 60 sec | disabled: 0)")
|
Property.Number(label="ReducedLogging", configurable=True, description="Reduced logging frequency in seconds if selected Kettle or Fermenter is inactive (default: 60 sec | disabled: 0)"),
|
||||||
])
|
Property.Number(label="TempRange", configurable=True, unit="degree",
|
||||||
|
description="Temp range in degree between reading and target temp of fermenter/kettle. Larger difference shows different color in dashboard (default:0 | deactivated: 0)")
|
||||||
|
])
|
||||||
|
|
||||||
class OneWire(CBPiSensor):
|
class OneWire(CBPiSensor):
|
||||||
|
|
||||||
def __init__(self, cbpi, id, props):
|
def __init__(self, cbpi, id, props):
|
||||||
|
@ -72,6 +75,7 @@ class OneWire(CBPiSensor):
|
||||||
if self.reducedfrequency < 0:
|
if self.reducedfrequency < 0:
|
||||||
self.reducedfrequency = 0
|
self.reducedfrequency = 0
|
||||||
self.lastlog=0
|
self.lastlog=0
|
||||||
|
self.temprange=float(self.props.get("TempRange", 0))
|
||||||
self.sensor=self.get_sensor(self.id)
|
self.sensor=self.get_sensor(self.id)
|
||||||
self.kettleid=self.props.get("Kettle", None)
|
self.kettleid=self.props.get("Kettle", None)
|
||||||
self.fermenterid=self.props.get("Fermenter", None)
|
self.fermenterid=self.props.get("Fermenter", None)
|
||||||
|
@ -79,7 +83,7 @@ class OneWire(CBPiSensor):
|
||||||
|
|
||||||
if self.kettleid is not None and self.fermenterid is not None:
|
if self.kettleid is not None and self.fermenterid is not None:
|
||||||
self.reducedlogging=False
|
self.reducedlogging=False
|
||||||
self.cbpi.notify("OneWire Sensor", "Sensor '" + str(self.sensor.name) + "' cant't have Fermenter and Kettle defined for reduced logging.", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
self.cbpi.notify("OneWire Sensor", "Sensor '" + str(self.sensor.name) + "' cant't have Fermenter and Kettle defined for reduced logging / range warning.", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
||||||
if (self.reducedfrequency != 0) and (self.interval >= self.reducedfrequency):
|
if (self.reducedfrequency != 0) and (self.interval >= self.reducedfrequency):
|
||||||
self.reducedlogging=False
|
self.reducedlogging=False
|
||||||
self.cbpi.notify("OneWire Sensor", "Sensor '" + str(self.sensor.name) + "' has shorter or equal 'reduced logging' compared to regular interval.", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
self.cbpi.notify("OneWire Sensor", "Sensor '" + str(self.sensor.name) + "' has shorter or equal 'reduced logging' compared to regular interval.", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
name: DummySensor
|
name: OneWireSensor
|
||||||
version: 4
|
version: 4
|
||||||
active: true
|
active: true
|
|
@ -37,4 +37,18 @@ class NotificationHttpEndpoints:
|
||||||
action_id = request.match_info['action_id']
|
action_id = request.match_info['action_id']
|
||||||
#print(notification_id, action_id)
|
#print(notification_id, action_id)
|
||||||
self.cbpi.notification.notify_callback(notification_id, action_id)
|
self.cbpi.notification.notify_callback(notification_id, action_id)
|
||||||
return web.Response(status=204)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
@request_mapping("/delete", method="POST", auth_required=False)
|
||||||
|
async def restart(self, request):
|
||||||
|
"""
|
||||||
|
---
|
||||||
|
description: DeleteNotifications
|
||||||
|
tags:
|
||||||
|
- Notification
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: successful operation
|
||||||
|
"""
|
||||||
|
self.cbpi.notification.delete_all_notifications()
|
||||||
|
return web.Response(status=200)
|
|
@ -42,6 +42,7 @@ class SystemHttpEndpoints:
|
||||||
step=self.cbpi.step.get_state(),
|
step=self.cbpi.step.get_state(),
|
||||||
fermentersteps=self.cbpi.fermenter.get_fermenter_steps(),
|
fermentersteps=self.cbpi.fermenter.get_fermenter_steps(),
|
||||||
config=self.cbpi.config.get_state(),
|
config=self.cbpi.config.get_state(),
|
||||||
|
notifications=self.cbpi.notification.get_state(),
|
||||||
version=__version__,
|
version=__version__,
|
||||||
guiversion=version,
|
guiversion=version,
|
||||||
codename=__codename__)
|
codename=__codename__)
|
||||||
|
@ -140,8 +141,8 @@ class SystemHttpEndpoints:
|
||||||
content: # Response body
|
content: # Response body
|
||||||
application/zip: # Media type
|
application/zip: # Media type
|
||||||
"""
|
"""
|
||||||
await self.controller.backupConfig()
|
filename = await self.controller.backupConfig()
|
||||||
filename = "cbpi4_config.zip"
|
#filename = "cbpi4_config.zip"
|
||||||
file_name = pathlib.Path(os.path.join(".", filename))
|
file_name = pathlib.Path(os.path.join(".", filename))
|
||||||
|
|
||||||
response = web.StreamResponse(
|
response = web.StreamResponse(
|
||||||
|
@ -165,31 +166,49 @@ class SystemHttpEndpoints:
|
||||||
description: Zip and download craftbeerpi.service log
|
description: Zip and download craftbeerpi.service log
|
||||||
tags:
|
tags:
|
||||||
- System
|
- System
|
||||||
|
parameters:
|
||||||
|
- name: "logtime"
|
||||||
|
in: "path"
|
||||||
|
description: "Logtime in hours"
|
||||||
|
required: true
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: successful operation
|
description: successful operation
|
||||||
content: # Response body
|
content: # Response body
|
||||||
application/zip: # Media type
|
application/zip: # Media type
|
||||||
"""
|
"""
|
||||||
|
checklogtime = False
|
||||||
logtime = request.match_info['logtime']
|
logtime = request.match_info['logtime']
|
||||||
await self.controller.downloadlog(logtime)
|
|
||||||
filename = "cbpi4_log.zip"
|
|
||||||
file_name = pathlib.Path(os.path.join(".", filename))
|
|
||||||
|
|
||||||
response = web.StreamResponse(
|
try:
|
||||||
status=200,
|
test=int(logtime)
|
||||||
reason='OK',
|
checklogtime = True
|
||||||
headers={'Content-Type': 'application/zip'},
|
except:
|
||||||
)
|
if logtime == "b":
|
||||||
await response.prepare(request)
|
checklogtime = True
|
||||||
with open(file_name, 'rb') as file:
|
|
||||||
for line in file.readlines():
|
|
||||||
await response.write(line)
|
|
||||||
|
|
||||||
await response.write_eof()
|
if checklogtime:
|
||||||
os.remove(file_name)
|
await self.controller.downloadlog(logtime)
|
||||||
return response
|
filename = "cbpi4_log.zip"
|
||||||
|
file_name = pathlib.Path(os.path.join(".", filename))
|
||||||
|
|
||||||
|
response = web.StreamResponse(
|
||||||
|
status=200,
|
||||||
|
reason='OK',
|
||||||
|
headers={'Content-Type': 'application/zip'},
|
||||||
|
)
|
||||||
|
await response.prepare(request)
|
||||||
|
with open(file_name, 'rb') as file:
|
||||||
|
for line in file.readlines():
|
||||||
|
await response.write(line)
|
||||||
|
|
||||||
|
await response.write_eof()
|
||||||
|
os.remove(file_name)
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
return web.Response(status=400, text='Need integer or "b" for logtime.')
|
||||||
|
|
||||||
@request_mapping("/restore", method="POST", name="RestoreConfig", auth_required=False)
|
@request_mapping("/restore", method="POST", name="RestoreConfig", auth_required=False)
|
||||||
async def restore(self, request):
|
async def restore(self, request):
|
||||||
|
|
|
@ -10,7 +10,7 @@ aiojobs==1.2.1
|
||||||
aiosqlite==0.17.0
|
aiosqlite==0.17.0
|
||||||
cryptography==42.0.5
|
cryptography==42.0.5
|
||||||
pyopenssl==24.1.0
|
pyopenssl==24.1.0
|
||||||
requests==2.31.0
|
requests==2.32.2
|
||||||
voluptuous==0.14.2
|
voluptuous==0.14.2
|
||||||
pyfiglet==1.0.2
|
pyfiglet==1.0.2
|
||||||
pandas==2.2.2
|
pandas==2.2.2
|
||||||
|
@ -20,10 +20,11 @@ numpy==1.26.4
|
||||||
cbpi4gui
|
cbpi4gui
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
importlib_metadata==4.11.1
|
importlib_metadata==4.11.1
|
||||||
aiomqtt==2.0.1
|
aiomqtt==2.1.0
|
||||||
psutil==5.9.8
|
psutil==5.9.8
|
||||||
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.2.4
|
inquirer==3.2.4
|
||||||
|
systemd-python
|
||||||
|
|
7
setup.py
7
setup.py
|
@ -50,13 +50,13 @@ setup(name='cbpi4',
|
||||||
"aiosqlite==0.17.0",
|
"aiosqlite==0.17.0",
|
||||||
"cryptography==42.0.5",
|
"cryptography==42.0.5",
|
||||||
"pyopenssl==24.1.0",
|
"pyopenssl==24.1.0",
|
||||||
"requests==2.31.0",
|
"requests==2.32.2",
|
||||||
"voluptuous==0.14.2",
|
"voluptuous==0.14.2",
|
||||||
"pyfiglet==1.0.2",
|
"pyfiglet==1.0.2",
|
||||||
'click==8.1.7',
|
'click==8.1.7',
|
||||||
'shortuuid==1.0.13',
|
'shortuuid==1.0.13',
|
||||||
'tabulate==0.9.0',
|
'tabulate==0.9.0',
|
||||||
'aiomqtt==2.0.1',
|
'aiomqtt==2.1.0',
|
||||||
'inquirer==3.2.4',
|
'inquirer==3.2.4',
|
||||||
'colorama==0.4.6',
|
'colorama==0.4.6',
|
||||||
'psutil==5.9.8',
|
'psutil==5.9.8',
|
||||||
|
@ -64,7 +64,8 @@ setup(name='cbpi4',
|
||||||
'importlib_metadata',
|
'importlib_metadata',
|
||||||
'numpy==1.26.4',
|
'numpy==1.26.4',
|
||||||
'pandas==2.2.2'] + (
|
'pandas==2.2.2'] + (
|
||||||
['rpi-lgpio'] if raspberrypi else [] ),
|
['rpi-lgpio'] if raspberrypi else [] ) + (
|
||||||
|
['systemd-python'] if localsystem == "Linux" else [] ),
|
||||||
|
|
||||||
dependency_links=[
|
dependency_links=[
|
||||||
'https://testpypi.python.org/pypi',
|
'https://testpypi.python.org/pypi',
|
||||||
|
|
Loading…
Reference in a new issue