Merge pull request #129 from PiBrewing/development

Merge from Development
This commit is contained in:
Alexander Vollkopf 2024-02-23 15:36:41 +01:00 committed by GitHub
commit 1f645a835c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 134 additions and 31 deletions

View file

@ -1,3 +1,3 @@
__version__ = "4.3.1" __version__ = "4.3.2"
__codename__ = "Winter Storm" __codename__ = "Winter Storm"

View file

@ -107,16 +107,24 @@ 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 folder may 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 : try:
print("***************************************************") if len(os.listdir(os.path.join(self.configFolderPath))) == 0 :
print(f"the config folder '{self.configFolderPath}' seems to be completely empty") print("***************************************************")
print("you might want to run 'cbpi setup'.print") print(f"the config folder '{self.configFolderPath}' seems to be completely empty")
print("but you could also place your zipped config backup named") print("you might want to run 'cbpi setup'.print")
print("'restored_config.zip' inside the mentioned config folder for") print("but you could also place your zipped config backup named")
print("cbpi4 to automatically unpack it") print("'restored_config.zip' inside the mentioned config folder for")
print("of course you can also place your config files manually") print("cbpi4 to automatically unpack it")
print("***************************************************") print("of course you can also place your config files manually")
return False print("***************************************************")
return False
except:
print("***************************************************")
print("Cannot find config folder!")
print("Please navigate to path where you did run 'cbpi setup'.")
print("Or run 'cbpi setup' before starting the server.")
print("***************************************************")
return False
# if cbpi_dashboard_1.json does'nt 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.
@ -145,8 +153,11 @@ class ConfigFolder:
# Starting with cbpi 4.2.0, the craftbeerpi.service file will be created dynamically from the template file based on the user id. # 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 # Therefore, the service file is replaced with a template file in the config folder
if whatsmissing.find("craftbeerpi.template"): if whatsmissing.find("craftbeerpi.template"):
self.copyDefaultFileIfNotExists("craftbeerpi.template") try:
return False self.copyDefaultFileIfNotExists("craftbeerpi.template")
return False
except:
pass
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 ")

View file

@ -524,6 +524,11 @@ class FermentationController:
except Exception as e: except Exception as e:
self.logger.error(e) self.logger.error(e)
def remove_key(self,d, key):
r = dict(d)
del r[key]
return r
def push_update(self, key="fermenterupdate"): def push_update(self, key="fermenterupdate"):
@ -531,7 +536,8 @@ class FermentationController:
self.cbpi.ws.send(dict(topic=key, data=list(map(lambda item: item.to_dict(), self.data)))) self.cbpi.ws.send(dict(topic=key, data=list(map(lambda item: item.to_dict(), self.data))))
for item in self.data: for item in self.data:
self.cbpi.push_update("cbpi/{}/{}".format(self.update_key,item.id), item.to_dict()) fermenters=self.remove_key(item.to_dict(),"steps")
self.cbpi.push_update("cbpi/{}/{}".format(self.update_key,item.id), fermenters)
pass pass
else: else:
fermentersteps=self.get_fermenter_steps() fermentersteps=self.get_fermenter_steps()
@ -539,9 +545,20 @@ class FermentationController:
# send mqtt update for active femrentersteps # send mqtt update for active femrentersteps
for fermenter in fermentersteps: for fermenter in fermentersteps:
active = False
for step in fermenter['steps']: for step in fermenter['steps']:
if step['status'] == 'A': if step['status'] == 'A':
self.cbpi.push_update("cbpi/{}/{}/{}".format(key,fermenter['id'],step['id']), step) active=True
active_step=step
# self.cbpi.push_update("cbpi/{}/{}/{}".format(key,fermenter['id'],step['id']), step)
#else:
# self.cbpi.push_update("cbpi/{}/{}".format(key,fermenter['id']), "")
if active:
self.cbpi.push_update("cbpi/{}/{}".format(key,fermenter['id']), active_step)
else:
self.cbpi.push_update("cbpi/{}/{}".format(key,fermenter['id']), "")
async def call_action(self, id, action, parameter) -> None: async def call_action(self, id, action, parameter) -> None:
logging.info("FermenterStep Controller - call Action {} {}".format(id, action)) logging.info("FermenterStep Controller - call Action {} {}".format(id, action))

View file

@ -34,6 +34,11 @@ class SatelliteController:
] ]
self.tasks = set() self.tasks = set()
def remove_key(self,d, key):
r = dict(d)
del r[key]
return r
async def init(self): async def init(self):
@ -132,7 +137,8 @@ class SatelliteController:
try: try:
self.fermenter=self.fermentercontroller.get_state() self.fermenter=self.fermentercontroller.get_state()
for item in self.fermenter['data']: for item in self.fermenter['data']:
self.cbpi.push_update("cbpi/{}/{}".format("fermenterupdate",item['id']), item) item_new=self.remove_key(item,"steps")
self.cbpi.push_update("cbpi/{}/{}".format("fermenterupdate",item['id']), item_new)
except Exception as e: except Exception as e:
self.logger.warning("Failed to send fermenterupdate via mqtt: {}".format(e)) self.logger.warning("Failed to send fermenterupdate via mqtt: {}".format(e))

View file

@ -9,7 +9,7 @@ from os import listdir
import os import os
from os.path import isfile, join from os.path import isfile, join
import shortuuid import shortuuid
from cbpi.api.dataclasses import NotificationAction, Props, Step from cbpi.api.dataclasses import NotificationAction, NotificationType, Props, Step
from tabulate import tabulate from tabulate import tabulate
from ..api.step import StepMove, StepResult, StepState from ..api.step import StepMove, StepResult, StepState
@ -156,14 +156,14 @@ class StepController:
logging.info("BREWING COMPLETE") logging.info("BREWING COMPLETE")
async def previous(self): async def previous(self):
logging.info("Trigger Next") logging.info("Trigger Previous")
async def next(self): async def next(self):
logging.info("Trigger Next") logging.info("Trigger Next")
print("\n\n\n\n") #print("\n\n\n\n")
print(self.profile) #print(self.profile)
print("\n\n\n\n") #print("\n\n\n\n")
step = self.find_by_status(StepState.ACTIVE) step = self.find_by_status(StepState.ACTIVE)
if step is not None: if step is not None:
if step.instance is not None: if step.instance is not None:
@ -299,6 +299,7 @@ class StepController:
await step.instance.start() await step.instance.start()
step.status = StepState.ACTIVE step.status = StepState.ACTIVE
except Exception as e: except Exception as e:
self.cbpi.notify("Error", "Can't start step. Please check step in Mash Profile", NotificationType.ERROR)
logging.error("Failed to start step %s" % step) logging.error("Failed to start step %s" % step)
async def save_basic(self, data): async def save_basic(self, data):

View file

@ -1,6 +1,7 @@
import logging import logging
import os import os
import shutil import shutil
import pkgutil
import psutil import psutil
import pathlib import pathlib
import json import json
@ -12,6 +13,8 @@ from cbpi.api.config import ConfigType
from cbpi.api import * from cbpi.api import *
import zipfile import zipfile
import socket import socket
import importlib
from tabulate import tabulate
class SystemController: class SystemController:
@ -41,6 +44,26 @@ class SystemController:
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)
async def plugins_list(self):
result = []
discovered_plugins = {
name: importlib.import_module(name)
for finder, name, ispkg
in pkgutil.iter_modules()
if name.startswith('cbpi') and len(name) > 4
}
for key, module in discovered_plugins.items():
from importlib.metadata import version
try:
from importlib.metadata import (distribution, metadata,
version)
meta = metadata(key)
result.append(dict(Name=meta["Name"], Version=meta["Version"], Author=meta["Author"], Homepage=meta["Home-page"], Summary=meta["Summary"]))
except Exception as e:
print(e)
return tabulate(result, headers="keys")
async def downloadlog(self, logtime): async def downloadlog(self, logtime):
filename = "cbpi4.log" filename = "cbpi4.log"
fullname = pathlib.Path(os.path.join(".",filename)) fullname = pathlib.Path(os.path.join(".",filename))
@ -60,7 +83,12 @@ class SystemController:
else: else:
os.system('journalctl --since \"{} hours ago\" -u craftbeerpi.service --output cat > {}'.format(logtime, fullname)) os.system('journalctl --since \"{} hours ago\" -u craftbeerpi.service --output cat > {}'.format(logtime, fullname))
os.system('cbpi plugins > {}'.format(fullpluginname)) plugins = await self.plugins_list()
with open(fullpluginname, 'w') as f:
f.write(plugins)
#os.system('echo "{}" >> {}'.format(plugins,fullpluginname))
try: try:
actors = self.cbpi.actor.get_state() actors = self.cbpi.actor.get_state()

View file

@ -1,6 +1,7 @@
import asyncio import asyncio
import sys import sys
import socket
try: try:
from asyncio import set_event_loop_policy, WindowsSelectorEventLoopPolicy from asyncio import set_event_loop_policy, WindowsSelectorEventLoopPolicy
except ImportError: except ImportError:
@ -276,6 +277,22 @@ class CraftBeerPi:
self.app.add_routes([web.get('/', http_index), self.app.add_routes([web.get('/', http_index),
web.static('/static', os.path.join(os.path.dirname(__file__), "static"), show_index=True)]) web.static('/static', os.path.join(os.path.dirname(__file__), "static"), show_index=True)])
def testport(self, port=8000):
HOST = "localhost"
# Creates a new socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Try to connect to the given host and port
if sock.connect_ex((HOST, port)) == 0:
#print("Port " + str(port) + " is open") # Connected successfully
isrunning = True
else:
#print("Port " + str(port) + " is closed") # Failed to connect because port is in use (or bad host)
isrunning = False
# Close the connection
sock.close()
return isrunning
async def init_serivces(self): async def init_serivces(self):
@ -304,4 +321,9 @@ class CraftBeerPi:
return self.app return self.app
def start(self): def start(self):
web.run_app(self.init_serivces(), port=self.static_config.get("port", 2202)) port=self.static_config.get("port",8000)
if not self.testport(port):
web.run_app(self.init_serivces(), port=port)
else:
logging.error("Port {} is already in use! Please check, if server is already running (e.g. in automode)".format(port))
exit(1)

View file

@ -46,6 +46,8 @@ class FermenterAutostart(CBPiExtension):
Property.Number(label="HeaterOffsetOff", configurable=True, description="Offset as decimal number when the heater is switched off. Should be smaller then 'HeaterOffsetOn'. For example a value of 1 switches off the heater if the current temperature is 1 degree below the target temperature"), Property.Number(label="HeaterOffsetOff", configurable=True, description="Offset as decimal number when the heater is switched off. Should be smaller then 'HeaterOffsetOn'. For example a value of 1 switches off the heater if the current temperature is 1 degree below the target temperature"),
Property.Number(label="CoolerOffsetOn", configurable=True, description="Offset as decimal number when the cooler is switched on. Should be greater then 'CoolerOffsetOff'. For example a value of 2 switches on the cooler if the current temperature is 2 degrees above the target temperature"), Property.Number(label="CoolerOffsetOn", configurable=True, description="Offset as decimal number when the cooler is switched on. Should be greater then 'CoolerOffsetOff'. For example a value of 2 switches on the cooler if the current temperature is 2 degrees above the target temperature"),
Property.Number(label="CoolerOffsetOff", configurable=True, description="Offset as decimal number when the cooler is switched off. Should be smaller then 'CoolerOffsetOn'. For example a value of 1 switches off the cooler if the current temperature is 1 degree above the target temperature"), Property.Number(label="CoolerOffsetOff", configurable=True, description="Offset as decimal number when the cooler is switched off. Should be smaller then 'CoolerOffsetOn'. For example a value of 1 switches off the cooler if the current temperature is 1 degree above the target temperature"),
Property.Number(label="HeaterMaxPower", configurable=True,description="Max Power [%] for Heater (default: 100)"),
Property.Number(label="CoolerMaxPower", configurable=True ,description="Max Power [%] for Cooler (default: 100)"),
Property.Select(label="AutoStart", options=["Yes","No"],description="Autostart Fermenter on cbpi start"), Property.Select(label="AutoStart", options=["Yes","No"],description="Autostart Fermenter on cbpi start"),
Property.Sensor(label="sensor2",description="Optional Sensor for LCDisplay(e.g. iSpindle)")]) Property.Sensor(label="sensor2",description="Optional Sensor for LCDisplay(e.g. iSpindle)")])
@ -57,7 +59,9 @@ class FermenterHysteresis(CBPiFermenterLogic):
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))
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.cooler_max_power = int(self.props.get("CoolerMaxPower", 100))
self.fermenter = self.get_fermenter(self.id) self.fermenter = self.get_fermenter(self.id)
self.heater = self.fermenter.heater self.heater = self.fermenter.heater
self.cooler = self.fermenter.cooler self.cooler = self.fermenter.cooler
@ -81,7 +85,7 @@ class FermenterHysteresis(CBPiFermenterLogic):
if sensor_value + self.heater_offset_min <= target_temp: if sensor_value + self.heater_offset_min <= target_temp:
if self.heater and (heater_state == False): if self.heater and (heater_state == False):
await self.actor_on(self.heater) await self.actor_on(self.heater, self.heater_max_power)
if sensor_value + self.heater_offset_max >= target_temp: if sensor_value + self.heater_offset_max >= target_temp:
if self.heater and (heater_state == True): if self.heater and (heater_state == True):
@ -89,7 +93,7 @@ class FermenterHysteresis(CBPiFermenterLogic):
if sensor_value >= self.cooler_offset_min + target_temp: if sensor_value >= self.cooler_offset_min + target_temp:
if self.cooler and (cooler_state == False): if self.cooler and (cooler_state == False):
await self.actor_on(self.cooler) await self.actor_on(self.cooler, self.cooler_max_power)
if sensor_value <= self.cooler_offset_max + target_temp: if sensor_value <= self.cooler_offset_max + target_temp:
if self.cooler and (cooler_state == True): if self.cooler and (cooler_state == True):

View file

@ -37,6 +37,11 @@ class MQTTUtil(CBPiExtension):
self.push_update() self.push_update()
await asyncio.sleep(self.mqttupdate) await asyncio.sleep(self.mqttupdate)
def remove_key(self,d, key):
r = dict(d)
del r[key]
return r
def push_update(self): def push_update(self):
# try: # try:
# self.actor=self.actorcontroller.get_state() # self.actor=self.actorcontroller.get_state()
@ -61,8 +66,9 @@ class MQTTUtil(CBPiExtension):
pass pass
try: try:
self.fermenter=self.fermentationcontroller.get_state() self.fermenter=self.fermentationcontroller.get_state()
for item in self.fermenter['data']: for item in self.fermenter['data']:
self.cbpi.push_update("cbpi/{}/{}".format("fermenterupdate",item['id']), item) item_new=self.remove_key(item,"steps")
self.cbpi.push_update("cbpi/{}/{}".format("fermenterupdate",item['id']), item_new)
except Exception as e: except Exception as e:
logging.error(e) logging.error(e)
pass pass

View file

@ -1,5 +1,5 @@
typing-extensions>=4 typing-extensions>=4
aiohttp==3.9.1 aiohttp==3.9.3
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.5.0 aiohttp-security==0.5.0

View file

@ -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.9.1", "aiohttp==3.9.3",
"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.5.0", "aiohttp-security==0.5.0",

View file

@ -80,7 +80,7 @@
"options": null, "options": null,
"source": "hidden", "source": "hidden",
"type": "string", "type": "string",
"value": "4.2.0.a6" "value": "4.3.2.a6"
}, },
"CSVLOGFILES": { "CSVLOGFILES": {
"description": "Write sensor data to csv logfiles (enabling requires restart)", "description": "Write sensor data to csv logfiles (enabling requires restart)",
@ -326,6 +326,14 @@
"type": "number", "type": "number",
"value": 1 "value": 1
}, },
"current_grid": {
"description": "Dashboard Grid Width",
"name": "current_grid",
"options": null,
"source": "hidden",
"type": "number",
"value": 5
},
"max_dashboard_number": { "max_dashboard_number": {
"description": "Max Number of Dashboards", "description": "Max Number of Dashboards",
"name": "max_dashboard_number", "name": "max_dashboard_number",