mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-21 22:48:16 +01:00
Merge pull request #129 from PiBrewing/development
Merge from Development
This commit is contained in:
commit
1f645a835c
12 changed files with 134 additions and 31 deletions
|
@ -1,3 +1,3 @@
|
|||
__version__ = "4.3.1"
|
||||
__version__ = "4.3.2"
|
||||
__codename__ = "Winter Storm"
|
||||
|
||||
|
|
|
@ -107,6 +107,7 @@ class ConfigFolder:
|
|||
for checking in required_config_content:
|
||||
if self.inform_missing_content(self.check_for_file_or_folder(os.path.join(self.configFolderPath, checking[0]), checking[1])):
|
||||
# since there is no complete config we now check if the config folder may be completely empty to show hints:
|
||||
try:
|
||||
if len(os.listdir(os.path.join(self.configFolderPath))) == 0 :
|
||||
print("***************************************************")
|
||||
print(f"the config folder '{self.configFolderPath}' seems to be completely empty")
|
||||
|
@ -117,6 +118,13 @@ class ConfigFolder:
|
|||
print("of course you can also place your config files manually")
|
||||
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)
|
||||
# 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.
|
||||
# Therefore, the service file is replaced with a template file in the config folder
|
||||
if whatsmissing.find("craftbeerpi.template"):
|
||||
try:
|
||||
self.copyDefaultFileIfNotExists("craftbeerpi.template")
|
||||
return False
|
||||
except:
|
||||
pass
|
||||
print("***************************************************")
|
||||
print(f"CraftBeerPi config content not found: {whatsmissing}")
|
||||
print("Please run 'cbpi setup' before starting the server ")
|
||||
|
|
|
@ -525,13 +525,19 @@ class FermentationController:
|
|||
except Exception as 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"):
|
||||
|
||||
if key == self.update_key:
|
||||
self.cbpi.ws.send(dict(topic=key, data=list(map(lambda item: item.to_dict(), 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
|
||||
else:
|
||||
fermentersteps=self.get_fermenter_steps()
|
||||
|
@ -539,9 +545,20 @@ class FermentationController:
|
|||
|
||||
# send mqtt update for active femrentersteps
|
||||
for fermenter in fermentersteps:
|
||||
active = False
|
||||
for step in fermenter['steps']:
|
||||
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:
|
||||
logging.info("FermenterStep Controller - call Action {} {}".format(id, action))
|
||||
|
|
|
@ -35,6 +35,11 @@ class SatelliteController:
|
|||
]
|
||||
self.tasks = set()
|
||||
|
||||
def remove_key(self,d, key):
|
||||
r = dict(d)
|
||||
del r[key]
|
||||
return r
|
||||
|
||||
async def init(self):
|
||||
|
||||
#not sure if required like done in the old routine
|
||||
|
@ -132,7 +137,8 @@ class SatelliteController:
|
|||
try:
|
||||
self.fermenter=self.fermentercontroller.get_state()
|
||||
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:
|
||||
self.logger.warning("Failed to send fermenterupdate via mqtt: {}".format(e))
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from os import listdir
|
|||
import os
|
||||
from os.path import isfile, join
|
||||
import shortuuid
|
||||
from cbpi.api.dataclasses import NotificationAction, Props, Step
|
||||
from cbpi.api.dataclasses import NotificationAction, NotificationType, Props, Step
|
||||
from tabulate import tabulate
|
||||
|
||||
from ..api.step import StepMove, StepResult, StepState
|
||||
|
@ -156,14 +156,14 @@ class StepController:
|
|||
logging.info("BREWING COMPLETE")
|
||||
|
||||
async def previous(self):
|
||||
logging.info("Trigger Next")
|
||||
logging.info("Trigger Previous")
|
||||
|
||||
|
||||
async def next(self):
|
||||
logging.info("Trigger Next")
|
||||
print("\n\n\n\n")
|
||||
print(self.profile)
|
||||
print("\n\n\n\n")
|
||||
#print("\n\n\n\n")
|
||||
#print(self.profile)
|
||||
#print("\n\n\n\n")
|
||||
step = self.find_by_status(StepState.ACTIVE)
|
||||
if step is not None:
|
||||
if step.instance is not None:
|
||||
|
@ -299,6 +299,7 @@ class StepController:
|
|||
await step.instance.start()
|
||||
step.status = StepState.ACTIVE
|
||||
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)
|
||||
|
||||
async def save_basic(self, data):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import pkgutil
|
||||
import psutil
|
||||
import pathlib
|
||||
import json
|
||||
|
@ -12,6 +13,8 @@ from cbpi.api.config import ConfigType
|
|||
from cbpi.api import *
|
||||
import zipfile
|
||||
import socket
|
||||
import importlib
|
||||
from tabulate import tabulate
|
||||
|
||||
class SystemController:
|
||||
|
||||
|
@ -41,6 +44,26 @@ class SystemController:
|
|||
dir_name = pathlib.Path(self.cbpi.config_folder.get_file_path(''))
|
||||
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):
|
||||
filename = "cbpi4.log"
|
||||
fullname = pathlib.Path(os.path.join(".",filename))
|
||||
|
@ -60,7 +83,12 @@ class SystemController:
|
|||
else:
|
||||
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:
|
||||
actors = self.cbpi.actor.get_state()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
import asyncio
|
||||
import sys
|
||||
import socket
|
||||
try:
|
||||
from asyncio import set_event_loop_policy, WindowsSelectorEventLoopPolicy
|
||||
except ImportError:
|
||||
|
@ -277,6 +278,22 @@ class CraftBeerPi:
|
|||
self.app.add_routes([web.get('/', http_index),
|
||||
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):
|
||||
|
||||
self._print_logo()
|
||||
|
@ -304,4 +321,9 @@ class CraftBeerPi:
|
|||
return self.app
|
||||
|
||||
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)
|
|
@ -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="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="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.Sensor(label="sensor2",description="Optional Sensor for LCDisplay(e.g. iSpindle)")])
|
||||
|
||||
|
@ -57,6 +59,8 @@ class FermenterHysteresis(CBPiFermenterLogic):
|
|||
self.heater_offset_max = float(self.props.get("HeaterOffsetOff", 0))
|
||||
self.cooler_offset_min = float(self.props.get("CoolerOffsetOn", 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.heater = self.fermenter.heater
|
||||
|
@ -81,7 +85,7 @@ class FermenterHysteresis(CBPiFermenterLogic):
|
|||
|
||||
if sensor_value + self.heater_offset_min <= target_temp:
|
||||
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 self.heater and (heater_state == True):
|
||||
|
@ -89,7 +93,7 @@ class FermenterHysteresis(CBPiFermenterLogic):
|
|||
|
||||
if sensor_value >= self.cooler_offset_min + target_temp:
|
||||
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 self.cooler and (cooler_state == True):
|
||||
|
|
|
@ -37,6 +37,11 @@ class MQTTUtil(CBPiExtension):
|
|||
self.push_update()
|
||||
await asyncio.sleep(self.mqttupdate)
|
||||
|
||||
def remove_key(self,d, key):
|
||||
r = dict(d)
|
||||
del r[key]
|
||||
return r
|
||||
|
||||
def push_update(self):
|
||||
# try:
|
||||
# self.actor=self.actorcontroller.get_state()
|
||||
|
@ -62,7 +67,8 @@ class MQTTUtil(CBPiExtension):
|
|||
try:
|
||||
self.fermenter=self.fermentationcontroller.get_state()
|
||||
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:
|
||||
logging.error(e)
|
||||
pass
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
typing-extensions>=4
|
||||
aiohttp==3.9.1
|
||||
aiohttp==3.9.3
|
||||
aiohttp-auth==0.1.1
|
||||
aiohttp-route-decorator==0.1.4
|
||||
aiohttp-security==0.5.0
|
||||
|
|
2
setup.py
2
setup.py
|
@ -39,7 +39,7 @@ setup(name='cbpi4',
|
|||
long_description_content_type='text/markdown',
|
||||
install_requires=[
|
||||
"typing-extensions>=4",
|
||||
"aiohttp==3.9.1",
|
||||
"aiohttp==3.9.3",
|
||||
"aiohttp-auth==0.1.1",
|
||||
"aiohttp-route-decorator==0.1.4",
|
||||
"aiohttp-security==0.5.0",
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
"options": null,
|
||||
"source": "hidden",
|
||||
"type": "string",
|
||||
"value": "4.2.0.a6"
|
||||
"value": "4.3.2.a6"
|
||||
},
|
||||
"CSVLOGFILES": {
|
||||
"description": "Write sensor data to csv logfiles (enabling requires restart)",
|
||||
|
@ -326,6 +326,14 @@
|
|||
"type": "number",
|
||||
"value": 1
|
||||
},
|
||||
"current_grid": {
|
||||
"description": "Dashboard Grid Width",
|
||||
"name": "current_grid",
|
||||
"options": null,
|
||||
"source": "hidden",
|
||||
"type": "number",
|
||||
"value": 5
|
||||
},
|
||||
"max_dashboard_number": {
|
||||
"description": "Max Number of Dashboards",
|
||||
"name": "max_dashboard_number",
|
||||
|
|
Loading…
Reference in a new issue