mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-12-22 13:34:55 +01:00
Merge pull request #86 from avollkopf/development
Merge from Development Branch
This commit is contained in:
commit
a819782e5a
39 changed files with 548 additions and 215 deletions
|
@ -1,4 +1,4 @@
|
||||||
FROM mcr.microsoft.com/vscode/devcontainers/python:3.9-bullseye
|
FROM mcr.microsoft.com/vscode/devcontainers/python:3.11-bullseye
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get upgrade -y
|
&& apt-get upgrade -y
|
||||||
|
|
|
@ -3,7 +3,7 @@ RUN apk --no-cache add curl && mkdir /downloads
|
||||||
# Download installation files
|
# Download installation files
|
||||||
RUN curl https://github.com/avollkopf/craftbeerpi4-ui/archive/main.zip -L -o ./downloads/cbpi-ui.zip
|
RUN curl https://github.com/avollkopf/craftbeerpi4-ui/archive/main.zip -L -o ./downloads/cbpi-ui.zip
|
||||||
|
|
||||||
FROM python:3.9 as base
|
FROM python:3.10 as base
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
__version__ = "4.0.7"
|
__version__ = "4.1.0"
|
||||||
__codename__ = "November Rain"
|
__codename__ = "Groundhog Day"
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ from colorama import Fore, Back, Style
|
||||||
import importlib
|
import importlib
|
||||||
from importlib_metadata import metadata
|
from importlib_metadata import metadata
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
from PyInquirer import prompt, print_json
|
from inquirer import prompt
|
||||||
import platform
|
import platform
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import glob
|
import glob
|
||||||
|
import json
|
||||||
|
|
||||||
class ConfigFolder:
|
class ConfigFolder:
|
||||||
def __init__(self, configFolderPath, logsFolderPath):
|
def __init__(self, configFolderPath, logsFolderPath):
|
||||||
|
@ -91,8 +91,8 @@ class ConfigFolder:
|
||||||
['actor.json', 'file'],
|
['actor.json', 'file'],
|
||||||
['sensor.json', 'file'],
|
['sensor.json', 'file'],
|
||||||
['kettle.json', 'file'],
|
['kettle.json', 'file'],
|
||||||
['fermenter_data.json', 'file'],
|
#['fermenter_data.json', 'file'], created by fermentation_controller @ start if not available
|
||||||
['step_data.json', 'file'],
|
#['step_data.json', 'file'], created by step_controller @ start if not available
|
||||||
['config.json', 'file'],
|
['config.json', 'file'],
|
||||||
['craftbeerpi.service', 'file'],
|
['craftbeerpi.service', 'file'],
|
||||||
['chromium.desktop', 'file'],
|
['chromium.desktop', 'file'],
|
||||||
|
@ -121,9 +121,23 @@ class ConfigFolder:
|
||||||
# if cbpi_dashboard_1.json doesnt exist at the new location (configFolderPath/dashboard)
|
# if cbpi_dashboard_1.json doesnt exist at the new location (configFolderPath/dashboard)
|
||||||
# we move every cbpi_dashboard_n.json file from the old location (configFolderPath) there.
|
# 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.
|
||||||
if not (os.path.isfile(os.path.join(self.configFolderPath, 'dashboard', 'cbpi_dashboard_1.json'))):
|
dashboard_1_path = os.path.join(self.configFolderPath, 'dashboard', 'cbpi_dashboard_1.json')
|
||||||
|
if (not (os.path.isfile(dashboard_1_path))) or self.check_for_empty_dashboard_1(dashboard_1_path):
|
||||||
for file in glob.glob(os.path.join(self.configFolderPath, 'cbpi_dashboard_*.json')):
|
for file in glob.glob(os.path.join(self.configFolderPath, 'cbpi_dashboard_*.json')):
|
||||||
shutil.move(file, os.path.join(self.configFolderPath, 'dashboard', os.path.basename(file)))
|
dashboardFile = os.path.basename(file)
|
||||||
|
print(f"Copy dashboard json file {dashboardFile} from config to config/dashboard folder")
|
||||||
|
shutil.move(file, os.path.join(self.configFolderPath, 'dashboard', dashboardFile))
|
||||||
|
|
||||||
|
def check_for_empty_dashboard_1(self, dashboard_1_path):
|
||||||
|
try:
|
||||||
|
with open(dashboard_1_path, 'r') as 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
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except: # file missing or bad json format
|
||||||
|
return True
|
||||||
|
|
||||||
def inform_missing_content(self, whatsmissing : str):
|
def inform_missing_content(self, whatsmissing : str):
|
||||||
if whatsmissing == "":
|
if whatsmissing == "":
|
||||||
|
|
|
@ -34,7 +34,7 @@ class DashboardController:
|
||||||
return {'elements': [], 'pathes': []}
|
return {'elements': [], 'pathes': []}
|
||||||
|
|
||||||
async def add_content(self, dashboard_id, data):
|
async def add_content(self, dashboard_id, data):
|
||||||
print(data)
|
#print(data)
|
||||||
self.path = self.cbpi.config_folder.get_dashboard_path("cbpi_dashboard_" + str(dashboard_id)+ ".json")
|
self.path = self.cbpi.config_folder.get_dashboard_path("cbpi_dashboard_" + str(dashboard_id)+ ".json")
|
||||||
with open(self.path, 'w') as outfile:
|
with open(self.path, 'w') as outfile:
|
||||||
json.dump(data, outfile, indent=4, sort_keys=True)
|
json.dump(data, outfile, indent=4, sort_keys=True)
|
||||||
|
|
|
@ -36,7 +36,7 @@ class FermentationController:
|
||||||
|
|
||||||
def check_fermenter_file(self):
|
def check_fermenter_file(self):
|
||||||
if os.path.exists(self.cbpi.config_folder.get_file_path("fermenter_data.json")) is False:
|
if os.path.exists(self.cbpi.config_folder.get_file_path("fermenter_data.json")) is False:
|
||||||
logging.info("INIT fermenter_data.json file")
|
logging.warning("Missing fermenter_data.json file. INIT empty file")
|
||||||
data = {
|
data = {
|
||||||
"data": [
|
"data": [
|
||||||
]
|
]
|
||||||
|
@ -71,11 +71,23 @@ class FermentationController:
|
||||||
|
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
|
try:
|
||||||
with open(self.path) as json_file:
|
with open(self.path) as json_file:
|
||||||
data = json.load(json_file)
|
data = json.load(json_file)
|
||||||
|
|
||||||
for i in data["data"]:
|
for i in data["data"]:
|
||||||
self.data.append(self._create(i))
|
self.data.append(self._create(i))
|
||||||
|
except:
|
||||||
|
logging.warning("Invalid fermenter_data.json file - Creating empty file")
|
||||||
|
os.remove(self.path)
|
||||||
|
data = {
|
||||||
|
"data": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
destfile = self.cbpi.config_folder.get_file_path("fermenter_data.json")
|
||||||
|
json.dump(data,open(destfile,'w'),indent=4, sort_keys=True)
|
||||||
|
for i in data["data"]:
|
||||||
|
self.data.append(self._create(i))
|
||||||
|
|
||||||
def _create_step(self, fermenter, item):
|
def _create_step(self, fermenter, item):
|
||||||
id = item.get("id")
|
id = item.get("id")
|
||||||
|
|
|
@ -53,6 +53,7 @@ class LogController:
|
||||||
self.influxdbname = self.cbpi.config.get("INFLUXDBNAME", None)
|
self.influxdbname = self.cbpi.config.get("INFLUXDBNAME", None)
|
||||||
self.influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None)
|
self.influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None)
|
||||||
self.influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None)
|
self.influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None)
|
||||||
|
self.influxdbmeasurement = self.cbpi.config.get("INFLUXDBMEASUREMENT", "measurement")
|
||||||
|
|
||||||
id = name
|
id = name
|
||||||
try:
|
try:
|
||||||
|
@ -62,7 +63,7 @@ class LogController:
|
||||||
itemname=sensor.name.replace(" ", "_")
|
itemname=sensor.name.replace(" ", "_")
|
||||||
for char in chars:
|
for char in chars:
|
||||||
itemname = itemname.replace(char,chars[char])
|
itemname = itemname.replace(char,chars[char])
|
||||||
out="measurement,source=" + itemname + ",itemID=" + str(id) + " value="+str(value)
|
out=str(self.influxdbmeasurement)+",source=" + itemname + ",itemID=" + str(id) + " value="+str(value)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("InfluxDB ID Error: {}".format(e))
|
logging.error("InfluxDB ID Error: {}".format(e))
|
||||||
|
|
||||||
|
@ -153,15 +154,12 @@ class LogController:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def get_data2(self, ids) -> dict:
|
async def get_data2(self, ids) -> dict:
|
||||||
def dateparse(time_in_secs):
|
|
||||||
return datetime.datetime.strptime(time_in_secs, '%Y-%m-%d %H:%M:%S')
|
|
||||||
|
|
||||||
|
dateparse = lambda dates: [datetime.datetime.strptime(d, '%Y-%m-%d %H:%M:%S') for d in dates]
|
||||||
result = dict()
|
result = dict()
|
||||||
for id in ids:
|
for id in ids:
|
||||||
# df = pd.read_csv("./logs/sensor_%s.log" % id, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime',"Values"], header=None)
|
|
||||||
# concat all logs
|
|
||||||
all_filenames = glob.glob(os.path.join(self.logsFolderPath,f"sensor_{id}.log*"))
|
all_filenames = glob.glob(os.path.join(self.logsFolderPath,f"sensor_{id}.log*"))
|
||||||
df = pd.concat([pd.read_csv(f, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime', 'Values'], header=None) for f in all_filenames])
|
df = pd.concat([pd.read_csv(f, parse_dates=['DateTime'], date_parser=dateparse, index_col='DateTime', names=['DateTime', 'Values'], header=None) for f in all_filenames])
|
||||||
df = df.resample('60s').max()
|
df = df.resample('60s').max()
|
||||||
df = df.dropna()
|
df = df.dropna()
|
||||||
result[id] = {"time": df.index.astype(str).tolist(), "value":df.Values.tolist()}
|
result[id] = {"time": df.index.astype(str).tolist(), "value":df.Values.tolist()}
|
||||||
|
@ -180,12 +178,18 @@ class LogController:
|
||||||
|
|
||||||
def clear_log(self, name:str ) -> str:
|
def clear_log(self, name:str ) -> str:
|
||||||
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))
|
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))
|
||||||
for f in all_filenames:
|
|
||||||
os.remove(f)
|
|
||||||
|
|
||||||
if name in self.datalogger:
|
if name in self.datalogger:
|
||||||
|
self.datalogger[name].removeHandler(self.datalogger[name].handlers[0])
|
||||||
del self.datalogger[name]
|
del self.datalogger[name]
|
||||||
|
|
||||||
|
for f in all_filenames:
|
||||||
|
try:
|
||||||
|
os.remove(f)
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(e)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_zip_file_names(self, name: str) -> list:
|
def get_all_zip_file_names(self, name: str) -> list:
|
||||||
|
|
||||||
|
|
|
@ -45,11 +45,11 @@ class NotificationController:
|
||||||
|
|
||||||
async def _call_listener(self, title, message, type, action):
|
async def _call_listener(self, title, message, type, action):
|
||||||
for id, method in self.listener.items():
|
for id, method in self.listener.items():
|
||||||
print(id, method)
|
#print(id, method)
|
||||||
asyncio.create_task(method(self.cbpi, title, message, type, action ))
|
asyncio.create_task(method(self.cbpi, title, message, type, action ))
|
||||||
|
|
||||||
|
|
||||||
def notify(self, title, message: str, type: NotificationType = NotificationType.INFO, action=[]) -> None:
|
def notify(self, title, message: str, type: NotificationType = NotificationType.INFO, action=[], timeout: int=5000) -> None:
|
||||||
'''
|
'''
|
||||||
This is a convinience method to send notification to the client
|
This is a convinience method to send notification to the client
|
||||||
|
|
||||||
|
@ -66,8 +66,8 @@ class NotificationController:
|
||||||
|
|
||||||
actions = list(map(lambda item: prepare_action(item), action))
|
actions = list(map(lambda item: prepare_action(item), action))
|
||||||
self.callback_cache[notifcation_id] = action
|
self.callback_cache[notifcation_id] = action
|
||||||
self.cbpi.ws.send(dict(id=notifcation_id, topic="notifiaction", type=type.value, title=title, message=message, action=actions))
|
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)
|
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)
|
||||||
asyncio.create_task(self._call_listener(title, message, type, action))
|
asyncio.create_task(self._call_listener(title, message, type, action))
|
||||||
|
|
||||||
|
|
|
@ -190,14 +190,14 @@ class PluginController():
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def load_plugin_list(self):
|
async def load_plugin_list(self, filter="cbpi"):
|
||||||
result = []
|
result = []
|
||||||
try:
|
try:
|
||||||
discovered_plugins = {
|
discovered_plugins = {
|
||||||
name: importlib.import_module(name)
|
name: importlib.import_module(name)
|
||||||
for finder, name, ispkg
|
for finder, name, ispkg
|
||||||
in pkgutil.iter_modules()
|
in pkgutil.iter_modules()
|
||||||
if name.startswith('cbpi') and len(name) > 4
|
if name.startswith(filter) and len(name) > 4
|
||||||
}
|
}
|
||||||
for key, module in discovered_plugins.items():
|
for key, module in discovered_plugins.items():
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
|
|
|
@ -2,14 +2,16 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from re import M
|
from re import M
|
||||||
from asyncio_mqtt import Client, MqttError, Will, client
|
from asyncio_mqtt import Client, MqttError, Will
|
||||||
from contextlib import AsyncExitStack, asynccontextmanager
|
from contextlib import AsyncExitStack, asynccontextmanager
|
||||||
from cbpi import __version__
|
from cbpi import __version__
|
||||||
import logging
|
import logging
|
||||||
|
import shortuuid
|
||||||
|
|
||||||
class SatelliteController:
|
class SatelliteController:
|
||||||
|
|
||||||
def __init__(self, cbpi):
|
def __init__(self, cbpi):
|
||||||
|
self.client_id = shortuuid.uuid()
|
||||||
self.cbpi = cbpi
|
self.cbpi = cbpi
|
||||||
self.kettlecontroller = cbpi.kettle
|
self.kettlecontroller = cbpi.kettle
|
||||||
self.fermentercontroller = cbpi.fermenter
|
self.fermentercontroller = cbpi.fermenter
|
||||||
|
@ -34,7 +36,46 @@ class SatelliteController:
|
||||||
self.tasks = set()
|
self.tasks = set()
|
||||||
|
|
||||||
async def init(self):
|
async def init(self):
|
||||||
asyncio.create_task(self.init_client(self.cbpi))
|
|
||||||
|
#not sure if required like done in the old routine
|
||||||
|
async def cancel_tasks(tasks):
|
||||||
|
for task in tasks:
|
||||||
|
if task.done():
|
||||||
|
continue
|
||||||
|
task.cancel()
|
||||||
|
try:
|
||||||
|
await task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.client = Client(self.host, port=self.port, username=self.username, password=self.password, will=Will(topic="cbpi/disconnect", payload="CBPi Server Disconnected"),client_id=self.client_id)
|
||||||
|
self.loop = asyncio.get_event_loop()
|
||||||
|
## Listen for mqtt messages in an (unawaited) asyncio task
|
||||||
|
task = self.loop.create_task(self.listen())
|
||||||
|
## Save a reference to the task so it doesn't get garbage collected
|
||||||
|
self.tasks.add(task)
|
||||||
|
task.add_done_callback(self.tasks.remove)
|
||||||
|
|
||||||
|
self.logger.info("MQTT Connected to {}:{}".format(self.host, self.port))
|
||||||
|
|
||||||
|
async def listen(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
async with self.client as client:
|
||||||
|
async with client.messages() as messages:
|
||||||
|
await client.subscribe("#")
|
||||||
|
async for message in messages:
|
||||||
|
for topic_filter in self.topic_filters:
|
||||||
|
topic = topic_filter[0]
|
||||||
|
method = topic_filter[1]
|
||||||
|
if message.topic.matches(topic):
|
||||||
|
await (method(message))
|
||||||
|
except MqttError as e:
|
||||||
|
self.logger.error("MQTT Exception: {}".format(e))
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error("MQTT General Exception: {}".format(e))
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
async def publish(self, topic, message, retain=False):
|
async def publish(self, topic, message, retain=False):
|
||||||
if self.client is not None and self.client._connected:
|
if self.client is not None and self.client._connected:
|
||||||
|
@ -43,26 +84,25 @@ class SatelliteController:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning("Failed to push data via mqtt: {}".format(e))
|
self.logger.warning("Failed to push data via mqtt: {}".format(e))
|
||||||
|
|
||||||
async def _actor_on(self, messages):
|
async def _actor_on(self, message):
|
||||||
async for message in messages:
|
|
||||||
try:
|
try:
|
||||||
topic_key = message.topic.split("/")
|
topic_key = str(message.topic).split("/")
|
||||||
await self.cbpi.actor.on(topic_key[2])
|
await self.cbpi.actor.on(topic_key[2])
|
||||||
|
self.logger.warning("Processed actor {} on via mqtt".format(topic_key[2]))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning("Failed to process actor on via mqtt: {}".format(e))
|
self.logger.warning("Failed to process actor on via mqtt: {}".format(e))
|
||||||
|
|
||||||
async def _actor_off(self, messages):
|
async def _actor_off(self, message):
|
||||||
async for message in messages:
|
|
||||||
try:
|
try:
|
||||||
topic_key = message.topic.split("/")
|
topic_key = str(message.topic).split("/")
|
||||||
await self.cbpi.actor.off(topic_key[2])
|
await self.cbpi.actor.off(topic_key[2])
|
||||||
|
self.logger.warning("Processed actor {} off via mqtt".format(topic_key[2]))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning("Failed to process actor off via mqtt: {}".format(e))
|
self.logger.warning("Failed to process actor off via mqtt: {}".format(e))
|
||||||
|
|
||||||
async def _actor_power(self, messages):
|
async def _actor_power(self, message):
|
||||||
async for message in messages:
|
|
||||||
try:
|
try:
|
||||||
topic_key = message.topic.split("/")
|
topic_key = str(message.topic).split("/")
|
||||||
try:
|
try:
|
||||||
power=int(message.payload.decode())
|
power=int(message.payload.decode())
|
||||||
if power > 100:
|
if power > 100:
|
||||||
|
@ -76,8 +116,7 @@ class SatelliteController:
|
||||||
except:
|
except:
|
||||||
self.logger.warning("Failed to set actor power via mqtt")
|
self.logger.warning("Failed to set actor power via mqtt")
|
||||||
|
|
||||||
async def _kettleupdate(self, messages):
|
async def _kettleupdate(self, message):
|
||||||
async for message in messages:
|
|
||||||
try:
|
try:
|
||||||
self.kettle=self.kettlecontroller.get_state()
|
self.kettle=self.kettlecontroller.get_state()
|
||||||
for item in self.kettle['data']:
|
for item in self.kettle['data']:
|
||||||
|
@ -85,8 +124,7 @@ class SatelliteController:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning("Failed to send kettleupdate via mqtt: {}".format(e))
|
self.logger.warning("Failed to send kettleupdate via mqtt: {}".format(e))
|
||||||
|
|
||||||
async def _fermenterupdate(self, messages):
|
async def _fermenterupdate(self, message):
|
||||||
async for message in messages:
|
|
||||||
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']:
|
||||||
|
@ -94,8 +132,7 @@ class SatelliteController:
|
||||||
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))
|
||||||
|
|
||||||
async def _actorupdate(self, messages):
|
async def _actorupdate(self, message):
|
||||||
async for message in messages:
|
|
||||||
try:
|
try:
|
||||||
self.actor=self.actorcontroller.get_state()
|
self.actor=self.actorcontroller.get_state()
|
||||||
for item in self.actor['data']:
|
for item in self.actor['data']:
|
||||||
|
@ -103,8 +140,7 @@ class SatelliteController:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning("Failed to send actorupdate via mqtt: {}".format(e))
|
self.logger.warning("Failed to send actorupdate via mqtt: {}".format(e))
|
||||||
|
|
||||||
async def _sensorupdate(self, messages):
|
async def _sensorupdate(self, message):
|
||||||
async for message in messages:
|
|
||||||
try:
|
try:
|
||||||
self.sensor=self.sensorcontroller.get_state()
|
self.sensor=self.sensorcontroller.get_state()
|
||||||
for item in self.sensor['data']:
|
for item in self.sensor['data']:
|
||||||
|
@ -120,9 +156,10 @@ class SatelliteController:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if self.client._connected.done():
|
if self.client._connected.done():
|
||||||
async with self.client.filtered_messages(topic) as messages:
|
async with self.client.messages() as messages:
|
||||||
await self.client.subscribe(topic)
|
await self.client.subscribe(topic)
|
||||||
async for message in messages:
|
async for message in messages:
|
||||||
|
if message.topic.matches(topic):
|
||||||
await method(message.payload.decode())
|
await method(message.payload.decode())
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
# Cancel
|
# Cancel
|
||||||
|
@ -134,45 +171,3 @@ class SatelliteController:
|
||||||
|
|
||||||
# wait before try to resubscribe
|
# wait before try to resubscribe
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
async def init_client(self, cbpi):
|
|
||||||
|
|
||||||
async def cancel_tasks(tasks):
|
|
||||||
for task in tasks:
|
|
||||||
if task.done():
|
|
||||||
continue
|
|
||||||
task.cancel()
|
|
||||||
try:
|
|
||||||
await task
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
async with AsyncExitStack() as stack:
|
|
||||||
self.tasks = set()
|
|
||||||
stack.push_async_callback(cancel_tasks, self.tasks)
|
|
||||||
self.client = Client(self.host, port=self.port, username=self.username, password=self.password, will=Will(topic="cbpi/disconnect", payload="CBPi Server Disconnected"))
|
|
||||||
|
|
||||||
await stack.enter_async_context(self.client)
|
|
||||||
|
|
||||||
for topic_filter in self.topic_filters:
|
|
||||||
topic = topic_filter[0]
|
|
||||||
method = topic_filter[1]
|
|
||||||
manager = self.client.filtered_messages(topic)
|
|
||||||
messages = await stack.enter_async_context(manager)
|
|
||||||
task = asyncio.create_task(method(messages))
|
|
||||||
self.tasks.add(task)
|
|
||||||
|
|
||||||
for topic_filter in self.topic_filters:
|
|
||||||
topic = topic_filter[0]
|
|
||||||
await self.client.subscribe(topic)
|
|
||||||
|
|
||||||
self.logger.info("MQTT Connected to {}:{}".format(self.host, self.port))
|
|
||||||
await asyncio.gather(*self.tasks)
|
|
||||||
|
|
||||||
except MqttError as e:
|
|
||||||
self.logger.error("MQTT Exception: {}".format(e))
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error("MQTT General Exception: {}".format(e))
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import yaml
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
from os import listdir
|
from os import listdir
|
||||||
|
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, Props, Step
|
||||||
|
@ -54,15 +55,34 @@ class StepController:
|
||||||
|
|
||||||
# create file if not exists
|
# create file if not exists
|
||||||
if os.path.exists(self.path) is False:
|
if os.path.exists(self.path) is False:
|
||||||
|
logging.warning("Missing step_data.json file. INIT empty file")
|
||||||
with open(self.path, "w") as file:
|
with open(self.path, "w") as file:
|
||||||
json.dump(dict(basic={}, steps=[]), file, indent=4, sort_keys=True)
|
json.dump(dict(basic={}, steps=[]), file, indent=4, sort_keys=True)
|
||||||
|
|
||||||
#load from json file
|
#load from json file
|
||||||
|
try:
|
||||||
with open(self.path) as json_file:
|
with open(self.path) as json_file:
|
||||||
data = json.load(json_file)
|
data = json.load(json_file)
|
||||||
self.basic_data = data["basic"]
|
self.basic_data = data["basic"]
|
||||||
self.profile = data["steps"]
|
self.profile = data["steps"]
|
||||||
|
|
||||||
|
# Start step after start up
|
||||||
|
self.profile = list(map(lambda item: self.create(item), self.profile))
|
||||||
|
if startActive is True:
|
||||||
|
active_step = self.find_by_status("A")
|
||||||
|
if active_step is not None:
|
||||||
|
asyncio.get_event_loop().create_task(self.start_step(active_step))
|
||||||
|
#self._loop.create_task(self.start_step(active_step))
|
||||||
|
except:
|
||||||
|
logging.warning("Invalid step_data.json file - Creating empty file")
|
||||||
|
os.remove(self.path)
|
||||||
|
with open(self.path, "w") as file:
|
||||||
|
json.dump(dict(basic={"name": ""}, steps=[]), file, indent=4, sort_keys=True)
|
||||||
|
|
||||||
|
with open(self.path) as json_file:
|
||||||
|
data = json.load(json_file)
|
||||||
|
self.basic_data = data["basic"]
|
||||||
|
self.profile = data["steps"]
|
||||||
|
|
||||||
# Start step after start up
|
# Start step after start up
|
||||||
self.profile = list(map(lambda item: self.create(item), self.profile))
|
self.profile = list(map(lambda item: self.create(item), self.profile))
|
||||||
|
|
|
@ -45,6 +45,7 @@ class ConfigUpdate(CBPiExtension):
|
||||||
influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None)
|
influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None)
|
||||||
influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None)
|
influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None)
|
||||||
influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", None)
|
influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", None)
|
||||||
|
influxdbmeasurement = self.cbpi.config.get("INFLUXDBMEASUREMENT", None)
|
||||||
mqttupdate = self.cbpi.config.get("MQTTUpdate", None)
|
mqttupdate = self.cbpi.config.get("MQTTUpdate", None)
|
||||||
PRESSURE_UNIT = self.cbpi.config.get("PRESSURE_UNIT", None)
|
PRESSURE_UNIT = self.cbpi.config.get("PRESSURE_UNIT", None)
|
||||||
SENSOR_LOG_BACKUP_COUNT = self.cbpi.config.get("SENSOR_LOG_BACKUP_COUNT", None)
|
SENSOR_LOG_BACKUP_COUNT = self.cbpi.config.get("SENSOR_LOG_BACKUP_COUNT", None)
|
||||||
|
@ -267,6 +268,14 @@ class ConfigUpdate(CBPiExtension):
|
||||||
except:
|
except:
|
||||||
logger.warning('Unable to update config')
|
logger.warning('Unable to update config')
|
||||||
|
|
||||||
|
## Check if influxdbname is in config
|
||||||
|
if influxdbmeasurement is None:
|
||||||
|
logger.info("INIT Influxdb measurementname")
|
||||||
|
try:
|
||||||
|
await self.cbpi.config.add("INFLUXDBMEASUREMENT", "measurement", ConfigType.STRING, "Name of the measurement in your INFLUXDB database (default: measurement)")
|
||||||
|
except:
|
||||||
|
logger.warning('Unable to update config')
|
||||||
|
|
||||||
if mqttupdate is None:
|
if mqttupdate is None:
|
||||||
logger.info("INIT MQTT update frequency for Kettles and Fermenters")
|
logger.info("INIT MQTT update frequency for Kettles and Fermenters")
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -44,8 +44,8 @@ class FermenterAutostart(CBPiExtension):
|
||||||
|
|
||||||
@parameters([Property.Number(label="HeaterOffsetOn", configurable=True, description="Offset as decimal number when the heater is switched on. Should be greater then 'HeaterOffsetOff'. For example a value of 2 switches on the heater if the current temperature is 2 degrees below the target temperature"),
|
@parameters([Property.Number(label="HeaterOffsetOn", configurable=True, description="Offset as decimal number when the heater is switched on. Should be greater then 'HeaterOffsetOff'. For example a value of 2 switches on the heater if the current temperature is 2 degrees 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="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 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 below 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.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)")])
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,38 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from cbpi.api import *
|
from cbpi.api import *
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
import re
|
import re
|
||||||
import random
|
import logging
|
||||||
|
from cbpi.api.dataclasses import NotificationAction, NotificationType
|
||||||
|
|
||||||
cache = {}
|
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)")
|
||||||
|
])
|
||||||
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)
|
||||||
self.running = True
|
self.running = True
|
||||||
self.value = 0
|
self.value = 0
|
||||||
|
self.timeout=int(self.props.get("Timeout", 60))
|
||||||
|
self.starttime = time.time()
|
||||||
|
self.notificationsend = False
|
||||||
|
self.nextchecktime=self.starttime+self.timeout
|
||||||
|
self.sensor=self.get_sensor(self.id)
|
||||||
|
self.lastdata=time.time()
|
||||||
|
|
||||||
|
async def Confirm(self, **kwargs):
|
||||||
|
self.nextchecktime = time.time() + self.timeout
|
||||||
|
self.notificationsend = False
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def message(self):
|
||||||
|
target_timestring= datetime.fromtimestamp(self.lastdata)
|
||||||
|
self.cbpi.notify("HTTPSensor Timeout", "Sensor '" + str(self.sensor.name) + "' did not respond. Last data received: "+target_timestring.strftime("%D %H:%M"), NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
||||||
|
pass
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
'''
|
'''
|
||||||
|
@ -22,12 +41,22 @@ class HTTPSensor(CBPiSensor):
|
||||||
In this example the code is executed every second
|
In this example the code is executed every second
|
||||||
'''
|
'''
|
||||||
while self.running is True:
|
while self.running is True:
|
||||||
|
if self.timeout !=0:
|
||||||
|
currenttime=time.time()
|
||||||
|
if currenttime > self.nextchecktime and self.notificationsend == False:
|
||||||
|
await self.message()
|
||||||
|
self.notificationsend=True
|
||||||
try:
|
try:
|
||||||
cache_value = cache.pop(self.props.get("Key"), None)
|
cache_value = cache.pop(self.props.get("Key"), None)
|
||||||
if cache_value is not None:
|
if cache_value is not None:
|
||||||
self.value = float(cache_value)
|
self.value = float(cache_value)
|
||||||
self.push_update(self.value)
|
self.push_update(self.value)
|
||||||
|
if self.timeout !=0:
|
||||||
|
self.nextchecktime = currenttime + self.timeout
|
||||||
|
self.notificationsend = False
|
||||||
|
self.lastdata=time.time()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
pass
|
pass
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from cbpi.api.dataclasses import NotificationAction, NotificationType
|
||||||
from cbpi.api import parameters, Property, CBPiSensor
|
from cbpi.api import parameters, Property, CBPiSensor
|
||||||
from cbpi.api import *
|
from cbpi.api import *
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
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.Number(label="Timeout", configurable="True",unit="sec",
|
||||||
|
description="Timeout in seconds to send notification (default:60 | deactivated: 0)")])
|
||||||
class MQTTSensor(CBPiSensor):
|
class MQTTSensor(CBPiSensor):
|
||||||
|
|
||||||
def __init__(self, cbpi, id, props):
|
def __init__(self, cbpi, id, props):
|
||||||
|
@ -19,6 +23,22 @@ class MQTTSensor(CBPiSensor):
|
||||||
self.payload_text = self.payload_text.split('.')
|
self.payload_text = self.payload_text.split('.')
|
||||||
self.mqtt_task = self.cbpi.satellite.subcribe(self.Topic, self.on_message)
|
self.mqtt_task = self.cbpi.satellite.subcribe(self.Topic, self.on_message)
|
||||||
self.value: float = 999
|
self.value: float = 999
|
||||||
|
self.timeout=int(self.props.get("Timeout", 60))
|
||||||
|
self.starttime = time.time()
|
||||||
|
self.notificationsend = False
|
||||||
|
self.nextchecktime=self.starttime+self.timeout
|
||||||
|
self.lastdata=time.time()
|
||||||
|
self.sensor=self.get_sensor(self.id)
|
||||||
|
|
||||||
|
async def Confirm(self, **kwargs):
|
||||||
|
self.nextchecktime = time.time() + self.timeout
|
||||||
|
self.notificationsend = False
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def message(self):
|
||||||
|
target_timestring= datetime.fromtimestamp(self.lastdata)
|
||||||
|
self.cbpi.notify("MQTTSensor Timeout", "Sensor '" + str(self.sensor.name) + "' did not respond. Last data received: "+target_timestring.strftime("%D %H:%M"), NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)])
|
||||||
|
pass
|
||||||
|
|
||||||
async def on_message(self, message):
|
async def on_message(self, message):
|
||||||
val = json.loads(message)
|
val = json.loads(message)
|
||||||
|
@ -31,11 +51,19 @@ class MQTTSensor(CBPiSensor):
|
||||||
self.value = float(val)
|
self.value = float(val)
|
||||||
self.log_data(self.value)
|
self.log_data(self.value)
|
||||||
self.push_update(self.value)
|
self.push_update(self.value)
|
||||||
|
if self.timeout !=0:
|
||||||
|
self.nextchecktime = time.time() + self.timeout
|
||||||
|
self.notificationsend = False
|
||||||
|
self.lastdata=time.time()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info("MQTT Sensor Error {}".format(e))
|
logging.info("MQTT Sensor Error {}".format(e))
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
while self.running:
|
while self.running:
|
||||||
|
if self.timeout !=0:
|
||||||
|
if time.time() > self.nextchecktime and self.notificationsend == False:
|
||||||
|
await self.message()
|
||||||
|
self.notificationsend=True
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
|
|
|
@ -247,7 +247,7 @@ class ActorHttpEndpoints():
|
||||||
"""
|
"""
|
||||||
actor_id = request.match_info['id']
|
actor_id = request.match_info['id']
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
print(data)
|
#print(data)
|
||||||
await self.controller.call_action(actor_id, data.get("action"), data.get("parameter"))
|
await self.controller.call_action(actor_id, data.get("action"), data.get("parameter"))
|
||||||
|
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
|
@ -69,7 +69,7 @@ class DashBoardHttpEndpoints:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
dashboard_id = int(request.match_info['id'])
|
dashboard_id = int(request.match_info['id'])
|
||||||
await self.cbpi.dashboard.add_content(dashboard_id, data)
|
await self.cbpi.dashboard.add_content(dashboard_id, data)
|
||||||
print("##### SAVE")
|
#print("##### SAVE")
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
||||||
@request_mapping(path="/{id:\d+}/content", method="DELETE", auth_required=False)
|
@request_mapping(path="/{id:\d+}/content", method="DELETE", auth_required=False)
|
||||||
|
|
|
@ -58,7 +58,7 @@ class FermenterRecipeHttpEndpoints():
|
||||||
description: successful operation
|
description: successful operation
|
||||||
"""
|
"""
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
print(data)
|
#print(data)
|
||||||
return web.json_response(dict(id=await self.controller.create(data.get("name"))))
|
return web.json_response(dict(id=await self.controller.create(data.get("name"))))
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ class FermenterRecipeHttpEndpoints():
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
name = request.match_info['name']
|
name = request.match_info['name']
|
||||||
await self.controller.save(name, data)
|
await self.controller.save(name, data)
|
||||||
print(data)
|
#print(data)
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
||||||
@request_mapping(path="/{name}", method="DELETE", auth_required=False)
|
@request_mapping(path="/{name}", method="DELETE", auth_required=False)
|
||||||
|
|
|
@ -189,7 +189,6 @@ class LogHttpEndpoints:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
"""
|
"""
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
print(data)
|
|
||||||
return web.json_response(await self.cbpi.log.get_data2(data), dumps=json_dumps)
|
return web.json_response(await self.cbpi.log.get_data2(data), dumps=json_dumps)
|
||||||
|
|
||||||
|
|
||||||
|
@ -240,7 +239,7 @@ class LogHttpEndpoints:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
|
|
||||||
result = await self.cbpi.log.get_data(data)
|
result = await self.cbpi.log.get_data(data)
|
||||||
print("JSON")
|
#print("JSON")
|
||||||
print(json.dumps(result, cls=ComplexEncoder))
|
#print(json.dumps(result, cls=ComplexEncoder))
|
||||||
print("JSON----")
|
#print("JSON----")
|
||||||
return web.json_response(result, dumps=json_dumps)
|
return web.json_response(result, dumps=json_dumps)
|
||||||
|
|
|
@ -35,6 +35,6 @@ class NotificationHttpEndpoints:
|
||||||
|
|
||||||
notification_id = request.match_info['id']
|
notification_id = request.match_info['id']
|
||||||
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=204)
|
|
@ -57,7 +57,7 @@ class RecipeHttpEndpoints():
|
||||||
description: successful operation
|
description: successful operation
|
||||||
"""
|
"""
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
print(data)
|
#print(data)
|
||||||
return web.json_response(dict(id=await self.controller.create(data.get("name"))))
|
return web.json_response(dict(id=await self.controller.create(data.get("name"))))
|
||||||
|
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ class RecipeHttpEndpoints():
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
name = request.match_info['name']
|
name = request.match_info['name']
|
||||||
await self.controller.save(name, data)
|
await self.controller.save(name, data)
|
||||||
print(data)
|
#print(data)
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
||||||
@request_mapping(path="/{name}", method="DELETE", auth_required=False)
|
@request_mapping(path="/{name}", method="DELETE", auth_required=False)
|
||||||
|
|
|
@ -224,7 +224,7 @@ class SensorHttpEndpoints():
|
||||||
"""
|
"""
|
||||||
sensor_id = request.match_info['id']
|
sensor_id = request.match_info['id']
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
print(data)
|
#print(data)
|
||||||
await self.controller.call_action(sensor_id, data.get("action"), data.get("parameter"))
|
await self.controller.call_action(sensor_id, data.get("action"), data.get("parameter"))
|
||||||
|
|
||||||
return web.Response(status=204)
|
return web.Response(status=204)
|
||||||
|
|
|
@ -28,6 +28,12 @@ class SystemHttpEndpoints:
|
||||||
"200":
|
"200":
|
||||||
description: successful operation
|
description: successful operation
|
||||||
"""
|
"""
|
||||||
|
plugin_list = await self.cbpi.plugin.load_plugin_list("cbpi4gui")
|
||||||
|
try:
|
||||||
|
version= plugin_list[0].get("Version", "not detected")
|
||||||
|
except:
|
||||||
|
version="not detected"
|
||||||
|
|
||||||
return web.json_response(data=dict(
|
return web.json_response(data=dict(
|
||||||
actor=self.cbpi.actor.get_state(),
|
actor=self.cbpi.actor.get_state(),
|
||||||
fermenter=self.cbpi.fermenter.get_state(),
|
fermenter=self.cbpi.fermenter.get_state(),
|
||||||
|
@ -37,6 +43,7 @@ class SystemHttpEndpoints:
|
||||||
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(),
|
||||||
version=__version__,
|
version=__version__,
|
||||||
|
guiversion=version,
|
||||||
codename=__codename__)
|
codename=__codename__)
|
||||||
, dumps=json_dumps)
|
, dumps=json_dumps)
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,10 @@ class ComplexEncoder(JSONEncoder):
|
||||||
elif isinstance(obj, datetime.datetime):
|
elif isinstance(obj, datetime.datetime):
|
||||||
return obj.__str__()
|
return obj.__str__()
|
||||||
elif isinstance(obj, Timestamp):
|
elif isinstance(obj, Timestamp):
|
||||||
print("TIMe")
|
#print("TIMe")
|
||||||
return obj.__str__()
|
return obj.__str__()
|
||||||
else:
|
else:
|
||||||
print(type(obj))
|
#print(type(obj))
|
||||||
raise TypeError()
|
raise TypeError()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
typing-extensions>=4
|
typing-extensions>=4
|
||||||
aiohttp==3.8.1
|
aiohttp==3.8.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.4.0
|
aiohttp-security==0.4.0
|
||||||
aiohttp-session==2.11.0
|
aiohttp-session==2.12.0
|
||||||
aiohttp-swagger==1.0.16
|
aiohttp-swagger==1.0.16
|
||||||
aiojobs==1.0.0
|
aiojobs==1.1.0
|
||||||
aiosqlite==0.17.0
|
aiosqlite==0.17.0
|
||||||
cryptography==36.0.1
|
cryptography==36.0.1
|
||||||
requests==2.27.1
|
requests==2.28.1
|
||||||
voluptuous==0.12.2
|
voluptuous==0.13.1
|
||||||
pyfiglet==0.8.post1
|
pyfiglet==0.8.post1
|
||||||
pandas==1.4.1
|
pandas==1.5.3
|
||||||
shortuuid==1.0.8
|
shortuuid==1.0.11
|
||||||
tabulate==0.8.9
|
tabulate==0.9.0
|
||||||
numpy==1.22.2
|
numpy==1.24.1
|
||||||
cbpi4gui
|
cbpi4gui
|
||||||
click==8.0.4
|
click==8.1.3
|
||||||
importlib_metadata==4.11.1
|
importlib_metadata==4.11.1
|
||||||
asyncio-mqtt
|
asyncio-mqtt==0.16.1
|
||||||
psutil==5.9.0
|
psutil==5.9.4
|
||||||
zipp>=0.5
|
zipp>=0.5
|
||||||
PyInquirer==1.0.3
|
colorama==0.4.6
|
||||||
colorama==0.4.4
|
|
||||||
pytest-aiohttp
|
pytest-aiohttp
|
||||||
coverage==6.3.1
|
coverage==6.3.1
|
||||||
|
inquirer==3.1.1
|
||||||
|
|
28
setup.py
28
setup.py
|
@ -39,29 +39,29 @@ 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.1",
|
"aiohttp==3.8.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.4.0",
|
"aiohttp-security==0.4.0",
|
||||||
"aiohttp-session==2.11.0",
|
"aiohttp-session==2.12.0",
|
||||||
"aiohttp-swagger==1.0.16",
|
"aiohttp-swagger==1.0.16",
|
||||||
"aiojobs==1.0.0 ",
|
"aiojobs==1.1.0 ",
|
||||||
"aiosqlite==0.17.0",
|
"aiosqlite==0.17.0",
|
||||||
"cryptography==36.0.1",
|
"cryptography==36.0.1",
|
||||||
"requests==2.27.1",
|
"requests==2.28.1",
|
||||||
"voluptuous==0.12.2",
|
"voluptuous==0.13.1",
|
||||||
"pyfiglet==0.8.post1",
|
"pyfiglet==0.8.post1",
|
||||||
'click==8.0.4',
|
'click==8.1.3',
|
||||||
'shortuuid==1.0.8',
|
'shortuuid==1.0.11',
|
||||||
'tabulate==0.8.9',
|
'tabulate==0.9.0',
|
||||||
'asyncio-mqtt',
|
'asyncio-mqtt==0.16.1',
|
||||||
'PyInquirer==1.0.3',
|
'inquirer==3.1.1',
|
||||||
'colorama==0.4.4',
|
'colorama==0.4.6',
|
||||||
'psutil==5.9.0',
|
'psutil==5.9.4',
|
||||||
'cbpi4gui',
|
'cbpi4gui',
|
||||||
'importlib_metadata',
|
'importlib_metadata',
|
||||||
'numpy==1.22.2',
|
'numpy==1.24.1',
|
||||||
'pandas==1.4.1'] + (
|
'pandas==1.5.3'] + (
|
||||||
['RPi.GPIO==0.7.1'] if raspberrypi else [] ),
|
['RPi.GPIO==0.7.1'] if raspberrypi else [] ),
|
||||||
|
|
||||||
dependency_links=[
|
dependency_links=[
|
||||||
|
|
|
@ -6,20 +6,6 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": "John Doe"
|
"value": "John Doe"
|
||||||
},
|
},
|
||||||
"BREWERY_NAME": {
|
|
||||||
"description": "Brewery Name",
|
|
||||||
"name": "BREWERY_NAME",
|
|
||||||
"options": null,
|
|
||||||
"type": "string",
|
|
||||||
"value": "CraftBeerPi Brewery"
|
|
||||||
},
|
|
||||||
"MASH_TUN": {
|
|
||||||
"description": "Default Mash Tun",
|
|
||||||
"name": "MASH_TUN",
|
|
||||||
"options": null,
|
|
||||||
"type": "kettle",
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
"AddMashInStep": {
|
"AddMashInStep": {
|
||||||
"description": "Add MashIn Step automatically if not defined in recipe",
|
"description": "Add MashIn Step automatically if not defined in recipe",
|
||||||
"name": "AddMashInStep",
|
"name": "AddMashInStep",
|
||||||
|
@ -36,26 +22,206 @@
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "Yes"
|
"value": "Yes"
|
||||||
},
|
},
|
||||||
|
"AutoMode": {
|
||||||
|
"description": "Use AutoMode in steps",
|
||||||
|
"name": "AutoMode",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Yes",
|
||||||
|
"value": "Yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "No",
|
||||||
|
"value": "No"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
"value": "Yes"
|
||||||
|
},
|
||||||
|
"BREWERY_NAME": {
|
||||||
|
"description": "Brewery Name",
|
||||||
|
"name": "BREWERY_NAME",
|
||||||
|
"options": null,
|
||||||
|
"type": "string",
|
||||||
|
"value": "Some New Brewery Name"
|
||||||
|
},
|
||||||
|
"BoilKettle": {
|
||||||
|
"description": "Define Kettle that is used for Boil, Whirlpool and Cooldown. If not selected, MASH_TUN will be used",
|
||||||
|
"name": "BoilKettle",
|
||||||
|
"options": null,
|
||||||
|
"type": "kettle",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"CSVLOGFILES": {
|
||||||
|
"description": "Write sensor data to csv logfiles",
|
||||||
|
"name": "CSVLOGFILES",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Yes",
|
||||||
|
"value": "Yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "No",
|
||||||
|
"value": "No"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
"value": "Yes"
|
||||||
|
},
|
||||||
|
"INFLUXDB": {
|
||||||
|
"description": "Write sensor data to influxdb",
|
||||||
|
"name": "INFLUXDB",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Yes",
|
||||||
|
"value": "Yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "No",
|
||||||
|
"value": "No"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
"value": "No"
|
||||||
|
},
|
||||||
|
"INFLUXDBADDR": {
|
||||||
|
"description": "IP Address of your influxdb server (If INFLUXDBCLOUD set to Yes use URL Address of your influxdb cloud server)",
|
||||||
|
"name": "INFLUXDBADDR",
|
||||||
|
"options": null,
|
||||||
|
"type": "string",
|
||||||
|
"value": "localhost"
|
||||||
|
},
|
||||||
|
"INFLUXDBCLOUD": {
|
||||||
|
"description": "Write sensor data to influxdb cloud (INFLUXDB must set to Yes)",
|
||||||
|
"name": "INFLUXDBCLOUD",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Yes",
|
||||||
|
"value": "Yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "No",
|
||||||
|
"value": "No"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
"value": "No"
|
||||||
|
},
|
||||||
|
"INFLUXDBNAME": {
|
||||||
|
"description": "Name of your influxdb database name (If INFLUXDBCLOUD set to Yes use bucket of your influxdb cloud database)",
|
||||||
|
"name": "INFLUXDBNAME",
|
||||||
|
"options": null,
|
||||||
|
"type": "string",
|
||||||
|
"value": "cbpi4"
|
||||||
|
},
|
||||||
|
"INFLUXDBPORT": {
|
||||||
|
"description": "Port of your influxdb server",
|
||||||
|
"name": "INFLUXDBPORT",
|
||||||
|
"options": null,
|
||||||
|
"type": "string",
|
||||||
|
"value": "8086"
|
||||||
|
},
|
||||||
|
"INFLUXDBPWD": {
|
||||||
|
"description": "Password for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use token of your influxdb cloud database)",
|
||||||
|
"name": "INFLUXDBPWD",
|
||||||
|
"options": null,
|
||||||
|
"type": "string",
|
||||||
|
"value": " "
|
||||||
|
},
|
||||||
|
"INFLUXDBUSER": {
|
||||||
|
"description": "User name for your influxdb database (only if required)(If INFLUXDBCLOUD set to Yes use organisation of your influxdb cloud database)",
|
||||||
|
"name": "INFLUXDBUSER",
|
||||||
|
"options": null,
|
||||||
|
"type": "string",
|
||||||
|
"value": " "
|
||||||
|
},
|
||||||
|
"MASH_TUN": {
|
||||||
|
"description": "Default Mash Tun",
|
||||||
|
"name": "MASH_TUN",
|
||||||
|
"options": null,
|
||||||
|
"type": "kettle",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"MQTTUpdate": {
|
||||||
|
"description": "Forced MQTT Update frequency in s for Kettle and Fermenter (no changes in payload required). Restart required after change",
|
||||||
|
"name": "MQTTUpdate",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "30",
|
||||||
|
"value": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "60",
|
||||||
|
"value": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "120",
|
||||||
|
"value": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "300",
|
||||||
|
"value": 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Never",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"NOTIFY_ON_ERROR": {
|
||||||
|
"description": "Send Notification on Logging Error",
|
||||||
|
"name": "NOTIFY_ON_ERROR",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Yes",
|
||||||
|
"value": "Yes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "No",
|
||||||
|
"value": "No"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
"value": "No"
|
||||||
|
},
|
||||||
|
"PRESSURE_UNIT": {
|
||||||
|
"description": "Set unit for pressure",
|
||||||
|
"name": "PRESSURE_UNIT",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "kPa",
|
||||||
|
"value": "kPa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "PSI",
|
||||||
|
"value": "PSI"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
"value": "kPa"
|
||||||
|
},
|
||||||
"RECIPE_CREATION_PATH": {
|
"RECIPE_CREATION_PATH": {
|
||||||
"description": "API path to creation plugin. Default: empty",
|
"description": "API path to creation plugin. Default: upload . CHANGE ONLY IF USING A RECIPE CREATION PLUGIN",
|
||||||
"name": "RECIPE_CREATION_PATH",
|
"name": "RECIPE_CREATION_PATH",
|
||||||
"options": null,
|
"options": null,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": ""
|
"value": "upload"
|
||||||
},
|
},
|
||||||
"brewfather_api_key": {
|
"SENSOR_LOG_BACKUP_COUNT": {
|
||||||
"description": "Brewfather API Kay",
|
"description": "Max. number of backup logs",
|
||||||
"name": "brewfather_api_key",
|
"name": "SENSOR_LOG_BACKUP_COUNT",
|
||||||
"options": null,
|
"options": null,
|
||||||
"type": "string",
|
"type": "number",
|
||||||
"value": ""
|
"value": 3
|
||||||
},
|
},
|
||||||
"brewfather_user_id": {
|
"SENSOR_LOG_MAX_BYTES": {
|
||||||
"description": "Brewfather User ID",
|
"description": "Max. number of bytes in sensor logs",
|
||||||
"name": "brewfather_user_id",
|
"name": "SENSOR_LOG_MAX_BYTES",
|
||||||
"options": null,
|
"options": null,
|
||||||
"type": "string",
|
"type": "number",
|
||||||
"value": ""
|
"value": 100000
|
||||||
},
|
},
|
||||||
"TEMP_UNIT": {
|
"TEMP_UNIT": {
|
||||||
"description": "Temperature Unit",
|
"description": "Temperature Unit",
|
||||||
|
@ -73,9 +239,78 @@
|
||||||
"type": "select",
|
"type": "select",
|
||||||
"value": "C"
|
"value": "C"
|
||||||
},
|
},
|
||||||
"AutoMode": {
|
"brewfather_api_key": {
|
||||||
"description": "Use AutoMode in steps",
|
"description": "Brewfather API Key",
|
||||||
"name": "AutoMode",
|
"name": "brewfather_api_key",
|
||||||
|
"options": null,
|
||||||
|
"type": "string",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"brewfather_user_id": {
|
||||||
|
"description": "Brewfather User ID",
|
||||||
|
"name": "brewfather_user_id",
|
||||||
|
"options": null,
|
||||||
|
"type": "string",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"current_dashboard_number": {
|
||||||
|
"description": "Number of current Dashboard",
|
||||||
|
"name": "current_dashboard_number",
|
||||||
|
"options": null,
|
||||||
|
"type": "number",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
"max_dashboard_number": {
|
||||||
|
"description": "Max Number of Dashboards",
|
||||||
|
"name": "max_dashboard_number",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "1",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "2",
|
||||||
|
"value": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "3",
|
||||||
|
"value": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "4",
|
||||||
|
"value": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "5",
|
||||||
|
"value": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "6",
|
||||||
|
"value": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "7",
|
||||||
|
"value": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "8",
|
||||||
|
"value": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "9",
|
||||||
|
"value": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "10",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "select",
|
||||||
|
"value": 4
|
||||||
|
},
|
||||||
|
"slow_pipe_animation": {
|
||||||
|
"description": "Slow down dashboard pipe animation taking up close to 100% of the CPU's capacity",
|
||||||
|
"name": "slow_pipe_animation",
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"label": "Yes",
|
"label": "Yes",
|
||||||
|
@ -110,6 +345,13 @@
|
||||||
"type": "step",
|
"type": "step",
|
||||||
"value": "CooldownStep"
|
"value": "CooldownStep"
|
||||||
},
|
},
|
||||||
|
"steps_cooldown_actor": {
|
||||||
|
"description": "Actor to trigger cooldown water on and off (default: None)",
|
||||||
|
"name": "steps_cooldown_actor",
|
||||||
|
"options": null,
|
||||||
|
"type": "actor",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
"steps_cooldown_sensor": {
|
"steps_cooldown_sensor": {
|
||||||
"description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)",
|
"description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)",
|
||||||
"name": "steps_cooldown_sensor",
|
"name": "steps_cooldown_sensor",
|
||||||
|
@ -122,7 +364,7 @@
|
||||||
"name": "steps_cooldown_temp",
|
"name": "steps_cooldown_temp",
|
||||||
"options": null,
|
"options": null,
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"value": "20"
|
"value": 35
|
||||||
},
|
},
|
||||||
"steps_mash": {
|
"steps_mash": {
|
||||||
"description": "Mash step type",
|
"description": "Mash step type",
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{
|
{
|
||||||
"data": [
|
"data": []
|
||||||
|
|
||||||
]
|
|
||||||
}
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
{
|
{
|
||||||
"data": [
|
"data": []
|
||||||
|
|
||||||
]
|
|
||||||
}
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
{
|
{
|
||||||
"data": [
|
"data": []
|
||||||
|
|
||||||
]
|
|
||||||
}
|
}
|
|
@ -2,7 +2,5 @@
|
||||||
"basic": {
|
"basic": {
|
||||||
"name": ""
|
"name": ""
|
||||||
},
|
},
|
||||||
"steps": [
|
"steps": []
|
||||||
|
|
||||||
]
|
|
||||||
}
|
}
|
|
@ -8,7 +8,6 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
|
||||||
|
|
||||||
class ActorTestCase(CraftBeerPiTestCase):
|
class ActorTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_actor_switch(self):
|
async def test_actor_switch(self):
|
||||||
|
|
||||||
resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"})
|
resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"})
|
||||||
|
@ -25,7 +24,6 @@ class ActorTestCase(CraftBeerPiTestCase):
|
||||||
i = self.cbpi.actor.find_by_id("3CUJte4bkxDMFCtLX8eqsX")
|
i = self.cbpi.actor.find_by_id("3CUJte4bkxDMFCtLX8eqsX")
|
||||||
assert i.instance.state is False
|
assert i.instance.state is False
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_crud(self):
|
async def test_crud(self):
|
||||||
data = {
|
data = {
|
||||||
"name": "SomeActor",
|
"name": "SomeActor",
|
||||||
|
@ -63,7 +61,6 @@ class ActorTestCase(CraftBeerPiTestCase):
|
||||||
resp = await self.client.delete(path="/actor/%s" % sensor_id)
|
resp = await self.client.delete(path="/actor/%s" % sensor_id)
|
||||||
assert resp.status == 204
|
assert resp.status == 204
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_crud_negative(self):
|
async def test_crud_negative(self):
|
||||||
data = {
|
data = {
|
||||||
"name": "CustomActor",
|
"name": "CustomActor",
|
||||||
|
@ -81,7 +78,6 @@ class ActorTestCase(CraftBeerPiTestCase):
|
||||||
resp = await self.client.put(path="/actor/%s" % 9999, json=data)
|
resp = await self.client.put(path="/actor/%s" % 9999, json=data)
|
||||||
assert resp.status == 500
|
assert resp.status == 500
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_actor_action(self):
|
async def test_actor_action(self):
|
||||||
resp = await self.client.post(path="/actor/1/action", json=dict(name="myAction", parameter=dict(name="Manuel")))
|
resp = await self.client.post(path="/actor/1/action", json=dict(name="myAction", parameter=dict(name="Manuel")))
|
||||||
assert resp.status == 204
|
assert resp.status == 204
|
||||||
|
|
|
@ -5,19 +5,16 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
class ConfigTestCase(CraftBeerPiTestCase):
|
class ConfigTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_get(self):
|
async def test_get(self):
|
||||||
|
|
||||||
assert self.cbpi.config.get("steps_boil_temp", 1) == "99"
|
assert self.cbpi.config.get("steps_boil_temp", 1) == "99"
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_set_get(self):
|
async def test_set_get(self):
|
||||||
value = 35
|
value = 35
|
||||||
|
|
||||||
await self.cbpi.config.set("steps_cooldown_temp", value)
|
await self.cbpi.config.set("steps_cooldown_temp", value)
|
||||||
assert self.cbpi.config.get("steps_cooldown_temp", 1) == value
|
assert self.cbpi.config.get("steps_cooldown_temp", 1) == value
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_http_set(self):
|
async def test_http_set(self):
|
||||||
value = "Some New Brewery Name"
|
value = "Some New Brewery Name"
|
||||||
key = "BREWERY_NAME"
|
key = "BREWERY_NAME"
|
||||||
|
@ -27,12 +24,10 @@ class ConfigTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
assert self.cbpi.config.get(key, -1) == value
|
assert self.cbpi.config.get(key, -1) == value
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_http_get(self):
|
async def test_http_get(self):
|
||||||
resp = await self.client.request("GET", "/config/")
|
resp = await self.client.request("GET", "/config/")
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_get_default(self):
|
async def test_get_default(self):
|
||||||
value = self.cbpi.config.get("HELLO_WORLD", "DefaultValue")
|
value = self.cbpi.config.get("HELLO_WORLD", "DefaultValue")
|
||||||
assert value == "DefaultValue"
|
assert value == "DefaultValue"
|
|
@ -6,7 +6,6 @@ from cbpi.craftbeerpi import CraftBeerPi
|
||||||
|
|
||||||
class DashboardTestCase(CraftBeerPiTestCase):
|
class DashboardTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_crud(self):
|
async def test_crud(self):
|
||||||
data = {
|
data = {
|
||||||
"name": "MyDashboard",
|
"name": "MyDashboard",
|
||||||
|
|
|
@ -4,7 +4,6 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
class IndexTestCase(CraftBeerPiTestCase):
|
class IndexTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_index(self):
|
async def test_index(self):
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,19 +11,16 @@ class IndexTestCase(CraftBeerPiTestCase):
|
||||||
resp = await self.client.get(path="/")
|
resp = await self.client.get(path="/")
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_404(self):
|
async def test_404(self):
|
||||||
# Test Index Page
|
# Test Index Page
|
||||||
resp = await self.client.get(path="/abc")
|
resp = await self.client.get(path="/abc")
|
||||||
assert resp.status == 500
|
assert resp.status == 500
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_wrong_login(self):
|
async def test_wrong_login(self):
|
||||||
resp = await self.client.post(path="/login", data={"username": "beer", "password": "123"})
|
resp = await self.client.post(path="/login", data={"username": "beer", "password": "123"})
|
||||||
print("REPONSE STATUS", resp.status)
|
print("REPONSE STATUS", resp.status)
|
||||||
assert resp.status == 403
|
assert resp.status == 403
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_login(self):
|
async def test_login(self):
|
||||||
|
|
||||||
resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"})
|
resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"})
|
||||||
|
|
|
@ -4,15 +4,13 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
class KettleTestCase(CraftBeerPiTestCase):
|
class KettleTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_get(self):
|
async def test_get(self):
|
||||||
|
|
||||||
resp = await self.client.request("GET", "/kettle")
|
resp = await self.client.request("GET", "/kettle")
|
||||||
assert resp.status == 200
|
assert resp.status == 200
|
||||||
kettle = resp.json()
|
kettle = await resp.json()
|
||||||
assert kettle != None
|
assert kettle != None
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_crud(self):
|
async def test_crud(self):
|
||||||
data = {
|
data = {
|
||||||
"name": "Test",
|
"name": "Test",
|
||||||
|
|
|
@ -7,7 +7,6 @@ import os
|
||||||
|
|
||||||
class LoggerTestCase(CraftBeerPiTestCase):
|
class LoggerTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_log_data(self):
|
async def test_log_data(self):
|
||||||
|
|
||||||
os.makedirs(os.path.join(".", "tests", "logs"), exist_ok=True)
|
os.makedirs(os.path.join(".", "tests", "logs"), exist_ok=True)
|
||||||
|
|
|
@ -4,6 +4,5 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase
|
||||||
|
|
||||||
class NotificationTestCase(CraftBeerPiTestCase):
|
class NotificationTestCase(CraftBeerPiTestCase):
|
||||||
|
|
||||||
@unittest_run_loop
|
|
||||||
async def test_actor_switch(self):
|
async def test_actor_switch(self):
|
||||||
self.cbpi.notify("test", "test")
|
self.cbpi.notify("test", "test")
|
Loading…
Reference in a new issue