From 0c957de5e5d9de27bdc25cdbb8bdfb1776be3bc7 Mon Sep 17 00:00:00 2001 From: phylax2020 Date: Thu, 5 Jan 2023 10:01:57 +0100 Subject: [PATCH 01/23] Copy dashboard files from config folder to config/dashboard when upgrading to version 4.0.7. Detect missing or empty dashboard file in config/dashboard. --- cbpi/configFolder.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/cbpi/configFolder.py b/cbpi/configFolder.py index be01fc0..043ed2e 100644 --- a/cbpi/configFolder.py +++ b/cbpi/configFolder.py @@ -8,7 +8,7 @@ import shutil import zipfile from pathlib import Path import glob - +import json class ConfigFolder: def __init__(self, configFolderPath, logsFolderPath): @@ -121,9 +121,23 @@ class ConfigFolder: # 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. # 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')): - 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): if whatsmissing == "": From 1f7cd381ad98cb40ac59327023bf313aed217c9d Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 7 Jan 2023 16:43:08 +0100 Subject: [PATCH 02/23] updated packages requirements to latest available packages --- cbpi/__init__.py | 2 +- setup.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index c4435e6..f2533f9 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.0.7" +__version__ = "4.0.8.a1" __codename__ = "November Rain" diff --git a/setup.py b/setup.py index a356451..9cac344 100644 --- a/setup.py +++ b/setup.py @@ -39,25 +39,25 @@ setup(name='cbpi4', long_description_content_type='text/markdown', install_requires=[ "typing-extensions>=4", - "aiohttp==3.8.1", + "aiohttp==3.8.3", "aiohttp-auth==0.1.1", "aiohttp-route-decorator==0.1.4", "aiohttp-security==0.4.0", - "aiohttp-session==2.11.0", + "aiohttp-session==2.12.0", "aiohttp-swagger==1.0.16", - "aiojobs==1.0.0 ", + "aiojobs==1.1.0 ", "aiosqlite==0.17.0", - "cryptography==36.0.1", - "requests==2.27.1", - "voluptuous==0.12.2", + "cryptography==38.0.4", + "requests==2.28.1", + "voluptuous==0.13.1", "pyfiglet==0.8.post1", - 'click==8.0.4', - 'shortuuid==1.0.8', - 'tabulate==0.8.9', + 'click==8.1.3', + 'shortuuid==1.0.11', + 'tabulate==0.9.0', 'asyncio-mqtt', 'PyInquirer==1.0.3', - 'colorama==0.4.4', - 'psutil==5.9.0', + 'colorama==0.4.6', + 'psutil==5.9.4', 'cbpi4gui', 'importlib_metadata', 'numpy==1.22.2', From d6316a13f64efd64370c9fb201453cb10e6cc3a8 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 7 Jan 2023 16:46:38 +0100 Subject: [PATCH 03/23] updated requirements.txt --- requirements.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/requirements.txt b/requirements.txt index 674e3bd..4f177a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,27 +1,27 @@ typing-extensions>=4 -aiohttp==3.8.1 +aiohttp==3.8.3 aiohttp-auth==0.1.1 aiohttp-route-decorator==0.1.4 aiohttp-security==0.4.0 -aiohttp-session==2.11.0 +aiohttp-session==2.12.0 aiohttp-swagger==1.0.16 -aiojobs==1.0.0 +aiojobs==1.1.0 aiosqlite==0.17.0 -cryptography==36.0.1 -requests==2.27.1 -voluptuous==0.12.2 +cryptography==38.0.4 +requests==2.28.1 +voluptuous==0.13.1 pyfiglet==0.8.post1 pandas==1.4.1 -shortuuid==1.0.8 -tabulate==0.8.9 +shortuuid==1.0.11 +tabulate==0.9.0 numpy==1.22.2 cbpi4gui -click==8.0.4 +click==8.1.3 importlib_metadata==4.11.1 asyncio-mqtt -psutil==5.9.0 +psutil==5.9.4 zipp>=0.5 PyInquirer==1.0.3 -colorama==0.4.4 +colorama==0.4.6 pytest-aiohttp coverage==6.3.1 From 8d6f21749dd657d56a404b0a82d4a84c74f736fe Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 7 Jan 2023 22:39:08 +0100 Subject: [PATCH 04/23] replaced PyInquirer with inquirer for python 3.10 compatibility --- .devcontainer/Dockerfile | 2 +- cbpi/__init__.py | 2 +- cbpi/cli.py | 2 +- requirements.txt | 2 +- setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 41c4890..42ad71c 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/vscode/devcontainers/python:3.9-bullseye +FROM mcr.microsoft.com/vscode/devcontainers/python:3.10-bullseye RUN apt-get update \ && apt-get upgrade -y diff --git a/cbpi/__init__.py b/cbpi/__init__.py index f2533f9..892692a 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.0.8.a1" +__version__ = "4.1.0.a1" __codename__ = "November Rain" diff --git a/cbpi/cli.py b/cbpi/cli.py index a0d783e..030d944 100644 --- a/cbpi/cli.py +++ b/cbpi/cli.py @@ -15,7 +15,7 @@ from colorama import Fore, Back, Style import importlib from importlib_metadata import metadata from tabulate import tabulate -from PyInquirer import prompt, print_json +from inquirer import prompt import platform import time diff --git a/requirements.txt b/requirements.txt index 4f177a0..4faa94f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ importlib_metadata==4.11.1 asyncio-mqtt psutil==5.9.4 zipp>=0.5 -PyInquirer==1.0.3 colorama==0.4.6 pytest-aiohttp coverage==6.3.1 +inquirer==3.1.1 diff --git a/setup.py b/setup.py index 9cac344..5ae9677 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ setup(name='cbpi4', 'shortuuid==1.0.11', 'tabulate==0.9.0', 'asyncio-mqtt', - 'PyInquirer==1.0.3', + 'inquirer==3.1.1', 'colorama==0.4.6', 'psutil==5.9.4', 'cbpi4gui', From 4823a04a8e9ec809debdfece3e5f87673f9f2f67 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 7 Jan 2023 22:39:17 +0100 Subject: [PATCH 05/23] python 3.10 compatibility --- tests/cbpi-test-config/config.json | 304 ++++++++++++++++++--- tests/cbpi-test-config/fermenter_data.json | 4 +- tests/cbpi-test-config/kettle.json | 4 +- tests/cbpi-test-config/sensor.json | 4 +- tests/cbpi-test-config/step_data.json | 4 +- 5 files changed, 277 insertions(+), 43 deletions(-) diff --git a/tests/cbpi-test-config/config.json b/tests/cbpi-test-config/config.json index 56f98c5..d6ab711 100644 --- a/tests/cbpi-test-config/config.json +++ b/tests/cbpi-test-config/config.json @@ -6,20 +6,6 @@ "type": "string", "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": { "description": "Add MashIn Step automatically if not defined in recipe", "name": "AddMashInStep", @@ -36,26 +22,206 @@ "type": "select", "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": { - "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", "options": null, "type": "string", - "value": "" + "value": "upload" }, - "brewfather_api_key": { - "description": "Brewfather API Kay", - "name": "brewfather_api_key", + "SENSOR_LOG_BACKUP_COUNT": { + "description": "Max. number of backup logs", + "name": "SENSOR_LOG_BACKUP_COUNT", "options": null, - "type": "string", - "value": "" + "type": "number", + "value": 3 }, - "brewfather_user_id": { - "description": "Brewfather User ID", - "name": "brewfather_user_id", + "SENSOR_LOG_MAX_BYTES": { + "description": "Max. number of bytes in sensor logs", + "name": "SENSOR_LOG_MAX_BYTES", "options": null, - "type": "string", - "value": "" + "type": "number", + "value": 100000 }, "TEMP_UNIT": { "description": "Temperature Unit", @@ -73,9 +239,78 @@ "type": "select", "value": "C" }, - "AutoMode": { - "description": "Use AutoMode in steps", - "name": "AutoMode", + "brewfather_api_key": { + "description": "Brewfather API Key", + "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": [ { "label": "Yes", @@ -110,6 +345,13 @@ "type": "step", "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": { "description": "Alternative Sensor to monitor temperature durring cooldown (if not selected, Kettle Sensor will be used)", "name": "steps_cooldown_sensor", @@ -122,7 +364,7 @@ "name": "steps_cooldown_temp", "options": null, "type": "number", - "value": "20" + "value": 35 }, "steps_mash": { "description": "Mash step type", @@ -145,4 +387,4 @@ "type": "step", "value": "NotificationStep" } -} +} \ No newline at end of file diff --git a/tests/cbpi-test-config/fermenter_data.json b/tests/cbpi-test-config/fermenter_data.json index f788313..ce96464 100644 --- a/tests/cbpi-test-config/fermenter_data.json +++ b/tests/cbpi-test-config/fermenter_data.json @@ -1,5 +1,3 @@ { - "data": [ - - ] + "data": [] } \ No newline at end of file diff --git a/tests/cbpi-test-config/kettle.json b/tests/cbpi-test-config/kettle.json index f788313..ce96464 100644 --- a/tests/cbpi-test-config/kettle.json +++ b/tests/cbpi-test-config/kettle.json @@ -1,5 +1,3 @@ { - "data": [ - - ] + "data": [] } \ No newline at end of file diff --git a/tests/cbpi-test-config/sensor.json b/tests/cbpi-test-config/sensor.json index 6346c5f..ce96464 100644 --- a/tests/cbpi-test-config/sensor.json +++ b/tests/cbpi-test-config/sensor.json @@ -1,5 +1,3 @@ { - "data": [ - - ] + "data": [] } \ No newline at end of file diff --git a/tests/cbpi-test-config/step_data.json b/tests/cbpi-test-config/step_data.json index 60a4e28..fa93cc6 100644 --- a/tests/cbpi-test-config/step_data.json +++ b/tests/cbpi-test-config/step_data.json @@ -2,7 +2,5 @@ "basic": { "name": "" }, - "steps": [ - - ] + "steps": [] } \ No newline at end of file From 4dd20ff985bf6f0484556f2af5ef4d35962beba0 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 8 Jan 2023 12:05:17 +0100 Subject: [PATCH 06/23] partial update for sattelite controller to adapt to future requirements of asyncio-mqtt Still some additional work required --- cbpi/__init__.py | 4 ++-- cbpi/controller/satellite_controller.py | 11 +++++++++-- setup.py | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 892692a..60d9b8e 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.a1" -__codename__ = "November Rain" +__version__ = "4.1.0.a2" +__codename__ = "Groundhog Day" diff --git a/cbpi/controller/satellite_controller.py b/cbpi/controller/satellite_controller.py index 230193e..73edc3d 100644 --- a/cbpi/controller/satellite_controller.py +++ b/cbpi/controller/satellite_controller.py @@ -120,10 +120,11 @@ class SatelliteController: while True: try: 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) async for message in messages: - await method(message.payload.decode()) + if message.topic.matches(topic): + await method(message.payload.decode()) except asyncio.CancelledError: # Cancel self.logger.warning("Sub Cancelled") @@ -147,7 +148,9 @@ class SatelliteController: except asyncio.CancelledError: pass + # This part needs to be updated in future as filtered_messages() is depracted and will be removed in future from asyncio-mqtt while True: + try: async with AsyncExitStack() as stack: self.tasks = set() @@ -158,9 +161,13 @@ class SatelliteController: for topic_filter in self.topic_filters: topic = topic_filter[0] + logging.info("Topic: "+topic) method = topic_filter[1] + logging.info("Method: "+str(method)) manager = self.client.filtered_messages(topic) + logging.info("Manager: " +str(manager)) messages = await stack.enter_async_context(manager) + logging.info("Messages: " +str(messages)) task = asyncio.create_task(method(messages)) self.tasks.add(task) diff --git a/setup.py b/setup.py index 5ae9677..9b97dc3 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ setup(name='cbpi4', 'click==8.1.3', 'shortuuid==1.0.11', 'tabulate==0.9.0', - 'asyncio-mqtt', + 'asyncio-mqtt==0.16.1', 'inquirer==3.1.1', 'colorama==0.4.6', 'psutil==5.9.4', From 7504ae23f65ac95fc4093e4ba41b1937a1ff9992 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 8 Jan 2023 12:19:50 +0100 Subject: [PATCH 07/23] update dockerfile to python 3.10 and requirements for asyncio-mqtt --- Dockerfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 23fa44e..69b9e0c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ RUN apk --no-cache add curl && mkdir /downloads # Download installation files 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 RUN apt-get update \ diff --git a/requirements.txt b/requirements.txt index 4faa94f..5aa85d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ numpy==1.22.2 cbpi4gui click==8.1.3 importlib_metadata==4.11.1 -asyncio-mqtt +asyncio-mqtt==0.16.1 psutil==5.9.4 zipp>=0.5 colorama==0.4.6 From 23d540f48f3be60ffc4eb21ba0f09f3b0f28e2ef Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 8 Jan 2023 13:37:34 +0100 Subject: [PATCH 08/23] reverting back cryptographie for now as v38 won't build on the pi --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5aa85d0..2e96034 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ aiohttp-session==2.12.0 aiohttp-swagger==1.0.16 aiojobs==1.1.0 aiosqlite==0.17.0 -cryptography==38.0.4 +cryptography==36.0.1 requests==2.28.1 voluptuous==0.13.1 pyfiglet==0.8.post1 diff --git a/setup.py b/setup.py index 9b97dc3..1819226 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ setup(name='cbpi4', "aiohttp-swagger==1.0.16", "aiojobs==1.1.0 ", "aiosqlite==0.17.0", - "cryptography==38.0.4", + "cryptography==36.0.1", "requests==2.28.1", "voluptuous==0.13.1", "pyfiglet==0.8.post1", From f5d9d4304a628879a9eff6491f761e1dbd2d16a7 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 8 Jan 2023 13:38:07 +0100 Subject: [PATCH 09/23] bump version --- cbpi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 60d9b8e..f419ca4 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.a2" +__version__ = "4.1.0.a3" __codename__ = "Groundhog Day" From 7f76645b05097ea075479ceebd7f5860895958bb Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 15 Jan 2023 08:25:01 +0100 Subject: [PATCH 10/23] adapted sattelite_controller init to upcomming asyncio-mqtt requirements --- cbpi/__init__.py | 2 +- cbpi/controller/satellite_controller.py | 121 +++++++++++------------- 2 files changed, 55 insertions(+), 68 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index f419ca4..254756f 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.a3" +__version__ = "4.1.0.a4" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/satellite_controller.py b/cbpi/controller/satellite_controller.py index 73edc3d..e53a086 100644 --- a/cbpi/controller/satellite_controller.py +++ b/cbpi/controller/satellite_controller.py @@ -2,7 +2,7 @@ import asyncio import json 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 cbpi import __version__ import logging @@ -34,7 +34,47 @@ class SatelliteController: self.tasks = set() 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: + print("3232") + 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")) + 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): if self.client is not None and self.client._connected: @@ -43,26 +83,25 @@ class SatelliteController: except Exception as e: self.logger.warning("Failed to push data via mqtt: {}".format(e)) - async def _actor_on(self, messages): - async for message in messages: + async def _actor_on(self, message): try: - topic_key = message.topic.split("/") + topic_key = str(message.topic).split("/") await self.cbpi.actor.on(topic_key[2]) + self.logger.warning("Processed actor {} on via mqtt".format(topic_key[2])) except Exception as e: self.logger.warning("Failed to process actor on via mqtt: {}".format(e)) - async def _actor_off(self, messages): - async for message in messages: + async def _actor_off(self, message): try: - topic_key = message.topic.split("/") + topic_key = str(message.topic).split("/") await self.cbpi.actor.off(topic_key[2]) + self.logger.warning("Processed actor {} off via mqtt".format(topic_key[2])) except Exception as e: self.logger.warning("Failed to process actor off via mqtt: {}".format(e)) - async def _actor_power(self, messages): - async for message in messages: + async def _actor_power(self, message): try: - topic_key = message.topic.split("/") + topic_key = str(message.topic).split("/") try: power=int(message.payload.decode()) if power > 100: @@ -76,8 +115,7 @@ class SatelliteController: except: self.logger.warning("Failed to set actor power via mqtt") - async def _kettleupdate(self, messages): - async for message in messages: + async def _kettleupdate(self, message): try: self.kettle=self.kettlecontroller.get_state() for item in self.kettle['data']: @@ -85,8 +123,7 @@ class SatelliteController: except Exception as e: self.logger.warning("Failed to send kettleupdate via mqtt: {}".format(e)) - async def _fermenterupdate(self, messages): - async for message in messages: + async def _fermenterupdate(self, message): try: self.fermenter=self.fermentercontroller.get_state() for item in self.fermenter['data']: @@ -94,8 +131,7 @@ class SatelliteController: except Exception as e: self.logger.warning("Failed to send fermenterupdate via mqtt: {}".format(e)) - async def _actorupdate(self, messages): - async for message in messages: + async def _actorupdate(self, message): try: self.actor=self.actorcontroller.get_state() for item in self.actor['data']: @@ -103,8 +139,7 @@ class SatelliteController: except Exception as e: self.logger.warning("Failed to send actorupdate via mqtt: {}".format(e)) - async def _sensorupdate(self, messages): - async for message in messages: + async def _sensorupdate(self, message): try: self.sensor=self.sensorcontroller.get_state() for item in self.sensor['data']: @@ -135,51 +170,3 @@ class SatelliteController: # wait before try to resubscribe 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 - - # This part needs to be updated in future as filtered_messages() is depracted and will be removed in future from asyncio-mqtt - 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] - logging.info("Topic: "+topic) - method = topic_filter[1] - logging.info("Method: "+str(method)) - manager = self.client.filtered_messages(topic) - logging.info("Manager: " +str(manager)) - messages = await stack.enter_async_context(manager) - logging.info("Messages: " +str(messages)) - 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) From 2d5ca557186b6e71624fb02c09e4776653dc6e0a Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 15 Jan 2023 09:33:11 +0100 Subject: [PATCH 11/23] add unique client id for mqtt broker --- cbpi/__init__.py | 2 +- cbpi/controller/satellite_controller.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 254756f..45c05cb 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.a4" +__version__ = "4.1.0.a5" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/satellite_controller.py b/cbpi/controller/satellite_controller.py index e53a086..c5856be 100644 --- a/cbpi/controller/satellite_controller.py +++ b/cbpi/controller/satellite_controller.py @@ -6,10 +6,12 @@ from asyncio_mqtt import Client, MqttError, Will from contextlib import AsyncExitStack, asynccontextmanager from cbpi import __version__ import logging +import shortuuid class SatelliteController: def __init__(self, cbpi): + self.client_id = shortuuid.uuid() self.cbpi = cbpi self.kettlecontroller = cbpi.kettle self.fermentercontroller = cbpi.fermenter @@ -47,7 +49,7 @@ class SatelliteController: 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")) + 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()) From 10e603e81cd05ab4a866299e5474f07a94e1c521 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 15 Jan 2023 14:21:16 +0100 Subject: [PATCH 12/23] require latest numpy t-> works with python up to 3.11 in devcontainer --- .devcontainer/Dockerfile | 2 +- cbpi/__init__.py | 2 +- cbpi/http_endpoints/http_log.py | 1 - requirements.txt | 2 +- setup.py | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 42ad71c..f1d8ce0 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/vscode/devcontainers/python:3.10-bullseye +FROM mcr.microsoft.com/vscode/devcontainers/python:3.11-bullseye RUN apt-get update \ && apt-get upgrade -y diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 45c05cb..600a623 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.a5" +__version__ = "4.1.0.rc1" __codename__ = "Groundhog Day" diff --git a/cbpi/http_endpoints/http_log.py b/cbpi/http_endpoints/http_log.py index f452026..efc716a 100644 --- a/cbpi/http_endpoints/http_log.py +++ b/cbpi/http_endpoints/http_log.py @@ -189,7 +189,6 @@ class LogHttpEndpoints: description: successful operation. """ data = await request.json() - print(data) return web.json_response(await self.cbpi.log.get_data2(data), dumps=json_dumps) diff --git a/requirements.txt b/requirements.txt index 2e96034..e54863f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ pyfiglet==0.8.post1 pandas==1.4.1 shortuuid==1.0.11 tabulate==0.9.0 -numpy==1.22.2 +numpy==1.24.1 cbpi4gui click==8.1.3 importlib_metadata==4.11.1 diff --git a/setup.py b/setup.py index 1819226..eedd5d5 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ setup(name='cbpi4', 'psutil==5.9.4', 'cbpi4gui', 'importlib_metadata', - 'numpy==1.22.2', + 'numpy==1.24.1', 'pandas==1.4.1'] + ( ['RPi.GPIO==0.7.1'] if raspberrypi else [] ), From 2429ea63d1f8badbf32f8c9722a051ecafb04359 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Fri, 20 Jan 2023 17:31:23 +0100 Subject: [PATCH 13/23] some fixes in tests and parameter for influxdb measurement name --- cbpi/__init__.py | 2 +- cbpi/controller/log_file_controller.py | 3 ++- cbpi/extension/ConfigUpdate/__init__.py | 9 +++++++++ tests/test_actor.py | 4 ---- tests/test_config.py | 5 ----- tests/test_dashboard.py | 1 - tests/test_index.py | 4 ---- tests/test_kettle.py | 4 +--- tests/test_logger.py | 1 - tests/test_notification_controller.py | 1 - 10 files changed, 13 insertions(+), 21 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 600a623..3330e38 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.rc1" +__version__ = "4.1.0.rc2" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index 50790e2..9859742 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -53,6 +53,7 @@ class LogController: self.influxdbname = self.cbpi.config.get("INFLUXDBNAME", None) self.influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None) self.influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None) + self.influxdbmeasurement = self.cbpi.config.get("INFLUXDBMEASUREMENT", "measurement") id = name try: @@ -62,7 +63,7 @@ class LogController: itemname=sensor.name.replace(" ", "_") for char in chars: 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: logging.error("InfluxDB ID Error: {}".format(e)) diff --git a/cbpi/extension/ConfigUpdate/__init__.py b/cbpi/extension/ConfigUpdate/__init__.py index 4b4de2a..ae6b42e 100644 --- a/cbpi/extension/ConfigUpdate/__init__.py +++ b/cbpi/extension/ConfigUpdate/__init__.py @@ -45,6 +45,7 @@ class ConfigUpdate(CBPiExtension): influxdbuser = self.cbpi.config.get("INFLUXDBUSER", None) influxdbpwd = self.cbpi.config.get("INFLUXDBPWD", None) influxdbcloud = self.cbpi.config.get("INFLUXDBCLOUD", None) + influxdbmeasurement = self.cbpi.config.get("INFLUXDBMEASUREMENT", None) mqttupdate = self.cbpi.config.get("MQTTUpdate", None) PRESSURE_UNIT = self.cbpi.config.get("PRESSURE_UNIT", None) SENSOR_LOG_BACKUP_COUNT = self.cbpi.config.get("SENSOR_LOG_BACKUP_COUNT", None) @@ -267,6 +268,14 @@ class ConfigUpdate(CBPiExtension): except: 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: logger.info("INIT MQTT update frequency for Kettles and Fermenters") try: diff --git a/tests/test_actor.py b/tests/test_actor.py index 69ecc82..4e40b93 100644 --- a/tests/test_actor.py +++ b/tests/test_actor.py @@ -8,7 +8,6 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %( class ActorTestCase(CraftBeerPiTestCase): - @unittest_run_loop async def test_actor_switch(self): 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") assert i.instance.state is False - @unittest_run_loop async def test_crud(self): data = { "name": "SomeActor", @@ -63,7 +61,6 @@ class ActorTestCase(CraftBeerPiTestCase): resp = await self.client.delete(path="/actor/%s" % sensor_id) assert resp.status == 204 - @unittest_run_loop async def test_crud_negative(self): data = { "name": "CustomActor", @@ -81,7 +78,6 @@ class ActorTestCase(CraftBeerPiTestCase): resp = await self.client.put(path="/actor/%s" % 9999, json=data) assert resp.status == 500 - @unittest_run_loop async def test_actor_action(self): resp = await self.client.post(path="/actor/1/action", json=dict(name="myAction", parameter=dict(name="Manuel"))) assert resp.status == 204 diff --git a/tests/test_config.py b/tests/test_config.py index ffe7cd0..c80280f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,19 +5,16 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase class ConfigTestCase(CraftBeerPiTestCase): - @unittest_run_loop async def test_get(self): assert self.cbpi.config.get("steps_boil_temp", 1) == "99" - @unittest_run_loop async def test_set_get(self): value = 35 await self.cbpi.config.set("steps_cooldown_temp", value) assert self.cbpi.config.get("steps_cooldown_temp", 1) == value - @unittest_run_loop async def test_http_set(self): value = "Some New Brewery Name" key = "BREWERY_NAME" @@ -27,12 +24,10 @@ class ConfigTestCase(CraftBeerPiTestCase): assert self.cbpi.config.get(key, -1) == value - @unittest_run_loop async def test_http_get(self): resp = await self.client.request("GET", "/config/") assert resp.status == 200 - @unittest_run_loop async def test_get_default(self): value = self.cbpi.config.get("HELLO_WORLD", "DefaultValue") assert value == "DefaultValue" \ No newline at end of file diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index ea7bbee..00f02e8 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -6,7 +6,6 @@ from cbpi.craftbeerpi import CraftBeerPi class DashboardTestCase(CraftBeerPiTestCase): - @unittest_run_loop async def test_crud(self): data = { "name": "MyDashboard", diff --git a/tests/test_index.py b/tests/test_index.py index c548113..e61656f 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -4,7 +4,6 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase class IndexTestCase(CraftBeerPiTestCase): - @unittest_run_loop async def test_index(self): @@ -12,19 +11,16 @@ class IndexTestCase(CraftBeerPiTestCase): resp = await self.client.get(path="/") assert resp.status == 200 - @unittest_run_loop async def test_404(self): # Test Index Page resp = await self.client.get(path="/abc") assert resp.status == 500 - @unittest_run_loop async def test_wrong_login(self): resp = await self.client.post(path="/login", data={"username": "beer", "password": "123"}) print("REPONSE STATUS", resp.status) assert resp.status == 403 - @unittest_run_loop async def test_login(self): resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"}) diff --git a/tests/test_kettle.py b/tests/test_kettle.py index b481d6a..a130dd6 100644 --- a/tests/test_kettle.py +++ b/tests/test_kettle.py @@ -4,15 +4,13 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase class KettleTestCase(CraftBeerPiTestCase): - @unittest_run_loop async def test_get(self): resp = await self.client.request("GET", "/kettle") assert resp.status == 200 - kettle = resp.json() + kettle = await resp.json() assert kettle != None - @unittest_run_loop async def test_crud(self): data = { "name": "Test", diff --git a/tests/test_logger.py b/tests/test_logger.py index 2f84642..50d729b 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -7,7 +7,6 @@ import os class LoggerTestCase(CraftBeerPiTestCase): - @unittest_run_loop async def test_log_data(self): os.makedirs(os.path.join(".", "tests", "logs"), exist_ok=True) diff --git a/tests/test_notification_controller.py b/tests/test_notification_controller.py index 9de65ff..b1bb149 100644 --- a/tests/test_notification_controller.py +++ b/tests/test_notification_controller.py @@ -4,6 +4,5 @@ from tests.cbpi_config_fixture import CraftBeerPiTestCase class NotificationTestCase(CraftBeerPiTestCase): - @unittest_run_loop async def test_actor_switch(self): self.cbpi.notify("test", "test") \ No newline at end of file From 4ddc9690efbd2bd8cf57d1043f2dac68a325ab1b Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Fri, 20 Jan 2023 21:26:40 +0100 Subject: [PATCH 14/23] added timeout parameter to notificartion controller for potential later usage in UI --- cbpi/controller/notification_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cbpi/controller/notification_controller.py b/cbpi/controller/notification_controller.py index eb105d6..49b1ac5 100644 --- a/cbpi/controller/notification_controller.py +++ b/cbpi/controller/notification_controller.py @@ -49,7 +49,7 @@ class NotificationController: 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 @@ -66,8 +66,8 @@ class NotificationController: actions = list(map(lambda item: prepare_action(item), 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)) - data = dict(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, timeout=timeout) self.cbpi.push_update(topic="cbpi/notification", data=data) asyncio.create_task(self._call_listener(title, message, type, action)) From 47e3bcb5297565435349eaf175e2320397a68709 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 22 Jan 2023 16:37:10 +0100 Subject: [PATCH 15/23] added alarm to httpsensor on timeout --- cbpi/__init__.py | 2 +- cbpi/controller/dashboard_controller.py | 2 +- cbpi/controller/notification_controller.py | 2 +- cbpi/controller/satellite_controller.py | 1 - cbpi/extension/httpsensor/__init__.py | 30 ++++++++++++++++++--- cbpi/http_endpoints/http_actor.py | 2 +- cbpi/http_endpoints/http_dashboard.py | 2 +- cbpi/http_endpoints/http_fermenterrecipe.py | 4 +-- cbpi/http_endpoints/http_log.py | 6 ++--- cbpi/http_endpoints/http_notification.py | 2 +- cbpi/http_endpoints/http_recipe.py | 4 +-- cbpi/http_endpoints/http_sensor.py | 2 +- cbpi/utils/encoder.py | 4 +-- 13 files changed, 42 insertions(+), 21 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 3330e38..ecee302 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.rc2" +__version__ = "4.1.0.rc3" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/dashboard_controller.py b/cbpi/controller/dashboard_controller.py index feef806..2bb77b3 100644 --- a/cbpi/controller/dashboard_controller.py +++ b/cbpi/controller/dashboard_controller.py @@ -34,7 +34,7 @@ class DashboardController: return {'elements': [], 'pathes': []} 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") with open(self.path, 'w') as outfile: json.dump(data, outfile, indent=4, sort_keys=True) diff --git a/cbpi/controller/notification_controller.py b/cbpi/controller/notification_controller.py index 49b1ac5..1040bf0 100644 --- a/cbpi/controller/notification_controller.py +++ b/cbpi/controller/notification_controller.py @@ -45,7 +45,7 @@ class NotificationController: async def _call_listener(self, title, message, type, action): for id, method in self.listener.items(): - print(id, method) + #print(id, method) asyncio.create_task(method(self.cbpi, title, message, type, action )) diff --git a/cbpi/controller/satellite_controller.py b/cbpi/controller/satellite_controller.py index c5856be..d11885e 100644 --- a/cbpi/controller/satellite_controller.py +++ b/cbpi/controller/satellite_controller.py @@ -40,7 +40,6 @@ class SatelliteController: #not sure if required like done in the old routine async def cancel_tasks(tasks): for task in tasks: - print("3232") if task.done(): continue task.cancel() diff --git a/cbpi/extension/httpsensor/__init__.py b/cbpi/extension/httpsensor/__init__.py index 3b89c69..5b77b6a 100644 --- a/cbpi/extension/httpsensor/__init__.py +++ b/cbpi/extension/httpsensor/__init__.py @@ -2,19 +2,34 @@ import asyncio from aiohttp import web from cbpi.api import * - +import time import re -import random - +import logging +from cbpi.api.dataclasses import NotificationAction, NotificationType 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)") +]) class HTTPSensor(CBPiSensor): def __init__(self, cbpi, id, props): super(HTTPSensor, self).__init__(cbpi, id, props) self.running = True self.value = 0 + self.timeout=int(self.props.get("Timeout", 60)) + self.starttime = time.time() + self.notificationsend = False + self.nextchecktime=self.starttime+self.timeout + + async def Confirm(self, **kwargs): + self.nextchecktime = time.time() + self.timeout + self.notificationsend = False + pass + + async def message(self): + self.cbpi.notify("HTTPSensor Timeout", "Sensor " + str(self.id) + " did not respond", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)]) + pass async def run(self): ''' @@ -22,12 +37,19 @@ class HTTPSensor(CBPiSensor): In this example the code is executed every second ''' while self.running is True: + currenttime=time.time() + if currenttime > self.nextchecktime and self.notificationsend == False: + await self.message() + self.notificationsend=True try: cache_value = cache.pop(self.props.get("Key"), None) if cache_value is not None: self.value = float(cache_value) self.push_update(self.value) + self.nextchecktime = currenttime + self.timeout + self.notificationsend = False except Exception as e: + logging.error(e) pass await asyncio.sleep(1) diff --git a/cbpi/http_endpoints/http_actor.py b/cbpi/http_endpoints/http_actor.py index 7738a5a..d3bec9b 100644 --- a/cbpi/http_endpoints/http_actor.py +++ b/cbpi/http_endpoints/http_actor.py @@ -247,7 +247,7 @@ class ActorHttpEndpoints(): """ actor_id = request.match_info['id'] data = await request.json() - print(data) + #print(data) await self.controller.call_action(actor_id, data.get("action"), data.get("parameter")) return web.Response(status=204) \ No newline at end of file diff --git a/cbpi/http_endpoints/http_dashboard.py b/cbpi/http_endpoints/http_dashboard.py index f24fe0c..38073cf 100644 --- a/cbpi/http_endpoints/http_dashboard.py +++ b/cbpi/http_endpoints/http_dashboard.py @@ -69,7 +69,7 @@ class DashBoardHttpEndpoints: data = await request.json() dashboard_id = int(request.match_info['id']) await self.cbpi.dashboard.add_content(dashboard_id, data) - print("##### SAVE") + #print("##### SAVE") return web.Response(status=204) @request_mapping(path="/{id:\d+}/content", method="DELETE", auth_required=False) diff --git a/cbpi/http_endpoints/http_fermenterrecipe.py b/cbpi/http_endpoints/http_fermenterrecipe.py index e974e3e..e653d76 100644 --- a/cbpi/http_endpoints/http_fermenterrecipe.py +++ b/cbpi/http_endpoints/http_fermenterrecipe.py @@ -58,7 +58,7 @@ class FermenterRecipeHttpEndpoints(): description: successful operation """ data = await request.json() - print(data) + #print(data) return web.json_response(dict(id=await self.controller.create(data.get("name")))) @@ -90,7 +90,7 @@ class FermenterRecipeHttpEndpoints(): data = await request.json() name = request.match_info['name'] await self.controller.save(name, data) - print(data) + #print(data) return web.Response(status=204) @request_mapping(path="/{name}", method="DELETE", auth_required=False) diff --git a/cbpi/http_endpoints/http_log.py b/cbpi/http_endpoints/http_log.py index efc716a..8183edb 100644 --- a/cbpi/http_endpoints/http_log.py +++ b/cbpi/http_endpoints/http_log.py @@ -239,7 +239,7 @@ class LogHttpEndpoints: data = await request.json() result = await self.cbpi.log.get_data(data) - print("JSON") - print(json.dumps(result, cls=ComplexEncoder)) - print("JSON----") + #print("JSON") + #print(json.dumps(result, cls=ComplexEncoder)) + #print("JSON----") return web.json_response(result, dumps=json_dumps) diff --git a/cbpi/http_endpoints/http_notification.py b/cbpi/http_endpoints/http_notification.py index b61e6f2..b25c4e3 100644 --- a/cbpi/http_endpoints/http_notification.py +++ b/cbpi/http_endpoints/http_notification.py @@ -35,6 +35,6 @@ class NotificationHttpEndpoints: notification_id = request.match_info['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) return web.Response(status=204) \ No newline at end of file diff --git a/cbpi/http_endpoints/http_recipe.py b/cbpi/http_endpoints/http_recipe.py index 734149b..9b8a6cc 100644 --- a/cbpi/http_endpoints/http_recipe.py +++ b/cbpi/http_endpoints/http_recipe.py @@ -57,7 +57,7 @@ class RecipeHttpEndpoints(): description: successful operation """ data = await request.json() - print(data) + #print(data) return web.json_response(dict(id=await self.controller.create(data.get("name")))) @@ -89,7 +89,7 @@ class RecipeHttpEndpoints(): data = await request.json() name = request.match_info['name'] await self.controller.save(name, data) - print(data) + #print(data) return web.Response(status=204) @request_mapping(path="/{name}", method="DELETE", auth_required=False) diff --git a/cbpi/http_endpoints/http_sensor.py b/cbpi/http_endpoints/http_sensor.py index bea7c08..d47aa9a 100644 --- a/cbpi/http_endpoints/http_sensor.py +++ b/cbpi/http_endpoints/http_sensor.py @@ -224,7 +224,7 @@ class SensorHttpEndpoints(): """ sensor_id = request.match_info['id'] data = await request.json() - print(data) + #print(data) await self.controller.call_action(sensor_id, data.get("action"), data.get("parameter")) return web.Response(status=204) diff --git a/cbpi/utils/encoder.py b/cbpi/utils/encoder.py index 616d3a9..475ec4d 100644 --- a/cbpi/utils/encoder.py +++ b/cbpi/utils/encoder.py @@ -13,10 +13,10 @@ class ComplexEncoder(JSONEncoder): elif isinstance(obj, datetime.datetime): return obj.__str__() elif isinstance(obj, Timestamp): - print("TIMe") + #print("TIMe") return obj.__str__() else: - print(type(obj)) + #print(type(obj)) raise TypeError() except Exception as e: From 85490cebc8610ed3cf86bf3a6820d5fd2a0d4997 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 22 Jan 2023 19:53:54 +0100 Subject: [PATCH 16/23] added timeout notification to mqtt sensor; => 0 value will deactivate function --- cbpi/__init__.py | 2 +- cbpi/extension/httpsensor/__init__.py | 16 ++++++++------- cbpi/extension/mqtt_sensor/__init__.py | 27 ++++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index ecee302..023dc6d 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.rc3" +__version__ = "4.1.0.rc4" __codename__ = "Groundhog Day" diff --git a/cbpi/extension/httpsensor/__init__.py b/cbpi/extension/httpsensor/__init__.py index 5b77b6a..7da1cdf 100644 --- a/cbpi/extension/httpsensor/__init__.py +++ b/cbpi/extension/httpsensor/__init__.py @@ -10,7 +10,7 @@ from cbpi.api.dataclasses import NotificationAction, NotificationType cache = {} @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)") + Property.Number(label="Timeout", configurable="True",unit="sec",description="Timeout in seconds to send notification (default:60 | deactivated: 0)") ]) class HTTPSensor(CBPiSensor): def __init__(self, cbpi, id, props): @@ -37,17 +37,19 @@ class HTTPSensor(CBPiSensor): In this example the code is executed every second ''' while self.running is True: - currenttime=time.time() - if currenttime > self.nextchecktime and self.notificationsend == False: - await self.message() - self.notificationsend=True + if self.timeout !=0: + currenttime=time.time() + if currenttime > self.nextchecktime and self.notificationsend == False: + await self.message() + self.notificationsend=True try: cache_value = cache.pop(self.props.get("Key"), None) if cache_value is not None: self.value = float(cache_value) self.push_update(self.value) - self.nextchecktime = currenttime + self.timeout - self.notificationsend = False + if self.timeout !=0: + self.nextchecktime = currenttime + self.timeout + self.notificationsend = False except Exception as e: logging.error(e) pass diff --git a/cbpi/extension/mqtt_sensor/__init__.py b/cbpi/extension/mqtt_sensor/__init__.py index e2d6e48..e121eb8 100644 --- a/cbpi/extension/mqtt_sensor/__init__.py +++ b/cbpi/extension/mqtt_sensor/__init__.py @@ -1,14 +1,17 @@ # -*- coding: utf-8 -*- import asyncio - +from cbpi.api.dataclasses import NotificationAction, NotificationType from cbpi.api import parameters, Property, CBPiSensor from cbpi.api import * import logging import json +import time @parameters([Property.Text(label="Topic", configurable=True, description="MQTT Topic"), 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): def __init__(self, cbpi, id, props): @@ -19,6 +22,19 @@ class MQTTSensor(CBPiSensor): self.payload_text = self.payload_text.split('.') self.mqtt_task = self.cbpi.satellite.subcribe(self.Topic, self.on_message) 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 + + async def Confirm(self, **kwargs): + self.nextchecktime = time.time() + self.timeout + self.notificationsend = False + pass + + async def message(self): + self.cbpi.notify("MQTTSensor Timeout", "Sensor " + str(self.Topic) + " did not respond", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)]) + pass async def on_message(self, message): val = json.loads(message) @@ -31,11 +47,18 @@ class MQTTSensor(CBPiSensor): self.value = float(val) self.log_data(self.value) self.push_update(self.value) + if self.timeout !=0: + self.nextchecktime = time.time() + self.timeout + self.notificationsend = False except Exception as e: logging.info("MQTT Sensor Error {}".format(e)) async def run(self): 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) def get_state(self): From 9e50b790e0b230edc5bc2b061fffedc5d88144c0 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Mon, 23 Jan 2023 07:15:29 +0100 Subject: [PATCH 17/23] aded sensor name and timestamp of last data to notification --- cbpi/__init__.py | 2 +- cbpi/extension/httpsensor/__init__.py | 7 ++++++- cbpi/extension/mqtt_sensor/__init__.py | 7 ++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 023dc6d..6c0ca09 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.rc4" +__version__ = "4.1.0.rc5" __codename__ = "Groundhog Day" diff --git a/cbpi/extension/httpsensor/__init__.py b/cbpi/extension/httpsensor/__init__.py index 7da1cdf..0abd2a4 100644 --- a/cbpi/extension/httpsensor/__init__.py +++ b/cbpi/extension/httpsensor/__init__.py @@ -3,6 +3,7 @@ import asyncio from aiohttp import web from cbpi.api import * import time +from datetime import datetime import re import logging from cbpi.api.dataclasses import NotificationAction, NotificationType @@ -21,6 +22,8 @@ class HTTPSensor(CBPiSensor): 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 @@ -28,7 +31,8 @@ class HTTPSensor(CBPiSensor): pass async def message(self): - self.cbpi.notify("HTTPSensor Timeout", "Sensor " + str(self.id) + " did not respond", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)]) + 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): @@ -50,6 +54,7 @@ class HTTPSensor(CBPiSensor): if self.timeout !=0: self.nextchecktime = currenttime + self.timeout self.notificationsend = False + self.lastdata=time.time() except Exception as e: logging.error(e) pass diff --git a/cbpi/extension/mqtt_sensor/__init__.py b/cbpi/extension/mqtt_sensor/__init__.py index e121eb8..c50a776 100644 --- a/cbpi/extension/mqtt_sensor/__init__.py +++ b/cbpi/extension/mqtt_sensor/__init__.py @@ -6,6 +6,7 @@ from cbpi.api import * import logging import json import time +from datetime import datetime @parameters([Property.Text(label="Topic", configurable=True, description="MQTT Topic"), Property.Text(label="PayloadDictionary", configurable=True, default_value="", @@ -26,6 +27,8 @@ class MQTTSensor(CBPiSensor): 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 @@ -33,7 +36,8 @@ class MQTTSensor(CBPiSensor): pass async def message(self): - self.cbpi.notify("MQTTSensor Timeout", "Sensor " + str(self.Topic) + " did not respond", NotificationType.WARNING, action=[NotificationAction("OK", self.Confirm)]) + 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): @@ -50,6 +54,7 @@ class MQTTSensor(CBPiSensor): if self.timeout !=0: self.nextchecktime = time.time() + self.timeout self.notificationsend = False + self.lastdata=time.time() except Exception as e: logging.info("MQTT Sensor Error {}".format(e)) From 366d6cbe91d5cb61a8341e1319cbace57bcdb609 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Thu, 26 Jan 2023 17:43:19 +0100 Subject: [PATCH 18/23] Improved handling of missing or corrupt step_data and fermenterstep_data files at startup --- cbpi/__init__.py | 2 +- cbpi/configFolder.py | 4 +-- cbpi/controller/fermentation_controller.py | 20 ++++++++--- cbpi/controller/step_controller.py | 42 ++++++++++++++++------ 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 6c0ca09..2f7ee36 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.rc5" +__version__ = "4.1.0.rc6" __codename__ = "Groundhog Day" diff --git a/cbpi/configFolder.py b/cbpi/configFolder.py index 043ed2e..807550b 100644 --- a/cbpi/configFolder.py +++ b/cbpi/configFolder.py @@ -91,8 +91,8 @@ class ConfigFolder: ['actor.json', 'file'], ['sensor.json', 'file'], ['kettle.json', 'file'], - ['fermenter_data.json', 'file'], - ['step_data.json', 'file'], + #['fermenter_data.json', 'file'], created by fermentation_controller @ start if not available + #['step_data.json', 'file'], created by step_controller @ start if not available ['config.json', 'file'], ['craftbeerpi.service', 'file'], ['chromium.desktop', 'file'], diff --git a/cbpi/controller/fermentation_controller.py b/cbpi/controller/fermentation_controller.py index 8fa5de2..9dbc6ba 100644 --- a/cbpi/controller/fermentation_controller.py +++ b/cbpi/controller/fermentation_controller.py @@ -36,7 +36,7 @@ class FermentationController: def check_fermenter_file(self): 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": [ ] @@ -71,11 +71,23 @@ class FermentationController: async def load(self): - with open(self.path) as json_file: - data = json.load(json_file) + try: + with open(self.path) as json_file: + data = json.load(json_file) + for i in data["data"]: + 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)) + self.data.append(self._create(i)) def _create_step(self, fermenter, item): id = item.get("id") diff --git a/cbpi/controller/step_controller.py b/cbpi/controller/step_controller.py index 8850fd9..651b3fb 100644 --- a/cbpi/controller/step_controller.py +++ b/cbpi/controller/step_controller.py @@ -6,6 +6,7 @@ import yaml import logging import os.path from os import listdir +import os from os.path import isfile, join import shortuuid from cbpi.api.dataclasses import NotificationAction, Props, Step @@ -54,23 +55,42 @@ class StepController: # create file if not exists 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: json.dump(dict(basic={}, steps=[]), file, indent=4, sort_keys=True) #load from json file - with open(self.path) as json_file: - data = json.load(json_file) - self.basic_data = data["basic"] - self.profile = data["steps"] + try: + 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 + 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 - 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)) + # 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)) async def add(self, item: Step): logging.debug("Add step") From 56ec309dabb11fb4b02b6d8121e23678c1542101 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Fri, 27 Jan 2023 12:26:55 +0100 Subject: [PATCH 19/23] transfer guiversion to gui --- cbpi/__init__.py | 2 +- cbpi/controller/plugin_controller.py | 4 ++-- cbpi/http_endpoints/http_system.py | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 2f7ee36..567a9ca 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.rc6" +__version__ = "4.1.0.rc7" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/plugin_controller.py b/cbpi/controller/plugin_controller.py index 26081e6..e958371 100644 --- a/cbpi/controller/plugin_controller.py +++ b/cbpi/controller/plugin_controller.py @@ -190,14 +190,14 @@ class PluginController(): return result - async def load_plugin_list(self): + async def load_plugin_list(self, filter="cbpi"): result = [] try: discovered_plugins = { name: importlib.import_module(name) for finder, name, ispkg 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(): from importlib.metadata import version diff --git a/cbpi/http_endpoints/http_system.py b/cbpi/http_endpoints/http_system.py index 823ef6f..8de8af2 100644 --- a/cbpi/http_endpoints/http_system.py +++ b/cbpi/http_endpoints/http_system.py @@ -28,6 +28,12 @@ class SystemHttpEndpoints: "200": 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( actor=self.cbpi.actor.get_state(), fermenter=self.cbpi.fermenter.get_state(), @@ -37,6 +43,7 @@ class SystemHttpEndpoints: fermentersteps=self.cbpi.fermenter.get_fermenter_steps(), config=self.cbpi.config.get_state(), version=__version__, + guiversion=version, codename=__codename__) , dumps=json_dumps) From 853920be6d27feee3a6e64756e595ed93d485094 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Fri, 27 Jan 2023 17:09:53 +0100 Subject: [PATCH 20/23] Fixed Typo in fermenterhysteresis description --- cbpi/extension/FermenterHysteresis/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cbpi/extension/FermenterHysteresis/__init__.py b/cbpi/extension/FermenterHysteresis/__init__.py index 0376d01..d100d62 100644 --- a/cbpi/extension/FermenterHysteresis/__init__.py +++ b/cbpi/extension/FermenterHysteresis/__init__.py @@ -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"), 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="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="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.Select(label="AutoStart", options=["Yes","No"],description="Autostart Fermenter on cbpi start"), Property.Sensor(label="sensor2",description="Optional Sensor for LCDisplay(e.g. iSpindle)")]) From 90f94525be32e42a41040174d3d8ef44fc732480 Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sat, 28 Jan 2023 16:34:09 +0100 Subject: [PATCH 21/23] trying to fix dleetion of logfiles via analytics page --- cbpi/__init__.py | 2 +- cbpi/controller/log_file_controller.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 567a9ca..83de51a 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.rc7" +__version__ = "4.1.0.rc8" __codename__ = "Groundhog Day" diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index 9859742..a4ab4e7 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -181,12 +181,18 @@ class LogController: def clear_log(self, name:str ) -> str: 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: + self.datalogger[name].removeHandler(self.datalogger[name].handlers[0]) 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: From 679d10d4dc1ef5fc3b21050e6af5eac87abe37ec Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Sun, 29 Jan 2023 12:26:03 +0100 Subject: [PATCH 22/23] newer pandas in requirements --- cbpi/controller/log_file_controller.py | 7 ++----- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index a4ab4e7..9e8c9a0 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -154,15 +154,12 @@ class LogController: return data 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() 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*")) - 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.dropna() result[id] = {"time": df.index.astype(str).tolist(), "value":df.Values.tolist()} diff --git a/requirements.txt b/requirements.txt index e54863f..9bcb800 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ cryptography==36.0.1 requests==2.28.1 voluptuous==0.13.1 pyfiglet==0.8.post1 -pandas==1.4.1 +pandas==1.5.3 shortuuid==1.0.11 tabulate==0.9.0 numpy==1.24.1 diff --git a/setup.py b/setup.py index eedd5d5..5e32bee 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,7 @@ setup(name='cbpi4', 'cbpi4gui', 'importlib_metadata', 'numpy==1.24.1', - 'pandas==1.4.1'] + ( + 'pandas==1.5.3'] + ( ['RPi.GPIO==0.7.1'] if raspberrypi else [] ), dependency_links=[ From 760d9b8c7a23f7d78290ddec33c765b4d8ce33ec Mon Sep 17 00:00:00 2001 From: avollkopf <43980694+avollkopf@users.noreply.github.com> Date: Wed, 1 Feb 2023 06:41:34 +0100 Subject: [PATCH 23/23] bump revision --- cbpi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbpi/__init__.py b/cbpi/__init__.py index 83de51a..efd5ddd 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1,3 +1,3 @@ -__version__ = "4.1.0.rc8" +__version__ = "4.1.0" __codename__ = "Groundhog Day"